import {
  StorageConfig,
  Document,
  ChatbotError,
} from "../contexts/ChatbotContext";
import { createClient, SupabaseClient } from "@supabase/supabase-js";

// File processing interfaces
export interface FileMetadata {
  originalName: string;
  size: number;
  type: string;
  uploadedAt: Date;
  description?: string;
  tags?: string[];
  extractedText?: string;
  processingStatus: "pending" | "processing" | "completed" | "failed";
}

export interface StorageUploadProgress {
  fileId: string;
  progress: number;
  status: "uploading" | "processing" | "completed" | "failed";
  error?: string;
}

export interface UploadOptions {
  description?: string;
  tags?: string[];
  extractText?: boolean;
  overwrite?: boolean;
}

// Abstract base class for storage operations
export abstract class BaseStorage {
  protected config: StorageConfig;
  protected isConnected: boolean = false;

  constructor(config: StorageConfig) {
    this.config = config;
  }

  abstract connect(): Promise<void>;
  abstract disconnect(): Promise<void>;
  abstract uploadFile(file: File, options?: UploadOptions): Promise<Document>;
  abstract downloadFile(documentId: string): Promise<Blob>;
  abstract deleteFile(documentId: string): Promise<void>;
  abstract listFiles(): Promise<Document[]>;
  abstract getFileMetadata(documentId: string): Promise<FileMetadata>;
  abstract healthCheck(): Promise<boolean>;

  public getStatus(): { isConnected: boolean; config: StorageConfig } {
    return {
      isConnected: this.isConnected,
      config: this.config,
    };
  }

  public updateConfig(updates: Partial<StorageConfig>): void {
    this.config = { ...this.config, ...updates };
  }
}

// Supabase Storage Implementation
export class SupabaseStorage extends BaseStorage {
  private client: SupabaseClient | null = null;
  private progressCallbacks: Map<
    string,
    (progress: StorageUploadProgress) => void
  > = new Map();

  constructor(config: StorageConfig) {
    super(config);
  }

  async connect(): Promise<void> {
    try {
      if (!this.config.url || !this.config.apiKey) {
        throw new Error("Supabase URL and API key are required");
      }

      this.client = createClient(this.config.url, this.config.apiKey);

      // Test connection by listing buckets
      const { data, error } = await this.client.storage.listBuckets();
      if (error) {
        throw new Error(
          `Failed to connect to Supabase Storage: ${error.message}`
        );
      }

      // Check if our bucket exists, create if not
      const bucketExists = data?.some(
        (bucket) => bucket.name === this.config.bucket
      );
      if (!bucketExists) {
        const { error: createError } = await this.client.storage.createBucket(
          this.config.bucket,
          {
            public: false,
            allowedMimeTypes: this.config.allowedTypes,
            fileSizeLimit: this.config.maxFileSize,
          }
        );
        if (createError) {
          console.warn(
            `Failed to create bucket ${this.config.bucket}:`,
            createError
          );
          // Continue anyway, bucket might exist but not be visible to this user
        }
      }

      this.isConnected = true;
    } catch (error) {
      this.isConnected = false;
      throw new Error(
        `Failed to connect to Supabase Storage: ${
          error instanceof Error ? error.message : "Unknown error"
        }`
      );
    }
  }

  async disconnect(): Promise<void> {
    this.client = null;
    this.isConnected = false;
    this.progressCallbacks.clear();
  }

  async uploadFile(file: File, options: UploadOptions = {}): Promise<Document> {
    if (!this.isConnected || !this.client) {
      throw new Error("Storage is not connected");
    }

    // Validate file
    this.validateFile(file);

    const fileId = `doc_${Date.now()}_${Math.random()
      .toString(36)
      .substr(2, 9)}`;
    const fileName = `${fileId}_${file.name}`;

    try {
      // Update progress
      this.updateProgress(fileId, { fileId, progress: 0, status: "uploading" });

      // Upload file to Supabase Storage
      const { data: uploadData, error: uploadError } = await this.client.storage
        .from(this.config.bucket)
        .upload(fileName, file, {
          cacheControl: "3600",
          upsert: options.overwrite || false,
        });

      if (uploadError) {
        throw new Error(`Failed to upload file: ${uploadError.message}`);
      }

      this.updateProgress(fileId, {
        fileId,
        progress: 50,
        status: "processing",
      });

      // Extract text content if requested
      let extractedText = "";
      if (options.extractText !== false) {
        // Default to true
        try {
          extractedText = await this.extractTextFromFile(file);
        } catch (error) {
          console.warn("Failed to extract text from file:", error);
          // Continue without text extraction
        }
      }

      this.updateProgress(fileId, {
        fileId,
        progress: 80,
        status: "processing",
      });

      // Create document metadata
      const document: Document = {
        id: fileId,
        title: options.description || file.name,
        content: extractedText,
        metadata: {
          source: fileName,
          uploadedAt: new Date(),
          size: file.size,
          type: file.type,
          description: options.description,
          tags: options.tags,
        },
        status: "ready",
      };

      this.updateProgress(fileId, {
        fileId,
        progress: 100,
        status: "completed",
      });

      return document;
    } catch (error) {
      this.updateProgress(fileId, {
        fileId,
        progress: 0,
        status: "failed",
        error: error instanceof Error ? error.message : "Unknown error",
      });
      throw error;
    }
  }

  async downloadFile(documentId: string): Promise<Blob> {
    if (!this.isConnected || !this.client) {
      throw new Error("Storage is not connected");
    }

    try {
      // Find the file by document ID
      const files = await this.listFiles();
      const document = files.find((doc) => doc.id === documentId);

      if (!document || !document.metadata.source) {
        throw new Error(`Document ${documentId} not found`);
      }

      const { data, error } = await this.client.storage
        .from(this.config.bucket)
        .download(document.metadata.source);

      if (error) {
        throw new Error(`Failed to download file: ${error.message}`);
      }

      return data;
    } catch (error) {
      throw new Error(
        `Failed to download file: ${
          error instanceof Error ? error.message : "Unknown error"
        }`
      );
    }
  }

  async deleteFile(documentId: string): Promise<void> {
    if (!this.isConnected || !this.client) {
      throw new Error("Storage is not connected");
    }

    try {
      // Find the file by document ID
      const files = await this.listFiles();
      const document = files.find((doc) => doc.id === documentId);

      if (!document || !document.metadata.source) {
        throw new Error(`Document ${documentId} not found`);
      }

      const { error } = await this.client.storage
        .from(this.config.bucket)
        .remove([document.metadata.source]);

      if (error) {
        throw new Error(`Failed to delete file: ${error.message}`);
      }
    } catch (error) {
      throw new Error(
        `Failed to delete file: ${
          error instanceof Error ? error.message : "Unknown error"
        }`
      );
    }
  }

  async listFiles(): Promise<Document[]> {
    if (!this.isConnected || !this.client) {
      throw new Error("Storage is not connected");
    }

    try {
      const { data, error } = await this.client.storage
        .from(this.config.bucket)
        .list();

      if (error) {
        throw new Error(`Failed to list files: ${error.message}`);
      }

      // Convert storage objects to documents
      const documents: Document[] = [];
      for (const file of data || []) {
        try {
          // Extract document ID from filename (assuming format: docId_originalName)
          const parts = file.name.split("_");
          if (parts.length < 2) continue;

          const docId = parts[0];
          const originalName = parts.slice(1).join("_");

          // Get file URL for content extraction (if needed)
          const { data: urlData } = this.client.storage
            .from(this.config.bucket)
            .getPublicUrl(file.name);

          const document: Document = {
            id: docId,
            title: originalName,
            content: "", // Content would be extracted separately if needed
            metadata: {
              source: file.name,
              uploadedAt: new Date(file.created_at || Date.now()),
              size: file.metadata?.size || 0,
              type: file.metadata?.mimetype || "application/octet-stream",
            },
            status: "ready",
          };

          documents.push(document);
        } catch (error) {
          console.warn(`Failed to process file ${file.name}:`, error);
          continue;
        }
      }

      return documents;
    } catch (error) {
      throw new Error(
        `Failed to list files: ${
          error instanceof Error ? error.message : "Unknown error"
        }`
      );
    }
  }

  async getFileMetadata(documentId: string): Promise<FileMetadata> {
    if (!this.isConnected || !this.client) {
      throw new Error("Storage is not connected");
    }

    try {
      const files = await this.listFiles();
      const document = files.find((doc) => doc.id === documentId);

      if (!document) {
        throw new Error(`Document ${documentId} not found`);
      }

      return {
        originalName: document.title,
        size: document.metadata.size,
        type: document.metadata.type,
        uploadedAt: document.metadata.uploadedAt,
        description: document.metadata.description,
        tags: document.metadata.tags,
        extractedText: document.content,
        processingStatus: "completed",
      };
    } catch (error) {
      throw new Error(
        `Failed to get file metadata: ${
          error instanceof Error ? error.message : "Unknown error"
        }`
      );
    }
  }

  async healthCheck(): Promise<boolean> {
    if (!this.isConnected || !this.client) return false;

    try {
      await this.client.storage.listBuckets();
      return true;
    } catch (error) {
      return false;
    }
  }

  // Progress tracking
  setProgressCallback(
    fileId: string,
    callback: (progress: StorageUploadProgress) => void
  ): void {
    this.progressCallbacks.set(fileId, callback);
  }

  removeProgressCallback(fileId: string): void {
    this.progressCallbacks.delete(fileId);
  }

  private updateProgress(
    fileId: string,
    progress: StorageUploadProgress
  ): void {
    const callback = this.progressCallbacks.get(fileId);
    if (callback) {
      callback(progress);
    }
  }

  private validateFile(file: File): void {
    // Check file size
    if (this.config.maxFileSize && file.size > this.config.maxFileSize) {
      throw new Error(
        `File size ${file.size} exceeds maximum allowed size ${this.config.maxFileSize}`
      );
    }

    // Check file type
    if (this.config.allowedTypes && this.config.allowedTypes.length > 0) {
      const isAllowed = this.config.allowedTypes.some((type) => {
        if (type.includes("*")) {
          const baseType = type.split("/")[0];
          return file.type.startsWith(baseType);
        }
        return file.type === type;
      });

      if (!isAllowed) {
        throw new Error(
          `File type ${
            file.type
          } is not allowed. Allowed types: ${this.config.allowedTypes.join(
            ", "
          )}`
        );
      }
    }
  }

  private async extractTextFromFile(file: File): Promise<string> {
    // Simple text extraction for common file types
    if (file.type === "text/plain") {
      return await file.text();
    }

    if (file.type === "application/json") {
      try {
        const text = await file.text();
        const json = JSON.parse(text);
        return JSON.stringify(json, null, 2);
      } catch (error) {
        return await file.text();
      }
    }

    if (file.type.startsWith("text/")) {
      return await file.text();
    }

    // For other file types, we'd need specialized libraries
    // For now, return empty string and let the user provide content manually
    return "";
  }
}

// Local Storage Implementation (for development/testing)
export class LocalStorage extends BaseStorage {
  private files: Map<string, { document: Document; blob: Blob }> = new Map();

  async connect(): Promise<void> {
    this.isConnected = true;
  }

  async disconnect(): Promise<void> {
    this.isConnected = false;
    this.files.clear();
  }

  async uploadFile(file: File, options: UploadOptions = {}): Promise<Document> {
    if (!this.isConnected) {
      throw new Error("Storage is not connected");
    }

    this.validateFile(file);

    const fileId = `doc_${Date.now()}_${Math.random()
      .toString(36)
      .substr(2, 9)}`;

    // Extract text if possible
    let extractedText = "";
    if (options.extractText !== false) {
      try {
        if (file.type.startsWith("text/")) {
          extractedText = await file.text();
        }
      } catch (error) {
        console.warn("Failed to extract text:", error);
      }
    }

    const document: Document = {
      id: fileId,
      title: options.description || file.name,
      content: extractedText,
      metadata: {
        source: file.name,
        uploadedAt: new Date(),
        size: file.size,
        type: file.type,
        description: options.description,
        tags: options.tags,
      },
      status: "ready",
    };

    this.files.set(fileId, { document, blob: file });
    return document;
  }

  async downloadFile(documentId: string): Promise<Blob> {
    if (!this.isConnected) {
      throw new Error("Storage is not connected");
    }

    const fileData = this.files.get(documentId);
    if (!fileData) {
      throw new Error(`Document ${documentId} not found`);
    }

    return fileData.blob;
  }

  async deleteFile(documentId: string): Promise<void> {
    if (!this.isConnected) {
      throw new Error("Storage is not connected");
    }

    if (!this.files.has(documentId)) {
      throw new Error(`Document ${documentId} not found`);
    }

    this.files.delete(documentId);
  }

  async listFiles(): Promise<Document[]> {
    if (!this.isConnected) {
      throw new Error("Storage is not connected");
    }

    return Array.from(this.files.values()).map((fileData) => fileData.document);
  }

  async getFileMetadata(documentId: string): Promise<FileMetadata> {
    if (!this.isConnected) {
      throw new Error("Storage is not connected");
    }

    const fileData = this.files.get(documentId);
    if (!fileData) {
      throw new Error(`Document ${documentId} not found`);
    }

    const doc = fileData.document;
    return {
      originalName: doc.title,
      size: doc.metadata.size,
      type: doc.metadata.type,
      uploadedAt: doc.metadata.uploadedAt,
      description: doc.metadata.description,
      tags: doc.metadata.tags,
      extractedText: doc.content,
      processingStatus: "completed",
    };
  }

  async healthCheck(): Promise<boolean> {
    return this.isConnected;
  }

  private validateFile(file: File): void {
    if (this.config.maxFileSize && file.size > this.config.maxFileSize) {
      throw new Error(`File size exceeds maximum allowed size`);
    }

    if (this.config.allowedTypes && this.config.allowedTypes.length > 0) {
      const isAllowed = this.config.allowedTypes.some((type) => {
        if (type.includes("*")) {
          const baseType = type.split("/")[0];
          return file.type.startsWith(baseType);
        }
        return file.type === type;
      });

      if (!isAllowed) {
        throw new Error(`File type ${file.type} is not allowed`);
      }
    }
  }
}

// Factory function to create storage instances
export function createStorage(config: StorageConfig): BaseStorage {
  // For now, we only have Supabase and Local implementations
  if (config.url && config.apiKey) {
    return new SupabaseStorage(config);
  } else {
    return new LocalStorage(config);
  }
}

// Storage utilities
export class StorageUtils {
  static validateConfig(config: StorageConfig): boolean {
    if (!config.bucket) return false;

    // For cloud storage, we need URL and API key
    if (config.url && !config.apiKey) return false;
    if (config.apiKey && !config.url) return false;

    return true;
  }

  static getDefaultConfig(): Partial<StorageConfig> {
    return {
      maxFileSize: 50 * 1024 * 1024, // 50MB
      allowedTypes: [
        "text/*",
        "application/pdf",
        "application/msword",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application/json",
        "application/csv",
        "text/csv",
      ],
    };
  }

  static formatFileSize(bytes: number): string {
    if (bytes === 0) return "0 Bytes";

    const k = 1024;
    const sizes = ["Bytes", "KB", "MB", "GB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));

    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  }

  static getFileExtension(filename: string): string {
    return filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2);
  }

  static generateUniqueFileName(originalName: string): string {
    const ext = this.getFileExtension(originalName);
    const baseName = originalName.replace(`.${ext}`, "");
    const timestamp = Date.now();
    const random = Math.random().toString(36).substr(2, 5);

    return `${baseName}_${timestamp}_${random}.${ext}`;
  }

  static isTextFile(mimeType: string): boolean {
    return (
      mimeType.startsWith("text/") ||
      mimeType === "application/json" ||
      mimeType === "application/xml" ||
      mimeType === "application/csv"
    );
  }

  static isSupportedFileType(
    mimeType: string,
    allowedTypes?: string[]
  ): boolean {
    if (!allowedTypes || allowedTypes.length === 0) return true;

    return allowedTypes.some((type) => {
      if (type.includes("*")) {
        const baseType = type.split("/")[0];
        return mimeType.startsWith(baseType);
      }
      return mimeType === type;
    });
  }
}

export default {
  BaseStorage,
  SupabaseStorage,
  LocalStorage,
  createStorage,
  StorageUtils,
};
