/**
 * Tier 3: Combinatorial Cross-Feature Integration Tests
 * Minimum 15 end-to-end multi-module workflow integration test cases.
 */

import { describe, test, it, expect } from "./runner";
import { ProcessedReceipt, ReceiptData } from "../../src/lib/schema/receipt";
import { validateReceiptMath } from "../../src/lib/ai/mathValidator";
import { generateDeterministicDemoExtraction } from "../../src/lib/ai/extractor";
import { generateDualSheetExcel } from "../../src/lib/export/excelGenerator";
import { generateAccountingCsv } from "../../src/lib/export/csvGenerator";
import { processReceiptImage } from "../../src/lib/image/processor";
import { FREE_SCAN_LIMIT } from "../../src/lib/limits";
import ExcelJS from "exceljs";

describe("Tier 3: Combinatorial Workflows & Cross-Feature Interactions", () => {
  // Workflow 1: MicroPrompt TANKEN_KFZ -> Ingestion -> Math Check -> Excel Generator
  test("Workflow 1: MicroPrompt (TANKEN_KFZ) -> AI extraction -> Math validation -> Dual-Sheet Excel export", async () => {
    // 1. Ingest Aral Tankstelle demo fixture
    const rawExtraction = generateDeterministicDemoExtraction("01_aral_tankbeleg_muenchen.jpg");
    expect(rawExtraction.suggestedCategory).toBe("Tanken & KFZ");

    // 2. Run deterministic math verification
    const mathResult = validateReceiptMath(rawExtraction);
    expect(mathResult.isMathValid).toBe(true);
    expect(mathResult.needsUserReview).toBe(false);

    // 3. Construct processed receipt record
    const receipt: ProcessedReceipt = {
      ...rawExtraction,
      id: "wf-1-aral",
      imageHash: "hash-aral-wf1",
      originalFileName: "01_aral_tankbeleg_muenchen.jpg",
      fileSizeBytes: 184000,
      createdAt: "2026-08-15T08:42:00.000Z",
      updatedAt: "2026-08-15T08:42:00.000Z",
      status: "ready",
    };

    // 4. Generate Dual-Sheet Excel Workbook
    const excelBuffer = await generateDualSheetExcel([receipt]);
    const workbook = new ExcelJS.Workbook();
    await workbook.xlsx.load(excelBuffer as any);

    // 5. Verify Sheet 1 and Sheet 2 structure
    const s1 = workbook.getWorksheet("Belegübersicht");
    const s2 = workbook.getWorksheet("Einzelpositionen Detail");

    expect(s1).toBeDefined();
    expect(s2).toBeDefined();
    expect(s1!.getRow(2).getCell(3).value).toBe("Aral Tankstelle Station");
    expect(s1!.getRow(2).getCell(10).value).toBe(68.45);

    // Verify SUM formula in summary row
    const sumGross = s1!.getRow(3).getCell(10);
    expect((sumGross.value as any)?.formula).toBe("SUM(J2:J2)");
  });

  // Workflow 2: MicroPrompt BEWIRTUNG -> AI Tip Extraction -> Accounting CSV Generation
  test("Workflow 2: MicroPrompt (BEWIRTUNG) -> AI extract with Tip -> Math check -> accounting CSV export", () => {
    // 1. Ingest Trattoria Bella Vista Bewirtungsbeleg fixture
    const rawExtraction = generateDeterministicDemoExtraction("02_trattoria_bewirtungsbeleg_berlin.jpg");
    expect(rawExtraction.documentType).toBe("BEWIRTUNGSBELEG");
    expect(rawExtraction.suggestedCategory).toBe("Bewirtung");

    // 2. Validate line items contain tip item and food/drinks
    const tipItem = rawExtraction.lineItems.find((i) => i.description.includes("Trinkgeld"));
    expect(tipItem).toBeDefined();
    expect(tipItem?.price).toBe(12.5);

    // 3. Check math validation
    const mathResult = validateReceiptMath(rawExtraction);
    expect(mathResult.isMathValid).toBe(true);

    // 4. Generate accounting CSV
    const receipt: ProcessedReceipt = {
      ...rawExtraction,
      id: "wf-2-trattoria",
      imageHash: "hash-trattoria-wf2",
      originalFileName: "02_trattoria.jpg",
      fileSizeBytes: 210000,
      createdAt: "2026-08-15T20:15:00.000Z",
      updatedAt: "2026-08-15T20:15:00.000Z",
      status: "ready",
    };

    const accountingCsv = generateAccountingCsv([receipt]);

    // 5. Verify CSV formatting rules
    expect(accountingCsv.startsWith("\uFEFF")).toBe(true);
    expect(accountingCsv).toContain('"Trattoria Bella Vista - Bewirtung"');
    expect(accountingCsv).toContain('"84,50"');
    expect(accountingCsv).toContain('"70,97"');
    expect(accountingCsv).toContain('"3,08"'); // 7% VAT
    expect(accountingCsv).toContain('"10,45"'); // 19% VAT
  });

  // Workflow 3: MicroPrompt MWST_SPLIT -> Mixed Supermarket Receipt -> Dual-Sheet Excel
  test("Workflow 3: MicroPrompt (MWST_SPLIT) -> Multi-tax receipt -> Math check pass -> Dual-Sheet Excel", async () => {
    // 1. Ingest REWE supermarket receipt fixture
    const rawExtraction = generateDeterministicDemoExtraction("04_rewe_supermarkt_kassenbon.jpg");
    expect(rawExtraction.taxBreakdown).toHaveLength(2); // 7% + 19%

    // 2. Verify math consistency
    const mathResult = validateReceiptMath(rawExtraction);
    expect(mathResult.isMathValid).toBe(true);

    const receipt: ProcessedReceipt = {
      ...rawExtraction,
      id: "wf-3-rewe",
      imageHash: "hash-rewe-wf3",
      originalFileName: "04_rewe.jpg",
      fileSizeBytes: 165000,
      createdAt: "2026-08-15T17:45:00.000Z",
      updatedAt: "2026-08-15T17:45:00.000Z",
      status: "ready",
    };

    // 3. Export to Excel
    const buf = await generateDualSheetExcel([receipt]);
    const wb = new ExcelJS.Workbook();
    await wb.xlsx.load(buf as any);

    const s1 = wb.getWorksheet("Belegübersicht");
    expect(s1?.getRow(2).getCell(8).value).toBe(1.31);
    expect(s1?.getRow(2).getCell(9).value).toBe(0.76);
    expect(s1?.getRow(2).getCell(10).value).toBe(24.8);

    const s2 = wb.getWorksheet("Einzelpositionen Detail");
    expect(s2?.rowCount).toBe(5); // 1 header + 4 items
  });

  // Workflow 4: Multi-File Ingestion Batch -> Category Filtering -> Batch Export
  test("Workflow 4: Multi-file batch -> Preprocessing hashes -> Category filter -> Batch export", async () => {
    const names = [
      "01_aral_tankbeleg_muenchen.jpg",
      "02_trattoria_bewirtungsbeleg_berlin.jpg",
      "04_rewe_supermarkt_kassenbon.jpg",
    ];

    const processedBatch: ProcessedReceipt[] = names.map((name, i) => {
      const ext = generateDeterministicDemoExtraction(name);
      return {
        ...ext,
        id: `wf4-${i}`,
        imageHash: `hash-wf4-${name}`,
        originalFileName: name,
        fileSizeBytes: 100000 + i * 50000,
        createdAt: "2026-08-15T12:00:00.000Z",
        updatedAt: "2026-08-15T12:00:00.000Z",
        status: "ready",
      };
    });

    expect(processedBatch).toHaveLength(3);

    // Filter by 'Tanken & KFZ'
    const tankenOnly = processedBatch.filter((r) => r.suggestedCategory === "Tanken & KFZ");
    expect(tankenOnly).toHaveLength(1);
    expect(tankenOnly[0].merchant.name).toContain("Aral");

    // Export full batch to Excel
    const buf = await generateDualSheetExcel(processedBatch);
    const wb = new ExcelJS.Workbook();
    await wb.xlsx.load(buf as any);

    const s1 = wb.getWorksheet("Belegübersicht");
    expect(s1?.rowCount).toBe(5); // 1 header + 3 data rows + 1 total
  });

  // Workflow 5: Guest Quota Lifecycle (0 -> 14 -> 15 limit reached -> Pro upgrade -> Unlimited)
  test("Workflow 5: Guest scan quota lifecycle (0 -> 15 limit -> Pro switch -> unlimited)", () => {
    let scanCount = 0;
    let isPro = false;

    // Scan 1..14
    for (let i = 0; i < 14; i++) {
      expect(isPro || scanCount < FREE_SCAN_LIMIT).toBe(true);
      scanCount++;
    }

    // Scan 15 (15th free scan allowed)
    expect(isPro || scanCount < FREE_SCAN_LIMIT).toBe(true);
    scanCount++;

    // Scan 16 attempt (blocked for guest)
    expect(isPro || scanCount < FREE_SCAN_LIMIT).toBe(false);

    // User purchases Pro license
    isPro = true;

    // Scan 16 attempt now succeeds
    expect(isPro || scanCount < FREE_SCAN_LIMIT).toBe(true);
    scanCount++;
    expect(scanCount).toBe(16);
  });

  // Workflow 6: Discrepant Receipt -> MicroPrompt Resolution -> Status Update -> Export
  test("Workflow 6: Discrepant receipt -> 1-Click MicroPrompt confirmation -> Status Valid -> Export", async () => {
    // 1. Create a receipt with a discrepancy
    const rawData: ProcessedReceipt = {
      ...generateDeterministicDemoExtraction("01_aral_tankbeleg_muenchen.jpg"),
      id: "wf-6-disc",
      imageHash: "hash-wf6",
      originalFileName: "faded_receipt.jpg",
      fileSizeBytes: 120000,
      createdAt: "2026-08-15T12:00:00.000Z",
      updatedAt: "2026-08-15T12:00:00.000Z",
      netAmount: 40.0, // 40 + 10.93 != 68.45
      validation: {
        isMathValid: false,
        isDuplicateSuspected: false,
        needsUserReview: true,
        reviewField: "taxBreakdown",
        reviewReason: "Netto + MwSt weicht von Brutto ab",
      },
      status: "needs_review",
    };

    expect(rawData.status).toBe("needs_review");
    expect(rawData.validation.needsUserReview).toBe(true);

    // 2. User clicks "Confirm" in MicroPromptBar
    const resolvedReceipt: ProcessedReceipt = {
      ...rawData,
      netAmount: 57.52, // Fixed net
      validation: {
        ...rawData.validation,
        isMathValid: true,
        needsUserReview: false,
        reviewReason: null,
      },
      status: "ready",
    };

    expect(resolvedReceipt.status).toBe("ready");
    expect(resolvedReceipt.validation.isMathValid).toBe(true);

    // 3. Export verified receipt
    const csv = generateAccountingCsv([resolvedReceipt]);
    expect(csv).toContain('"Valide"');
    expect(csv).not.toContain('"Prüfung erforderlich"');
  });

  // Workflow 7: LiveTable Inline Editing -> Math Engine Recalculation -> Export Synchronization
  test("Workflow 7: LiveTable inline edit -> Math validation re-evaluation -> Export sync", () => {
    const initial = generateDeterministicDemoExtraction("01_aral_tankbeleg_muenchen.jpg");

    // Inline edit: user corrects gross amount from 68.45 to 68.50
    const editedData: Partial<ReceiptData> = {
      ...initial,
      totalAmount: { value: 68.5, confidence: 1.0 },
    };

    const revalidation = validateReceiptMath(editedData);
    // 57.52 + 10.93 = 68.45 != 68.50 (diff 0.05 > 0.03) -> flagged
    expect(revalidation.isMathValid).toBe(false);
    expect(revalidation.needsUserReview).toBe(true);

    // User also corrects net amount to 57.57 -> 57.57 + 10.93 = 68.50
    const correctedData: Partial<ReceiptData> = {
      ...editedData,
      netAmount: 57.57,
    };
    const correctedValidation = validateReceiptMath(correctedData);
    expect(correctedValidation.isMathValid).toBe(true);
  });

  // Workflow 8: CMD+K Spotlight Search -> Filter & Modal Inspection Selection
  test("Workflow 8: CMD+K Spotlight search -> Select matching receipt -> Data binding matches", () => {
    const list = [
      createMockReceipt({ id: "1", merchant: { name: "Aral", address: null, taxId: null, confidence: 1 } }),
      createMockReceipt({ id: "2", merchant: { name: "MediaMarkt", address: null, taxId: null, confidence: 1 } }),
      createMockReceipt({ id: "3", merchant: { name: "REWE", address: null, taxId: null, confidence: 1 } }),
    ];

    const searchQuery = "mediamarkt";
    const matched = list.filter((r) => r.merchant.name.toLowerCase().includes(searchQuery));
    expect(matched).toHaveLength(1);
    expect(matched[0].id).toBe("2");
  });

  // Workflow 9: International Currency Receipt -> Pipeline Execution -> CSV & Excel
  test("Workflow 9: International currency receipt (USD) -> Validation -> Excel & CSV generation", async () => {
    const usdReceipt: ProcessedReceipt = {
      id: "wf-9-usd",
      merchant: { name: "Apple Store New York", address: "5th Ave", taxId: null, confidence: 0.99 },
      date: { isoDate: "2026-08-10", time: "11:00", confidence: 0.95 },
      documentType: "RECHNUNG",
      receiptNumber: "INV-US-9912",
      currency: "USD",
      totalAmount: { value: 108.87, confidence: 0.99 },
      netAmount: 100.0,
      taxBreakdown: [{ ratePercent: 8.875, taxAmount: 8.87, netAmount: 100.0 }],
      lineItems: [{ description: "Magic Mouse 3", quantity: 1, price: 108.87, taxRate: 8.875 }],
      suggestedCategory: "Bürobedarf & IT",
      validation: {
        isMathValid: true,
        isDuplicateSuspected: false,
        needsUserReview: false,
        reviewField: "none",
        reviewReason: null,
      },
      imageHash: "hash-usd-wf9",
      originalFileName: "apple_ny.jpg",
      fileSizeBytes: 140000,
      createdAt: "2026-08-10T11:00:00.000Z",
      updatedAt: "2026-08-10T11:00:00.000Z",
      status: "ready",
    };

    const val = validateReceiptMath(usdReceipt);
    expect(val.isMathValid).toBe(true);

    const csv = generateAccountingCsv([usdReceipt]);
    expect(csv).toContain('"Apple Store New York"');
    expect(csv).toContain('"108,87"');

    const buf = await generateDualSheetExcel([usdReceipt]);
    expect(buf.length).toBeGreaterThan(1000);
  });

  // Workflow 10: Multi-Receipt Batch Export with Excel Dynamic SUM Spanning All Rows
  test("Workflow 10: 10-Receipt batch -> Dual-Sheet Excel with total formula =SUM(G2:G11)", async () => {
    const batch = Array.from({ length: 10 }, (_, i) => {
      const ext = generateDeterministicDemoExtraction();
      return {
        ...ext,
        id: `batch-${i}`,
        imageHash: `hash-batch-${i}`,
        originalFileName: `receipt_${i}.jpg`,
        fileSizeBytes: 120000,
        createdAt: "2026-08-15T12:00:00.000Z",
        updatedAt: "2026-08-15T12:00:00.000Z",
        status: "ready" as const,
      };
    });

    const buf = await generateDualSheetExcel(batch);
    const wb = new ExcelJS.Workbook();
    await wb.xlsx.load(buf as any);

    const s1 = wb.getWorksheet("Belegübersicht");
    const summaryRow = s1!.getRow(12); // 1 header + 10 rows + 1 summary
    const netSum = summaryRow.getCell(7);
    const grossSum = summaryRow.getCell(10);

    expect((netSum.value as any)?.formula).toBe("SUM(G2:G11)");
    expect((grossSum.value as any)?.formula).toBe("SUM(J2:J11)");
  });

  // Workflow 11: Image Preprocessing Hash -> Duplicate Ingestion Alert
  test("Workflow 11: Image Preprocessing -> Hash computation -> Duplicate flag detection", async () => {
    // Valid 1x1 PNG (real magic bytes) — identical bytes, identical hashes.
    const png1x1 = Buffer.from(
      "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
      "base64"
    );
    const p1 = await processReceiptImage(png1x1, "image/png");
    const p2 = await processReceiptImage(png1x1, "image/png");

    expect(p1.sha256Hash).toBe(p2.sha256Hash);

    // Simulate existing store check
    const existingHashes = new Set([p1.sha256Hash]);
    const isDuplicate = existingHashes.has(p2.sha256Hash);
    expect(isDuplicate).toBe(true);
  });

  // Workflow 12: Tax-Exempt Invoices (0% VAT) -> Excel & CSV Export
  test("Workflow 12: Tax-exempt invoice -> Math check 0% tax -> Excel & CSV export", async () => {
    const taxExemptReceipt: ProcessedReceipt = {
      id: "wf-12-exempt",
      merchant: { name: "Deutsche Post AG", address: "Bonn", taxId: null, confidence: 0.98 },
      date: { isoDate: "2026-08-14", time: "09:10", confidence: 0.95 },
      documentType: "KASSENBON",
      receiptNumber: "DP-88712",
      currency: "EUR",
      totalAmount: { value: 35.0, confidence: 0.98 },
      netAmount: 35.0,
      taxBreakdown: [{ ratePercent: 0, taxAmount: 0.0, netAmount: 35.0 }],
      lineItems: [{ description: "Briefmarken Set 50x", quantity: 1, price: 35.0, taxRate: 0 }],
      suggestedCategory: "Bürobedarf & IT",
      validation: {
        isMathValid: true,
        isDuplicateSuspected: false,
        needsUserReview: false,
        reviewField: "none",
        reviewReason: null,
      },
      imageHash: "hash-post-wf12",
      originalFileName: "post.jpg",
      fileSizeBytes: 110000,
      createdAt: "2026-08-14T09:10:00.000Z",
      updatedAt: "2026-08-14T09:10:00.000Z",
      status: "ready",
    };

    const val = validateReceiptMath(taxExemptReceipt);
    expect(val.isMathValid).toBe(true);
    expect(val.calculatedTaxSum).toBe(0.0);

    const csv = generateAccountingCsv([taxExemptReceipt]);
    expect(csv).toContain('"35,00"');

    const buf = await generateDualSheetExcel([taxExemptReceipt]);
    expect(buf.length).toBeGreaterThan(1000);
  });

  // Workflow 13: Discount Voucher + Pfand Return Combination
  test("Workflow 13: Discounted receipt with Pfand return -> Line item cross-sum -> Excel detail", async () => {
    const discountReceipt: ProcessedReceipt = {
      id: "wf-13-disc",
      merchant: { name: "EDEKA Center", address: "München", taxId: "DE881122", confidence: 0.96 },
      date: { isoDate: "2026-08-15", time: "16:00", confidence: 0.95 },
      documentType: "KASSENBON",
      receiptNumber: "ED-9912",
      currency: "EUR",
      totalAmount: { value: 27.5, confidence: 0.98 },
      netAmount: 24.3,
      taxBreakdown: [
        { ratePercent: 7, taxAmount: 1.4, netAmount: 20.0 },
        { ratePercent: 19, taxAmount: 1.8, netAmount: 4.3 },
      ],
      lineItems: [
        { description: "Einkauf Lebensmittel", quantity: 1, price: 21.4, taxRate: 7 },
        { description: "Haushaltswaren", quantity: 1, price: 11.1, taxRate: 19 },
        { description: "Treuerabatt Coupon", quantity: 1, price: -5.0, taxRate: 19 },
        { description: "Pfandrückgabe", quantity: 1, price: -0.0, taxRate: 0 },
      ],
      suggestedCategory: "Material & Einkauf",
      validation: {
        isMathValid: true,
        isDuplicateSuspected: false,
        needsUserReview: false,
        reviewField: "none",
        reviewReason: null,
      },
      imageHash: "hash-edeka-wf13",
      originalFileName: "edeka.jpg",
      fileSizeBytes: 140000,
      createdAt: "2026-08-15T16:00:00.000Z",
      updatedAt: "2026-08-15T16:00:00.000Z",
      status: "ready",
    };

    const val = validateReceiptMath(discountReceipt);
    expect(val.calculatedItemsSum).toBe(27.5);
    expect(val.isMathValid).toBe(true);

    const buf = await generateDualSheetExcel([discountReceipt]);
    const wb = new ExcelJS.Workbook();
    await wb.xlsx.load(buf as any);
    const s2 = wb.getWorksheet("Einzelpositionen Detail");
    expect(s2?.rowCount).toBe(5); // 1 header + 4 line items
  });

  // Workflow 14: Category Reassignment -> Category column in CSV
  test("Workflow 14: Category reassignment -> updated category column in CSV", () => {
    const receipt = createMockReceipt({
      suggestedCategory: "Bewirtung",
      merchant: { name: "Restaurant Bella", address: null, taxId: null, confidence: 1 },
    });

    const csvInitial = generateAccountingCsv([receipt]);
    expect(csvInitial).toContain('"Restaurant Bella - Bewirtung"');

    // Reassign category to 'Reisekosten & Hotel'
    const reassigned: ProcessedReceipt = {
      ...receipt,
      suggestedCategory: "Reisekosten & Hotel",
    };
    const csvUpdated = generateAccountingCsv([reassigned]);
    expect(csvUpdated).toContain('"Restaurant Bella - Reisekosten & Hotel"');
  });

  // Workflow 15: Full End-to-End Lifecycle Execution
  test("Workflow 15: Full End-to-End Lifecycle: Ingest -> Preprocess -> Extract -> Validate -> Persist -> Dual Export", async () => {
    // 1. Ingest raw image buffer (valid 1x1 PNG — the whitelist rejects
    //    non-image bytes, so the lifecycle starts from a real image)
    const mockImageBytes = Buffer.from(
      "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
      "base64"
    );
    const preprocessed = await processReceiptImage(mockImageBytes, "image/png");
    expect(preprocessed.sha256Hash).toBeDefined();

    // 2. Multimodal AI Extraction (Deterministic generator)
    const extraction = generateDeterministicDemoExtraction("01_aral_tankbeleg_muenchen.jpg");
    expect(extraction.merchant.name).toContain("Aral");

    // 3. Math Determinism Verification
    const mathValidation = validateReceiptMath(extraction);
    expect(mathValidation.isMathValid).toBe(true);

    // 4. Create full processed receipt record
    const processedRecord: ProcessedReceipt = {
      ...extraction,
      id: `lifecycle_${Date.now()}`,
      imageHash: preprocessed.sha256Hash,
      originalFileName: "aral_tankstelle.jpg",
      fileSizeBytes: preprocessed.processedSizeBytes,
      previewUrl: preprocessed.base64DataUrl,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      status: "ready",
    };

    // 5. Generate Dual-Sheet Excel
    const excelBuffer = await generateDualSheetExcel([processedRecord]);
    expect(excelBuffer.length).toBeGreaterThan(1000);

    // 6. Generate accounting CSV
    const csvContent = generateAccountingCsv([processedRecord]);
    expect(csvContent.startsWith("\uFEFF")).toBe(true);
    expect(csvContent).toContain("Aral Tankstelle Station");
  });
});

function createMockReceipt(overrides: Partial<ProcessedReceipt> = {}): ProcessedReceipt {
  return {
    id: "mock-wf-id",
    merchant: { name: "Mock Vendor", address: null, taxId: null, confidence: 1 },
    date: { isoDate: "2026-08-15", time: null, confidence: 1 },
    documentType: "KASSENBON",
    receiptNumber: "MOCK-1",
    currency: "EUR",
    totalAmount: { value: 10.0, confidence: 1 },
    netAmount: 8.4,
    taxBreakdown: [{ ratePercent: 19, taxAmount: 1.6, netAmount: 8.4 }],
    lineItems: [{ description: "Item", quantity: 1, price: 10.0, taxRate: 19 }],
    suggestedCategory: "Sonstiges",
    validation: {
      isMathValid: true,
      isDuplicateSuspected: false,
      needsUserReview: false,
      reviewField: "none",
      reviewReason: null,
    },
    imageHash: "hash-mock",
    originalFileName: "mock.jpg",
    fileSizeBytes: 1000,
    createdAt: "2026-08-15T12:00:00.000Z",
    updatedAt: "2026-08-15T12:00:00.000Z",
    status: "ready",
    ...overrides,
  };
}
