import { tap, expect } from '@push.rocks/tapbundle';
import { XInvoice } from '../ts/classes.xinvoice.js';
import { InvoiceFormat } from '../ts/interfaces/common.js';
import * as fs from 'fs/promises';
import * as path from 'path';

// Test a focused subset of corpus files
tap.test('XInvoice should handle a focused subset of corpus files', async () => {
  // Get a small subset of files for focused testing
  const ciiFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII'), '.xml', 5);
  const ublFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL'), '.xml', 5);
  const zugferdV2Files = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/correct/intarsys/EN16931'), '.pdf', 5);

  // Log the number of files found
  console.log(`Found ${ciiFiles.length} CII files for focused testing`);
  console.log(`Found ${ublFiles.length} UBL files for focused testing`);
  console.log(`Found ${zugferdV2Files.length} ZUGFeRD v2 files for focused testing`);

  // Test CII files
  console.log('\nTesting CII files:');
  for (const file of ciiFiles) {
    console.log(`\nTesting file: ${path.basename(file)}`);
    await testXmlFile(file, InvoiceFormat.CII);
  }

  // Test UBL files
  console.log('\nTesting UBL files:');
  for (const file of ublFiles) {
    console.log(`\nTesting file: ${path.basename(file)}`);
    await testXmlFile(file, InvoiceFormat.UBL);
  }

  // Test ZUGFeRD v2 files
  console.log('\nTesting ZUGFeRD v2 files:');
  for (const file of zugferdV2Files) {
    console.log(`\nTesting file: ${path.basename(file)}`);
    await testPdfFile(file);
  }

  // Create a test directory for output
  const testDir = path.join(process.cwd(), 'test', 'output', 'focused');
  await fs.mkdir(testDir, { recursive: true });

  // Success - we're just testing individual files
  expect(true).toBeTrue();
});

/**
 * Tests an XML file
 * @param file File to test
 * @param expectedFormat Expected format
 */
async function testXmlFile(file: string, expectedFormat: InvoiceFormat): Promise<void> {
  try {
    // Read the file
    const xmlContent = await fs.readFile(file, 'utf8');

    // Create XInvoice from XML
    const xinvoice = await XInvoice.fromXml(xmlContent);

    // Check that the XInvoice instance has the expected properties
    if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) {
      // Check that the format is detected correctly
      const format = xinvoice.getFormat();
      const isCorrectFormat = format === expectedFormat ||
                             (expectedFormat === InvoiceFormat.CII && format === InvoiceFormat.FACTURX) ||
                             (expectedFormat === InvoiceFormat.FACTURX && format === InvoiceFormat.CII) ||
                             (expectedFormat === InvoiceFormat.UBL && format === InvoiceFormat.XRECHNUNG) ||
                             (expectedFormat === InvoiceFormat.XRECHNUNG && format === InvoiceFormat.UBL);

      if (isCorrectFormat) {
        // Try to export the invoice back to XML
        try {
          let exportFormat = 'facturx';
          if (format === InvoiceFormat.UBL || format === InvoiceFormat.XRECHNUNG) {
            exportFormat = 'xrechnung';
          }

          const exportedXml = await xinvoice.exportXml(exportFormat as any);

          if (exportedXml) {
            console.log('✅ Success: File loaded, format detected correctly, and exported successfully');
            console.log(`Format: ${format}`);
            console.log(`From: ${xinvoice.from.name}`);
            console.log(`To: ${xinvoice.to.name}`);
            console.log(`Items: ${xinvoice.items.length}`);

            // Save the exported XML for inspection
            const testDir = path.join(process.cwd(), 'test', 'output', 'focused');
            await fs.mkdir(testDir, { recursive: true });
            await fs.writeFile(path.join(testDir, `${path.basename(file)}-exported.xml`), exportedXml);
          } else {
            console.log('❌ Failed to export valid XML');
          }
        } catch (exportError) {
          console.log(`❌ Export error: ${exportError.message}`);
        }
      } else {
        console.log(`❌ Wrong format detected: ${format}, expected: ${expectedFormat}`);
      }
    } else {
      console.log('❌ Missing required properties');
    }
  } catch (error) {
    console.log(`❌ Error processing the file: ${error.message}`);
  }
}

/**
 * Tests a PDF file
 * @param file File to test
 */
async function testPdfFile(file: string): Promise<void> {
  try {
    // Read the file
    const pdfBuffer = await fs.readFile(file);

    // Extract XML from PDF
    const { PDFExtractor } = await import('../ts/formats/pdf/pdf.extractor.js');
    const extractor = new PDFExtractor();
    const xmlContent = await extractor.extractXml(pdfBuffer);

    // Save the raw XML content for inspection, even if it's invalid
    const testDir = path.join(process.cwd(), 'test', 'output', 'focused');
    await fs.mkdir(testDir, { recursive: true });

    // Try to get the raw XML content directly from the PDF
    try {
      const pdfDoc = await import('pdf-lib').then(lib => lib.PDFDocument.load(pdfBuffer));
      const namesDictObj = pdfDoc.catalog.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('Names')));

      if (namesDictObj) {
        const embeddedFilesDictObj = namesDictObj.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('EmbeddedFiles')));

        if (embeddedFilesDictObj) {
          const filesSpecObj = embeddedFilesDictObj.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('Names')));

          if (filesSpecObj && filesSpecObj.size && filesSpecObj.size() > 0) {
            for (let i = 0; i < filesSpecObj.size(); i += 2) {
              const fileNameObj = filesSpecObj.lookup(i);
              const fileSpecObj = filesSpecObj.lookup(i + 1);

              if (fileNameObj && fileSpecObj) {
                const fileName = fileNameObj.toString();
                console.log(`Found embedded file: ${fileName}`);

                const efDictObj = fileSpecObj.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('EF')));

                if (efDictObj) {
                  const maybeStream = efDictObj.lookup(await import('pdf-lib').then(lib => lib.PDFName.of('F')));

                  if (maybeStream) {
                    try {
                      const xmlBytes = maybeStream.getContents();
                      const rawXmlContent = new TextDecoder('utf-8').decode(xmlBytes);

                      await fs.writeFile(path.join(testDir, `${path.basename(file)}-raw-${fileName}.xml`), rawXmlContent);
                      console.log(`Saved raw XML content from ${fileName}`);
                    } catch (streamError) {
                      console.log(`Error extracting stream content: ${streamError.message}`);
                    }
                  }
                }
              }
            }
          }
        }
      }
    } catch (pdfError) {
      console.log(`Error inspecting PDF structure: ${pdfError.message}`);
    }

    if (xmlContent) {
      console.log('✅ Successfully extracted XML from PDF');

      // Save the extracted XML for inspection
      await fs.writeFile(path.join(testDir, `${path.basename(file)}-extracted.xml`), xmlContent);

      // Try to create XInvoice from the extracted XML
      try {
        const xinvoice = await XInvoice.fromXml(xmlContent);

        if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) {
          console.log('✅ Successfully created XInvoice from extracted XML');
          console.log(`Format: ${xinvoice.getFormat()}`);
          console.log(`From: ${xinvoice.from.name}`);
          console.log(`To: ${xinvoice.to.name}`);
          console.log(`Items: ${xinvoice.items.length}`);

          // Try to export the invoice back to XML
          try {
            const exportedXml = await xinvoice.exportXml('facturx');

            if (exportedXml) {
              console.log('✅ Successfully exported XInvoice back to XML');

              // Save the exported XML for inspection
              await fs.writeFile(path.join(testDir, `${path.basename(file)}-reexported.xml`), exportedXml);
            } else {
              console.log('❌ Failed to export valid XML');
            }
          } catch (exportError) {
            console.log(`❌ Export error: ${exportError.message}`);
          }
        } else {
          console.log('❌ Missing required properties in created XInvoice');
        }
      } catch (xmlError) {
        console.log(`❌ Error creating XInvoice from extracted XML: ${xmlError.message}`);
      }
    } else {
      console.log('❌ No XML found in PDF');
    }

    // Try to create XInvoice directly from PDF
    try {
      const xinvoice = await XInvoice.fromPdf(pdfBuffer);

      if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) {
        console.log('✅ Successfully created XInvoice directly from PDF');
        console.log(`Format: ${xinvoice.getFormat()}`);
        console.log(`From: ${xinvoice.from.name}`);
        console.log(`To: ${xinvoice.to.name}`);
        console.log(`Items: ${xinvoice.items.length}`);
      } else {
        console.log('❌ Missing required properties in created XInvoice');
      }
    } catch (pdfError) {
      console.log(`❌ Error creating XInvoice directly from PDF: ${pdfError.message}`);
    }
  } catch (error) {
    console.log(`❌ Error processing the file: ${error.message}`);
  }
}

/**
 * Recursively finds files with a specific extension in a directory
 * @param dir Directory to search
 * @param extension File extension to look for
 * @param limit Maximum number of files to return
 * @returns Array of file paths
 */
async function findFiles(dir: string, extension: string, limit?: number): Promise<string[]> {
  try {
    const files = await fs.readdir(dir, { withFileTypes: true });

    const result: string[] = [];

    for (const file of files) {
      if (limit && result.length >= limit) {
        break;
      }

      const filePath = path.join(dir, file.name);

      if (file.isDirectory()) {
        // Recursively search subdirectories
        const remainingLimit = limit ? limit - result.length : undefined;
        const subDirFiles = await findFiles(filePath, extension, remainingLimit);
        result.push(...subDirFiles);

        if (limit && result.length >= limit) {
          break;
        }
      } else if (file.name.toLowerCase().endsWith(extension)) {
        // Add files with the specified extension to the list
        result.push(filePath);
      }
    }

    return result;
  } catch (error) {
    console.error(`Error finding files in ${dir}:`, error);
    return [];
  }
}

// Run the tests
tap.start();
