/**
 * Milestone 3 (R3) Challenger 2 Deep Adversarial Stress Suite
 * Empirical Verification of Requirement R3:
 * 1. Inline Cell Editing & Mathematical Recalculations (0 €, negative, corrupted strings, rapid sequential edits)
 * 2. Bulk Export Generation (Selected only vs all, CSV injection sanitization, UTF-8 BOM, dual-sheet XLSX integrity)
 * 3. Bulk Status Updates & Categorization & Deletion
 * 4. Selection & Filtering Edge Cases & Invariants
 */

import { describe, test, it, expect, beforeEach } from "./runner";
import { ProcessedReceipt, ReceiptCategory, PaymentMethod, DocumentType } from "../../src/lib/schema/receipt";
import {
  resolveReceiptStatusTier,
  getStatusTierMeta,
  ReceiptStatusTier,
} from "../../src/components/dashboard/StatusBadge";
import {
  recalculateReceipt,
  confirmReceiptReviewed,
  receiptNeedsAttention,
} from "../../src/lib/ai/recalculate";
import {
  parseAmountInput,
  formatAmountInput,
  formatMoney,
  grossOf,
  netOf,
  totalTaxOf,
  taxAmountForRate,
  collectTaxRates,
} from "../../src/components/dashboard/receiptFormat";
import { generateDualSheetExcel } from "../../src/lib/export/excelGenerator";
import { generateAccountingCsv } from "../../src/lib/export/csvGenerator";

function createTestReceipt(overrides: Partial<ProcessedReceipt> = {}): ProcessedReceipt {
  return {
    id: "challenger-rcpt-001",
    imageHash: "hash-ch-123",
    originalFileName: "sample_receipt.pdf",
    fileSizeBytes: 150000,
    previewUrl: "blob:http://localhost/sample.pdf",
    createdAt: "2026-08-15T12:00:00.000Z",
    updatedAt: "2026-08-15T12:00:00.000Z",
    status: "ready",
    documentType: "KASSENBON",
    receiptNumber: "RE-9901",
    currency: "EUR",
    merchant: {
      name: "Café Extrablatt GmbH",
      address: "Alexanderplatz 1, 10178 Berlin",
      taxId: "DE123456789",
      confidence: 0.95,
    },
    date: {
      isoDate: "2026-08-15",
      time: "14:30",
      confidence: 0.95,
    },
    totalAmount: {
      value: 119.0,
      confidence: 0.95,
    },
    netAmount: 100.0,
    taxBreakdown: [
      { ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 },
    ],
    lineItems: [
      {
        description: "Espresso Doppio",
        quantity: 2,
        unitPrice: 3.5,
        price: 7.0,
        taxRate: 19,
      },
      {
        description: "Frühstücksbuffet",
        quantity: 4,
        unitPrice: 28.0,
        price: 112.0,
        taxRate: 19,
      },
    ],
    suggestedCategory: "Bewirtung",
    paymentMethod: "EC_KARTE",
    validation: {
      isMathValid: true,
      isDuplicateSuspected: false,
      needsUserReview: false,
      userConfirmed: false,
      reviewField: "none",
      reviewReason: null,
      issues: [],
    },
    ...overrides,
  };
}

describe("CHALLENGER-M3: Inline Cell Editing & Numeric Parsing Stress", () => {
  test("CH-M3.1: parseAmountInput parses varied valid and tricky numeric inputs", () => {
    expect(parseAmountInput("119,00")).toBe(119.0);
    expect(parseAmountInput("119.00")).toBe(119.0);
    expect(parseAmountInput("1.234,56 €")).toBe(1234.56);
    expect(parseAmountInput("1,234.56 EUR")).toBe(1234.56);
    expect(parseAmountInput("0,00")).toBe(0.0);
    expect(parseAmountInput("0")).toBe(0.0);
    expect(parseAmountInput("0.05")).toBe(0.05);
    expect(parseAmountInput("-50,00")).toBe(-50.0);
    expect(parseAmountInput("-12.34")).toBe(-12.34);
    expect(parseAmountInput("   99,99   ")).toBe(99.99);
  });

  test("CH-M3.2: parseAmountInput returns null for corrupted strings without crashing or returning NaN", () => {
    const corrupted = [
      "abc",
      "",
      "   ",
      "NaN",
      "Infinity",
      "-Infinity",
      "€€€",
      "EUR",
      "foo-bar-123",
      "12.34.56.78",
      ",,,",
      "... ",
      "$$$123",
      "[object Object]",
      "undefined",
      "null",
    ];

    for (const val of corrupted) {
      const parsed = parseAmountInput(val);
      if (parsed !== null) {
        expect(Number.isFinite(parsed)).toBe(true);
      }
    }
  });

  test("CH-M3.3: Editing Gross Amount to 0.00 € recalculates Net and Tax to 0.00 without division by zero", () => {
    const rcpt = createTestReceipt({
      totalAmount: { value: 119.0, confidence: 0.9 },
      netAmount: 100.0,
      taxBreakdown: [{ ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 }],
    });

    const updated = {
      ...rcpt,
      totalAmount: { ...rcpt.totalAmount, value: 0.0 },
    };

    const result = recalculateReceipt(updated, { editedField: "totalAmount" });
    expect(result.totalAmount.value).toBe(0.0);
    expect(result.netAmount).toBe(0.0);
    expect(result.taxBreakdown[0].taxAmount).toBe(0.0);
    expect(result.taxBreakdown[0].netAmount).toBe(0.0);
    expect(Number.isNaN(result.netAmount ?? 0)).toBe(false);
    expect(Number.isFinite(result.netAmount ?? 0)).toBe(true);
    expect(result.validation.isMathValid).toBe(true);
  });

  test("CH-M3.4: Editing Gross Amount to negative value (refund / credit note) distributes proportionally", () => {
    const rcpt = createTestReceipt({
      totalAmount: { value: 119.0, confidence: 0.9 },
      netAmount: 100.0,
      taxBreakdown: [{ ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 }],
      lineItems: [], // No positive line items
    });

    const updated = {
      ...rcpt,
      totalAmount: { ...rcpt.totalAmount, value: -119.0 },
    };

    const result = recalculateReceipt(updated, { editedField: "totalAmount" });
    expect(result.totalAmount.value).toBe(-119.0);
    expect(result.netAmount).toBe(-100.0);
    expect(result.taxBreakdown[0].taxAmount).toBe(-19.0);
    expect(result.taxBreakdown[0].netAmount).toBe(-100.0);
    // Net + tax sum precisely equals negative gross (-100 + -19 = -119)
    expect((result.netAmount ?? 0) + (result.taxBreakdown[0]?.taxAmount ?? 0)).toBe(-119.0);
  });

  test("CH-M3.5: Multi-tax rate gross recalculation with 19%, 7%, and 0% taxes", () => {
    const multiTax = createTestReceipt({
      totalAmount: { value: 126.0, confidence: 0.9 },
      netAmount: 110.0,
      taxBreakdown: [
        { ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 },
        { ratePercent: 7, taxAmount: 0.7, netAmount: 10.0 },
        { ratePercent: 0, taxAmount: 0.0, netAmount: 0.0 },
      ],
    });

    const updated = {
      ...multiTax,
      totalAmount: { ...multiTax.totalAmount, value: 252.0 },
    };

    const result = recalculateReceipt(updated, { editedField: "totalAmount" });
    expect(result.totalAmount.value).toBe(252.0);
    // Gross doubled: 126 -> 252. Net and tax should double accordingly
    const sumTaxes = totalTaxOf(result);
    const net = netOf(result);
    expect(Math.round((net + sumTaxes) * 100) / 100).toBe(252.0);
    expect(result.validation.isMathValid).toBe(true);
  });

  test("CH-M3.6: Editing Net Amount holds Gross constant and re-derives taxes", () => {
    const rcpt = createTestReceipt({
      totalAmount: { value: 119.0, confidence: 0.9 },
      netAmount: 100.0,
      taxBreakdown: [{ ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 }],
    });

    const updated = {
      ...rcpt,
      netAmount: 90.0,
    };

    const result = recalculateReceipt(updated, { editedField: "netAmount" });
    expect(result.totalAmount.value).toBe(119.0);
    expect(result.netAmount).toBe(90.0);
    expect(result.taxBreakdown[0].taxAmount).toBe(29.0); // 119 - 90 = 29
    expect(result.taxBreakdown[0].netAmount).toBe(90.0);
  });

  test("CH-M3.7: Rapid sequential edits across multiple fields preserve integrity", () => {
    let rcpt = createTestReceipt();

    // 1. Edit Merchant
    rcpt = recalculateReceipt(
      {
        ...rcpt,
        merchant: { ...rcpt.merchant, name: "Neue Gastronomie Berlin" },
        editedFields: { merchant: true },
      },
      { editedField: "merchant" }
    );
    expect(rcpt.merchant.name).toBe("Neue Gastronomie Berlin");
    expect(rcpt.merchant.confidence).toBe(1.0);

    // 2. Edit Date
    rcpt = recalculateReceipt(
      {
        ...rcpt,
        date: { ...rcpt.date, isoDate: "2026-08-01" },
        editedFields: { ...rcpt.editedFields, date: true },
      },
      { editedField: "date" }
    );
    expect(rcpt.date.isoDate).toBe("2026-08-01");
    expect(rcpt.date.confidence).toBe(1.0);

    // 3. Edit Gross
    rcpt = recalculateReceipt(
      {
        ...rcpt,
        totalAmount: { ...rcpt.totalAmount, value: 595.0 },
        editedFields: { ...rcpt.editedFields, totalAmount: true },
      },
      { editedField: "totalAmount" }
    );
    expect(rcpt.totalAmount.value).toBe(595.0);
    expect(rcpt.netAmount).toBe(500.0);
    expect(rcpt.taxBreakdown[0].taxAmount).toBe(95.0);
    expect(rcpt.totalAmount.confidence).toBe(1.0);

    // 4. Edit Category
    rcpt = recalculateReceipt(
      {
        ...rcpt,
        suggestedCategory: "Reisekosten & Hotel",
        editedFields: { ...rcpt.editedFields, category: true },
      },
      { editedField: "category" }
    );
    expect(rcpt.suggestedCategory).toBe("Reisekosten & Hotel");

    expect(rcpt.validation.isMathValid).toBe(true);
  });
});

describe("CHALLENGER-M3: Bulk Export Generation & Security Sanitization", () => {
  const dataset: ProcessedReceipt[] = [
    createTestReceipt({
      id: "exp-1",
      merchant: { name: 'Firma "Test & Co." GmbH', address: "München", taxId: "DE1", confidence: 1.0 },
      receiptNumber: "INV-001",
      totalAmount: { value: 119.0, confidence: 1.0 },
      suggestedCategory: "Bürobedarf & IT",
    }),
    createTestReceipt({
      id: "exp-2",
      merchant: { name: "=1+1; -- Formula Injection Test", address: "Frankfurt", taxId: "DE2", confidence: 1.0 },
      receiptNumber: "INV-002",
      totalAmount: { value: 53.5, confidence: 1.0 },
      netAmount: 50.0,
      taxBreakdown: [{ ratePercent: 7, taxAmount: 3.5, netAmount: 50.0 }],
      suggestedCategory: "Bewirtung",
      hospitality: { occasion: "Kundengespräch & Akquise", participants: "Max Mustermann, Erika Musterfrau" },
    }),
    createTestReceipt({
      id: "exp-3",
      merchant: { name: "@SUM(A1:A100) \n Multiline \r\n Carriage", address: "Hamburg", taxId: "DE3", confidence: 1.0 },
      receiptNumber: "INV-003",
      totalAmount: { value: 200.0, confidence: 1.0 },
      suggestedCategory: "Tanken & KFZ",
    }),
  ];

  test("CH-M3.8: Bulk export for SELECTED items only excludes unselected records", async () => {
    const selectedIds = ["exp-1", "exp-3"];
    const selectedReceipts = dataset.filter((r) => selectedIds.includes(r.id));

    expect(selectedReceipts).toHaveLength(2);
    expect(selectedReceipts.some((r) => r.id === "exp-2")).toBe(false);

    // CSV
    const csv = generateAccountingCsv(selectedReceipts);
    expect(csv).toContain("INV-001");
    expect(csv).toContain("INV-003");
    expect(csv.includes("INV-002")).toBe(false);

    // Excel
    const xlsxBuffer = await generateDualSheetExcel(selectedReceipts);
    expect(xlsxBuffer).toBeDefined();
    expect(xlsxBuffer.length).toBeGreaterThan(2000);
  });

  test("CH-M3.9: Bulk export with EMPTY list handles cleanly without throwing", async () => {
    const emptyCsv = generateAccountingCsv([]);
    expect(emptyCsv).toBeDefined();
    expect(emptyCsv.charCodeAt(0)).toBe(0xfeff); // UTF-8 BOM
    expect(emptyCsv).toContain("Laufende Nr");

    const emptyXlsx = await generateDualSheetExcel([]);
    expect(emptyXlsx).toBeDefined();
    expect(emptyXlsx.length).toBeGreaterThan(500);
  });

  test("CH-M3.10: Accounting CSV properly quotes and escapes semicolons, quotes, and newlines", () => {
    const csv = generateAccountingCsv(dataset);

    // Must start with UTF-8 BOM
    expect(csv.charCodeAt(0)).toBe(0xfeff);

    // Double quotes must be escaped as ""
    expect(csv).toContain('""Test & Co.""');

    // Formula-injection payloads (=, +, - @, tab, CR) are neutralized with a
    // leading apostrophe (OWASP CSV-injection mitigation) before quoting, so
    // the cell is exported as safe text, never as an evaluable formula
    expect(csv).toContain("'=1+1; -- Formula Injection Test\"");

    // Hospitality details must be exported when present
    expect(csv).toContain("Kundengespräch & Akquise");
    expect(csv).toContain("Max Mustermann, Erika Musterfrau");
  });

  test("CH-M3.11: Dual-Sheet Excel generates both Belegübersicht and Einzelpositionen Detail sheets", async () => {
    const buffer = await generateDualSheetExcel(dataset);
    expect(buffer).toBeInstanceOf(Buffer);
    expect(buffer.length).toBeGreaterThan(5000);

    // Verify it's a valid ZIP / XLSX header (PK\x03\x04)
    expect(buffer[0]).toBe(0x50); // 'P'
    expect(buffer[1]).toBe(0x4b); // 'K'
    expect(buffer[2]).toBe(0x03);
    expect(buffer[3]).toBe(0x04);
  });
});

describe("CHALLENGER-M3: Bulk Status Updates & Batch Operations", () => {
  const mockBatch = [
    createTestReceipt({ id: "b1", status: "needs_review", validation: { isMathValid: false, isDuplicateSuspected: false, needsUserReview: true, userConfirmed: false, reviewField: "taxBreakdown", reviewReason: "Diff", issues: [{ field: "taxBreakdown", severity: "error", message: "Diff" }] } }),
    createTestReceipt({ id: "b2", status: "ready", validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: false, userConfirmed: false, reviewField: "none", reviewReason: null, issues: [] } }),
    createTestReceipt({ id: "b3", status: "ready", validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: false, userConfirmed: false, reviewField: "none", reviewReason: null, issues: [] } }),
  ];

  test("CH-M3.12: Bulk Status Update to 'confirmed' verifies all selected items and clears review flags", () => {
    const selectedIds = ["b1", "b2"];
    const updatedBatch = mockBatch.map((r) =>
      selectedIds.includes(r.id) ? confirmReceiptReviewed(r) : r
    );

    const b1Updated = updatedBatch.find((r) => r.id === "b1");
    const b2Updated = updatedBatch.find((r) => r.id === "b2");
    const b3Updated = updatedBatch.find((r) => r.id === "b3");

    expect(b1Updated?.validation.userConfirmed).toBe(true);
    expect(b1Updated?.validation.needsUserReview).toBe(false);
    expect(resolveReceiptStatusTier(b1Updated!)).toBe("confirmed");

    expect(b2Updated?.validation.userConfirmed).toBe(true);
    expect(resolveReceiptStatusTier(b2Updated!)).toBe("confirmed");

    expect(b3Updated?.validation.userConfirmed).toBe(false);
    expect(resolveReceiptStatusTier(b3Updated!)).toBe("scanned");
  });

  test("CH-M3.13: Bulk Categorize updates category and marks editedFields", () => {
    const selectedIds = ["b1", "b3"];
    const targetCategory: ReceiptCategory = "Tanken & KFZ";

    const updatedBatch = mockBatch.map((r) => {
      if (selectedIds.includes(r.id)) {
        const withCat = {
          ...r,
          suggestedCategory: targetCategory,
          editedFields: { ...(r.editedFields || {}), category: true },
        };
        return recalculateReceipt(withCat, { editedField: "category" });
      }
      return r;
    });

    expect(updatedBatch.find((r) => r.id === "b1")?.suggestedCategory).toBe("Tanken & KFZ");
    expect(updatedBatch.find((r) => r.id === "b2")?.suggestedCategory).toBe("Bewirtung");
    expect(updatedBatch.find((r) => r.id === "b3")?.suggestedCategory).toBe("Tanken & KFZ");
  });
});

describe("CHALLENGER-M3: Filter & Selection Mathematical Invariants", () => {
  const testList: ProcessedReceipt[] = [
    createTestReceipt({ id: "t1", date: { isoDate: "2026-08-15", time: "10:00", confidence: 1.0 }, totalAmount: { value: 25.0, confidence: 1.0 }, suggestedCategory: "Bewirtung" }),
    createTestReceipt({ id: "t2", date: { isoDate: "2026-08-14", time: "12:00", confidence: 1.0 }, totalAmount: { value: 75.0, confidence: 1.0 }, suggestedCategory: "Tanken & KFZ", validation: { isMathValid: false, isDuplicateSuspected: false, needsUserReview: true, userConfirmed: false, reviewField: "taxBreakdown", reviewReason: "Diff", issues: [] } }),
    createTestReceipt({ id: "t3", date: { isoDate: "2026-07-01", time: "09:00", confidence: 1.0 }, totalAmount: { value: 350.0, confidence: 1.0 }, suggestedCategory: "Bürobedarf & IT", validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: false, userConfirmed: true, reviewField: "none", reviewReason: null, issues: [] } }),
    createTestReceipt({ id: "t4", date: { isoDate: "2026-01-10", time: "15:00", confidence: 1.0 }, totalAmount: { value: 12.0, confidence: 1.0 }, suggestedCategory: "Sonstiges" }),
  ];

  test("CH-M3.14: Status count partition invariant holds (all === scanned + pending + confirmed)", () => {
    let scanned = 0;
    let pending = 0;
    let confirmed = 0;

    for (const r of testList) {
      const tier = resolveReceiptStatusTier(r);
      if (tier === "pending_review") pending++;
      else if (tier === "confirmed") confirmed++;
      else scanned++;
    }

    expect(scanned + pending + confirmed).toBe(testList.length);
    expect(scanned).toBe(2);
    expect(pending).toBe(1);
    expect(confirmed).toBe(1);
  });

  test("CH-M3.15: Category collection and tax rate collection are deterministic sets", () => {
    const taxRates = collectTaxRates(testList);
    expect(Array.isArray(taxRates)).toBe(true);
    expect(taxRates).toContain(19);

    const categories = Array.from(new Set(testList.map((r) => r.suggestedCategory)));
    expect(categories).toHaveLength(4);
    expect(categories).toContain("Bewirtung");
    expect(categories).toContain("Tanken & KFZ");
    expect(categories).toContain("Bürobedarf & IT");
    expect(categories).toContain("Sonstiges");
  });
});
