/**
 * File manager for handling file uploads, storage, and presigned URL generation.
 * Supports direct file uploads with automatic text extraction and storage management.
 */
/** Allowed TTL values for S3 object expiration (days). Only these values are applied; any other value is ignored. */
export type TtlDays = 1 | 7 | 30 | 90;

const ALLOWED_TTL_DAYS: TtlDays[] = [1, 7, 30, 90];

export class CaptivateChatFileManager {
  private static readonly FILE_TO_TEXT_API_URL = 'https://file-to-text.prod.captivat.io/api/file-to-text';
  private static readonly PRESIGNED_URL_API_URL = 'https://file-to-text.prod.captivat.io/api/presigned-url';

  public readonly type: 'files' = 'files';
  public readonly files: Array<{
    filename: string;
    type: string;
    file?: File | Blob; // For direct file uploads
    url?: string; // For referencing files when storage is false
    storage?: {
      fileKey: string;
      presignedUrl: string;
      expiresIn: number;
      fileSize: number;
      processingTime: number;
    }; // Storage information when storage is true
    textContent: {
      type: 'file_content';
      text: string;
      metadata: {
        source: 'file_attachment';
        originalFileName: string;
        storageType?: 'direct';
      };
    };
  }>;

  constructor(
    file: File | Blob,
    textContent: {
      type: 'file_content';
      text: string;
      metadata: {
        source: 'file_attachment';
        originalFileName: string;
        storageType?: 'direct';
      };
    },
    type: string
  ) {
    // Set up files array for direct file upload
    this.files = [{
      filename: textContent.metadata.originalFileName,
      type: type,
      file: file,
      textContent: textContent
    }];
  }

  /**
   * Factory method to create a CaptivateChatFileManager with automatic file-to-text conversion for direct file uploads.
   * @param options - Configuration options for the file input.
   * @param options.file - The file to convert.
   * @param options.fileName - Optional custom file name.
   * @param options.fileType - Optional custom file type.
   * @param options.storage - Whether to store the file for future reference (default: true).
   * @param options.url - URL to reference the file when storage is false (required when storage is false).
   * @param options.apiKey - Optional API key for constructing the path parameter.
   * @param options.conversationId - Optional conversation ID for constructing the path parameter.
   * @param options.ttlDays - Optional TTL for the stored S3 object: 1, 7, 30, or 90 days. Omit for no expiration.
   * @returns A promise that resolves to a CaptivateChatFileManager instance with converted text.
   */
  static async create(
    options: {
      file: File | Blob;
      fileName?: string;
      fileType?: string;
      storage?: boolean;
      url?: string;
      apiKey?: string;
      conversationId?: string;
      ttlDays?: TtlDays;
    }
  ): Promise<CaptivateChatFileManager> {
    // Direct file upload
    const file = options.file;
    const storage = options.storage !== undefined ? options.storage : true; // Default to true
    
    // If storage is false, URL is required for sendMessage compatibility
    if (storage === false && !options.url) {
      throw new Error('URL is required when storage is false for sendMessage compatibility');
    }
    
    const finalFileName = options.fileName || (file instanceof File ? file.name : `attachment_${Date.now()}`);
    const finalType = options.fileType || (file instanceof File ? file.type : file.type || 'application/octet-stream');

    // Convert file to text
    const conversionResult = await CaptivateChatFileManager.convertFileToText(
      file,
      finalFileName,
      true, // Always include metadata
      storage,
      options.apiKey,
      options.conversationId,
      options.ttlDays
    );

    // Create and return the instance with proxy for array-like behavior
    const instance = new CaptivateChatFileManager(file, {
      type: 'file_content',
      text: conversionResult.text,
      metadata: {
        source: 'file_attachment',
        originalFileName: finalFileName,
        storageType: 'direct'
      }
    }, finalType);
    
    // Add storage information if available
    if (storage && conversionResult.storageInfo) {
      instance.files[0].storage = conversionResult.storageInfo;
    }
    
    // If storage is false and URL is provided, add URL to the file object
    if (storage === false && options.url) {
      instance.files[0].url = options.url;
    }
    
    return CaptivateChatFileManager.createProxy(instance);
  }

  /**
   * Gets the first file from the files array for convenience.
   * @returns The first file object, or undefined if no files exist.
   */
  getFirstFile() {
    return this.files[0];
  }

  /**
   * Gets the filename of the first file.
   * @returns The filename, or undefined if no files exist.
   */
  getFilename() {
    return this.files[0]?.filename;
  }

  /**
   * Gets the text content of the first file.
   * @returns The text content, or empty string if no files exist.
   */
  getTextContent() {
    return this.files[0]?.textContent?.text || '';
  }

  /**
   * Gets the file type of the first file.
   * @returns The file type, or undefined if no files exist.
   */
  getFileType() {
    return this.files[0]?.type;
  }

  /**
   * Returns the files array directly for compatibility with the files property.
   * This allows using fileInputObj directly as the files value.
   * @returns The files array.
   */
  toFilesArray() {
    return this.files;
  }

  /**
   * Makes the class iterable so it can be used directly as an array.
   */
  *[Symbol.iterator]() {
    yield* this.files;
  }

  /**
   * Getter that returns the files array, allowing the class to be used directly as files.
   * This enables: files: fileInputObj instead of files: fileInputObj.files
   */
  get [Symbol.toStringTag]() {
    return 'Array';
  }

  /**
   * Returns the length of the files array for array-like behavior.
   */
  get length() {
    return this.files.length;
  }

  /**
   * Refreshes the secure URL for the first file (if it has storage information).
   * @param expiresIn - Expiration time in seconds (default: 7200 = 2 hours).
   * @returns A promise that resolves to the refreshed secure URL, or undefined if no storage info.
   */
  async refreshSecureUrl(expiresIn: number = 7200): Promise<string | undefined> {
    const firstFile = this.files[0];
    if (!firstFile || !(firstFile as any).storage?.fileKey) {
      return undefined;
    }
    
    return await CaptivateChatFileManager.getSecureFileUrl(
      (firstFile as any).storage.fileKey, 
      expiresIn
    );
  }

  /**
   * Proxy handler to make the class behave like the files array.
   * This allows using fileInputObj directly as files: fileInputObj
   */
  static createProxy(instance: CaptivateChatFileManager) {
    return new Proxy(instance, {
      get(target, prop) {
        // If accessing the files array directly, return the files
        if (prop === Symbol.iterator || prop === 'length' || typeof prop === 'number') {
          return target.files[prop as any];
        }
        // Otherwise, return the property from the instance
        return (target as any)[prop];
      }
    });
  }

  /**
   * Factory method to create a single file object with automatic file-to-text conversion.
   * Returns just the file object instead of the full CaptivateChatFileManager wrapper.
   * @param options - Configuration options for the file input.
   * @param options.file - The file to convert.
   * @param options.fileName - Optional custom file name.
   * @param options.fileType - Optional custom file type.
   * @param options.storage - Whether to store the file for future reference (default: true).
   * @param options.url - URL to reference the file when storage is false (required when storage is false).
   * @param options.ttlDays - Optional TTL for the stored S3 object: 1, 7, 30, or 90 days. Omit for no expiration.
   * @returns A promise that resolves to a single file object.
   */
  static async createFile(
    options: {
      file: File | Blob;
      fileName?: string;
      fileType?: string;
      storage?: boolean;
      url?: string;
      apiKey?: string;
      conversationId?: string;
      ttlDays?: TtlDays;
    }
  ): Promise<{
    filename: string;
    type: string;
    file?: File | Blob;
    textContent: {
      type: 'file_content';
      text: string;
      metadata: {
        source: 'file_attachment';
        originalFileName: string;
        storageType?: 'direct';
      };
    };
  }> {
    const fileInput = await CaptivateChatFileManager.create({
      file: options.file,
      fileName: options.fileName,
      fileType: options.fileType,
      storage: options.storage,
      url: options.url,
      apiKey: options.apiKey,
      conversationId: options.conversationId,
      ttlDays: options.ttlDays
    });
    return fileInput.getFirstFile()!;
  }

  /**
   * Factory method to create multiple file inputs from an array of files.
   * Processes all files in parallel and returns a single CaptivateChatFileManager with all files.
   * @param options - Configuration options for all files.
   * @param options.files - Array of files to process.
   * @param options.storage - Whether to store the files for future reference (default: true).
   * @param options.urls - Array of URLs to reference files when storage is false (required when storage is false).
   * @param options.ttlDays - Optional TTL for stored S3 objects: 1, 7, 30, or 90 days. Omit for no expiration.
   * @returns A promise that resolves to a CaptivateChatFileManager instance with all processed files.
   */
  static async createMultiple(
    options: {
      files: (File | Blob)[];
      storage?: boolean;
      urls?: string[];
      apiKey?: string;
      conversationId?: string;
      ttlDays?: TtlDays;
    }
  ): Promise<CaptivateChatFileManager> {
    const storage = options.storage !== undefined ? options.storage : true; // Default to true
    
    // If storage is false, URLs are required for sendMessage compatibility
    if (storage === false && (!options.urls || options.urls.length !== options.files.length)) {
      throw new Error('URLs array is required when storage is false and must match the number of files');
    }
    
    // Process multiple files
    const files = options.files;
    const fileInputs = await Promise.all(
      files.map((file, index) => CaptivateChatFileManager.create({
        file: file,
        storage: storage,
        url: storage === false ? options.urls![index] : undefined,
        apiKey: options.apiKey,
        conversationId: options.conversationId,
        ttlDays: options.ttlDays
      }))
    );

    // Combine all files into a single array
    const allFiles = fileInputs.flatMap(input => input.files);

    // Create a new instance with all files
    const combinedInstance = new CaptivateChatFileManager(
      files[0], // Use first file as placeholder
      {
        type: 'file_content',
        text: '', // Will be ignored since we're using the files array
        metadata: {
          source: 'file_attachment',
          originalFileName: 'multiple_files',
          storageType: 'direct'
        }
      },
      'application/octet-stream'
    );

    // Replace the files array with the combined files
    (combinedInstance as any).files = allFiles;

    return CaptivateChatFileManager.createProxy(combinedInstance);
  }

  /**
   * Generates a secure URL for accessing a stored file.
   * @param fileKey - The file key from the storage response.
   * @param expiresIn - Expiration time in seconds (default: 7200 = 2 hours).
   * @returns A promise that resolves to the secure URL.
   */
  static async getSecureFileUrl(fileKey: string, expiresIn: number = 7200): Promise<string> {
    const url = CaptivateChatFileManager.PRESIGNED_URL_API_URL;

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          fileKey: fileKey,
          expiresIn: expiresIn
        })
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(`Secure URL generation failed: ${response.status} ${response.statusText}. ${errorData.error || ''}`);
      }

      const data = await response.json();
      
      if (!data.success) {
        throw new Error(`Secure URL generation failed: ${data.error || 'Unknown error'}`);
      }

      return data.presignedUrl || '';

    } catch (error: any) {
      if (error.message.includes('Secure URL generation failed')) {
        throw error;
      }
      throw new Error(`Failed to generate secure URL: ${error.message}`);
    }
  }

  /**
   * Converts a file to text using the file-to-text API endpoint.
   * @param file - The file to convert (File or Blob).
   * @param fileName - The name of the file.
   * @param includeMetadata - Whether to include additional metadata in the response.
   * @param storage - Whether to store the file for future reference.
   * @param apiKey - Optional API key for constructing the path parameter.
   * @param conversationId - Optional conversation ID for constructing the path parameter.
   * @param ttlDays - Optional TTL for the stored S3 object: 1, 7, 30, or 90 days. Only these values are sent; any other is ignored.
   * @returns A promise that resolves to the extracted text.
   */
  private static async convertFileToText(file: File | Blob, fileName: string, includeMetadata: boolean, storage: boolean, apiKey?: string, conversationId?: string, ttlDays?: TtlDays): Promise<{text: string, storageInfo?: any}> {
    const url = CaptivateChatFileManager.FILE_TO_TEXT_API_URL;

    // Create FormData for multipart/form-data request
    const formData = new FormData();
    formData.append('file', file, fileName);
    formData.append('includeMetadata', includeMetadata.toString());
    formData.append('storage', storage.toString());

    // Add path parameter if both apiKey and conversationId are provided
    if (apiKey && conversationId) {
      const path = `${apiKey}/${conversationId}`;
      formData.append('path', path);
    }

    // Add ttlDays only if it's one of the allowed values (1, 7, 30, 90)
    if (ttlDays !== undefined && ALLOWED_TTL_DAYS.includes(ttlDays)) {
      formData.append('ttlDays', String(ttlDays));
    }

    try {
      const response = await fetch(url, {
        method: 'POST',
        body: formData
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(`File conversion failed: ${response.status} ${response.statusText}. ${errorData.error || ''}`);
      }

      const data = await response.json();
      
      if (!data.success) {
        throw new Error(`File conversion failed: ${data.error || 'Unknown error'}`);
      }

      return {
        text: data.text || '',
        storageInfo: storage ? {
          fileKey: data.fileKey,
          presignedUrl: data.presignedUrl,
          expiresIn: data.expiresIn,
          fileSize: data.fileSize,
          processingTime: data.processingTime
        } : undefined
      };

    } catch (error: any) {
      if (error.message.includes('File conversion failed')) {
        throw error;
      }
      throw new Error(`Failed to convert file to text: ${error.message}`);
    }
  }
}