import { createClient, SupabaseClient } from "@supabase/supabase-js";

export interface SupabaseStorageConfig {
  url: string;
  anonKey: string;
  bucket: string;
}

export interface DocumentUploadResult {
  id: string;
  path: string;
  url: string;
  size: number;
  mimeType: string;
  uploadedAt: Date;
}

export interface DocumentVersion {
  id: string;
  documentId: string;
  version: number;
  path: string;
  size: number;
  uploadedAt: Date;
  metadata?: Record<string, any>;
}

export class SupabaseStorageManager {
  private client: SupabaseClient;
  private bucket: string;

  constructor(config: SupabaseStorageConfig) {
    this.client = createClient(config.url, config.anonKey);
    this.bucket = config.bucket;
  }

  async initializeBucket(): Promise<void> {
    try {
      // Check if bucket exists
      const { data: buckets, error } = await this.client.storage.listBuckets();

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

      const bucketExists = buckets?.some(
        (bucket) => bucket.name === this.bucket
      );

      if (!bucketExists) {
        // Create bucket if it doesn't exist
        const { error: createError } = await this.client.storage.createBucket(
          this.bucket,
          {
            public: false, // Private bucket for security
            allowedMimeTypes: [
              "application/pdf",
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
              "application/msword",
              "text/plain",
              "text/markdown",
            ],
            fileSizeLimit: 50 * 1024 * 1024, // 50MB limit
          }
        );

        if (createError) {
          throw new Error(`Failed to create bucket: ${createError.message}`);
        }
      }
    } catch (error) {
      console.warn("Failed to initialize bucket:", error);
      // Continue execution - bucket might already exist
    }
  }

  async uploadDocument(
    file: Buffer | File,
    fileName: string,
    metadata?: Record<string, any>
  ): Promise<DocumentUploadResult> {
    try {
      const timestamp = new Date().toISOString();
      const sanitizedFileName = this.sanitizeFileName(fileName);
      const path = `documents/${timestamp}_${sanitizedFileName}`;

      const uploadData = file instanceof File ? file : file;

      const { data, error } = await this.client.storage
        .from(this.bucket)
        .upload(path, uploadData, {
          cacheControl: "3600",
          upsert: false,
          metadata: {
            ...metadata,
            originalName: fileName,
            uploadedAt: timestamp,
          },
        });

      if (error) {
        throw new Error(`Upload failed: ${error.message}`);
      }

      const { data: urlData } = this.client.storage
        .from(this.bucket)
        .getPublicUrl(data.path);

      const fileSize = file instanceof File ? file.size : file.length;
      const mimeType = this.getMimeType(fileName);

      return {
        id: this.generateDocumentId(data.path),
        path: data.path,
        url: urlData.publicUrl,
        size: fileSize,
        mimeType,
        uploadedAt: new Date(timestamp),
      };
    } catch (error) {
      throw new Error(`Failed to upload document: ${error}`);
    }
  }

  async downloadDocument(path: string): Promise<Buffer> {
    try {
      const { data, error } = await this.client.storage
        .from(this.bucket)
        .download(path);

      if (error) {
        throw new Error(`Download failed: ${error.message}`);
      }

      if (!data) {
        throw new Error("No data received from download");
      }

      return Buffer.from(await data.arrayBuffer());
    } catch (error) {
      throw new Error(`Failed to download document: ${error}`);
    }
  }

  async deleteDocument(path: string): Promise<void> {
    try {
      const { error } = await this.client.storage
        .from(this.bucket)
        .remove([path]);

      if (error) {
        throw new Error(`Delete failed: ${error.message}`);
      }
    } catch (error) {
      throw new Error(`Failed to delete document: ${error}`);
    }
  }

  async listDocuments(prefix?: string): Promise<
    Array<{
      name: string;
      id: string;
      updated_at: string;
      size: number;
      metadata: Record<string, any>;
    }>
  > {
    try {
      const { data, error } = await this.client.storage
        .from(this.bucket)
        .list(prefix || "documents");

      if (error) {
        throw new Error(`List failed: ${error.message}`);
      }

      return data.map((file) => ({
        name: file.name,
        id: file.id || this.generateDocumentId(file.name),
        updated_at: file.updated_at || "",
        size: file.metadata?.size || 0,
        metadata: file.metadata || {},
      }));
    } catch (error) {
      throw new Error(`Failed to list documents: ${error}`);
    }
  }

  async createDocumentVersion(
    originalPath: string,
    newFile: Buffer | File,
    version: number,
    metadata?: Record<string, any>
  ): Promise<DocumentVersion> {
    try {
      const pathParts = originalPath.split("/");
      const originalFileName = pathParts[pathParts.length - 1];
      const baseFileName = originalFileName.replace(
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z_/,
        ""
      );

      const timestamp = new Date().toISOString();
      const versionPath = `documents/versions/${timestamp}_v${version}_${baseFileName}`;

      const uploadData = newFile instanceof File ? newFile : newFile;

      const { data, error } = await this.client.storage
        .from(this.bucket)
        .upload(versionPath, uploadData, {
          cacheControl: "3600",
          upsert: false,
          metadata: {
            ...metadata,
            originalPath,
            version,
            createdAt: timestamp,
          },
        });

      if (error) {
        throw new Error(`Version upload failed: ${error.message}`);
      }

      const fileSize = newFile instanceof File ? newFile.size : newFile.length;

      return {
        id: this.generateDocumentId(data.path),
        documentId: this.generateDocumentId(originalPath),
        version,
        path: data.path,
        size: fileSize,
        uploadedAt: new Date(timestamp),
        metadata,
      };
    } catch (error) {
      throw new Error(`Failed to create document version: ${error}`);
    }
  }

  async getDocumentVersions(documentId: string): Promise<DocumentVersion[]> {
    try {
      const { data, error } = await this.client.storage
        .from(this.bucket)
        .list("documents/versions");

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

      // Filter versions for the specific document
      const versions = data
        .filter(
          (file) =>
            file.metadata?.originalPath &&
            this.generateDocumentId(file.metadata.originalPath) === documentId
        )
        .map((file) => ({
          id: this.generateDocumentId(file.name),
          documentId,
          version: file.metadata?.version || 1,
          path: `documents/versions/${file.name}`,
          size: file.metadata?.size || 0,
          uploadedAt: new Date(file.metadata?.createdAt || file.updated_at),
          metadata: file.metadata,
        }))
        .sort((a, b) => b.version - a.version);

      return versions;
    } catch (error) {
      throw new Error(`Failed to get document versions: ${error}`);
    }
  }

  private sanitizeFileName(fileName: string): string {
    return fileName
      .replace(/[^a-zA-Z0-9.-]/g, "_")
      .replace(/_{2,}/g, "_")
      .toLowerCase();
  }

  private generateDocumentId(path: string): string {
    return Buffer.from(path)
      .toString("base64")
      .replace(/[^a-zA-Z0-9]/g, "");
  }

  private getMimeType(fileName: string): string {
    const extension = fileName.split(".").pop()?.toLowerCase();

    const mimeTypes: Record<string, string> = {
      pdf: "application/pdf",
      docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      doc: "application/msword",
      txt: "text/plain",
      md: "text/markdown",
    };

    return mimeTypes[extension || ""] || "application/octet-stream";
  }

  async getStorageStats(): Promise<{
    totalFiles: number;
    totalSize: number;
    bucketName: string;
  }> {
    try {
      const files = await this.listDocuments();
      const totalSize = files.reduce((sum, file) => sum + file.size, 0);

      return {
        totalFiles: files.length,
        totalSize,
        bucketName: this.bucket,
      };
    } catch (error) {
      throw new Error(`Failed to get storage stats: ${error}`);
    }
  }
}
