import { PDFDocument, AFRelationship } from '../../plugins.js';
import type { IPdf } from '../../interfaces/common.js';

/**
 * Error types for PDF embedding operations
 */
export enum PDFEmbedError {
  LOAD_ERROR = 'PDF loading failed',
  EMBED_ERROR = 'XML embedding failed',
  SAVE_ERROR = 'PDF saving failed',
  INVALID_INPUT = 'Invalid input parameters'
}

/**
 * Result of a PDF embedding operation
 */
export interface PDFEmbedResult {
  success: boolean;
  data?: Uint8Array;
  pdf?: IPdf;
  error?: {
    type: PDFEmbedError;
    message: string;
    originalError?: Error;
  };
}

/**
 * Class for embedding XML into PDF files
 * Provides robust error handling and support for different PDF formats
 */
export class PDFEmbedder {
  /**
   * Embeds XML into a PDF
   * @param pdfBuffer PDF buffer
   * @param xmlContent XML content to embed
   * @param filename Filename for the embedded XML
   * @param description Description for the embedded XML
   * @returns Result with either modified PDF buffer or error information
   */
  public async embedXml(
    pdfBuffer: Uint8Array | Buffer,
    xmlContent: string,
    filename: string = 'invoice.xml',
    description: string = 'XML Invoice'
  ): Promise<PDFEmbedResult> {
    try {
      // Validate inputs
      if (!pdfBuffer || pdfBuffer.length === 0) {
        return this.createErrorResult(PDFEmbedError.INVALID_INPUT, 'PDF buffer is empty or undefined');
      }
      
      if (!xmlContent) {
        return this.createErrorResult(PDFEmbedError.INVALID_INPUT, 'XML content is empty or undefined');
      }

      // Ensure buffer is Uint8Array
      const pdfBufferArray = Buffer.isBuffer(pdfBuffer) ? new Uint8Array(pdfBuffer) : pdfBuffer;

      // Load the PDF
      let pdfDoc: PDFDocument;
      try {
        pdfDoc = await PDFDocument.load(pdfBufferArray, {
          ignoreEncryption: true,  // Try to load encrypted PDFs
          updateMetadata: false    // Don't automatically update metadata
        });
      } catch (error) {
        return this.createErrorResult(
          PDFEmbedError.LOAD_ERROR,
          `Failed to load PDF: ${error instanceof Error ? error.message : String(error)}`,
          error instanceof Error ? error : undefined
        );
      }

      // Normalize filename (lowercase with XML extension)
      filename = this.normalizeFilename(filename);

      // Convert the XML string to a Uint8Array
      const xmlBuffer = new TextEncoder().encode(xmlContent);

      try {
        // Use pdf-lib's .attach() to embed the XML
        pdfDoc.attach(xmlBuffer, filename, {
          mimeType: 'text/xml',
          description: description,
          creationDate: new Date(),
          modificationDate: new Date(),
          afRelationship: AFRelationship.Alternative,
        });
      } catch (error) {
        return this.createErrorResult(
          PDFEmbedError.EMBED_ERROR,
          `Failed to embed XML: ${error instanceof Error ? error.message : String(error)}`,
          error instanceof Error ? error : undefined
        );
      }

      // Save the modified PDF
      let modifiedPdfBytes: Uint8Array;
      try {
        modifiedPdfBytes = await pdfDoc.save({
          addDefaultPage: false,           // Don't add a page if the document is empty
          useObjectStreams: false,        // Better compatibility with older PDF readers
          updateFieldAppearances: false   // Don't update form fields
        });
      } catch (error) {
        return this.createErrorResult(
          PDFEmbedError.SAVE_ERROR,
          `Failed to save modified PDF: ${error instanceof Error ? error.message : String(error)}`,
          error instanceof Error ? error : undefined
        );
      }

      return {
        success: true,
        data: modifiedPdfBytes
      };
    } catch (error) {
      // Catch any uncaught errors
      return this.createErrorResult(
        PDFEmbedError.EMBED_ERROR,
        `Unexpected error during XML embedding: ${error instanceof Error ? error.message : String(error)}`,
        error instanceof Error ? error : undefined
      );
    }
  }

  /**
   * Creates an IPdf object with embedded XML
   * @param pdfBuffer PDF buffer
   * @param xmlContent XML content to embed
   * @param filename Filename for the embedded XML
   * @param description Description for the embedded XML
   * @param pdfName Name for the PDF
   * @param pdfId ID for the PDF
   * @returns Result with either IPdf object or error information
   */
  public async createPdfWithXml(
    pdfBuffer: Uint8Array | Buffer,
    xmlContent: string,
    filename: string = 'invoice.xml',
    description: string = 'XML Invoice',
    pdfName: string = 'invoice.pdf',
    pdfId: string = `invoice-${Date.now()}`
  ): Promise<PDFEmbedResult> {
    // Embed XML into PDF
    const embedResult = await this.embedXml(pdfBuffer, xmlContent, filename, description);
    
    // If embedding failed, return the error
    if (!embedResult.success || !embedResult.data) {
      return embedResult;
    }

    // Create IPdf object
    const pdfObject: IPdf = {
      name: pdfName,
      id: pdfId,
      metadata: {
        textExtraction: '',
        format: this.detectPdfFormat(xmlContent),
        embeddedXml: {
          filename: filename,
          description: description
        }
      },
      buffer: embedResult.data
    };

    return {
      success: true,
      pdf: pdfObject
    };
  }

  /**
   * Ensures the filename is normalized according to PDF/A requirements
   * @param filename Filename to normalize
   * @returns Normalized filename
   */
  private normalizeFilename(filename: string): string {
    // Convert to lowercase
    let normalized = filename.toLowerCase();
    
    // Ensure it has .xml extension
    if (!normalized.endsWith('.xml')) {
      normalized = normalized.replace(/\.[^/.]+$/, '') + '.xml';
    }
    
    // Replace invalid characters
    normalized = normalized.replace(/[^a-z0-9_.-]/g, '_');
    
    return normalized;
  }

  /**
   * Tries to detect the format of the XML content
   * @param xmlContent XML content
   * @returns Format string or undefined
   */
  private detectPdfFormat(xmlContent: string): string | undefined {
    if (xmlContent.includes('factur-x.eu') || xmlContent.includes('factur-x.xml')) {
      return 'factur-x';
    } else if (xmlContent.includes('zugferd') || xmlContent.includes('ZUGFeRD')) {
      return 'zugferd';
    } else if (xmlContent.includes('xrechnung')) {
      return 'xrechnung';
    } else if (xmlContent.includes('<Invoice') || xmlContent.includes('<CreditNote')) {
      return 'ubl';
    } else if (xmlContent.includes('FatturaElettronica')) {
      return 'fatturapa';
    }
    
    return undefined;
  }

  /**
   * Creates an error result object
   * @param type Error type
   * @param message Error message
   * @param originalError Original error object
   * @returns Error result
   */
  private createErrorResult(
    type: PDFEmbedError,
    message: string,
    originalError?: Error
  ): PDFEmbedResult {
    console.error(`PDF Embedder Error (${type}): ${message}`);
    if (originalError) {
      console.error(originalError);
    }
    
    return {
      success: false,
      error: {
        type,
        message,
        originalError
      }
    };
  }
}