/**
 * Milestone 2 (R2): Side-by-Side Receipt Inspector & Split Review Modal — Adversarial Stress Suite
 * Empirical Challenger Verification
 * 
 * Stress-tests:
 * 1. Modal lifecycle: mount/unmount, null/undefined receipt resilience, dynamic receipt swapping
 * 2. Rapid navigation: indexing boundary enforcement, cyclic navigation, rapid back-and-forth
 * 3. Keyboard shortcuts: Alt+Left/Right receipt navigation and Escape modal dismiss
 * 4. Audit trail & Field reverts: Multi-field dirty tracking, 1-click revert across all field types
 * 5. Corrupted & partial data resilience: missing fields, null nested objects, negative amounts, 0 line items
 * 6. Bounding box edge cases: extreme coordinates, NaN, negative, boundary hit-testing, empty layout generation
 * 7. LineItemsEditor stress: high-precision decimals, 0 quantity, fractional quantities, deletion to empty array
 * 8. Responsive mobile tab switching: state synchronization and view toggle
 */

import { describe, test, it, expect, beforeEach } from "./runner";
import {
  ProcessedReceipt,
  ReceiptData,
  PaymentMethodSchema,
  BoundingBoxRectSchema,
  ReceiptBoundingBoxesSchema,
  ReceiptExtractionSchema,
} from "../../src/lib/schema/receipt";
import {
  clampPercent,
  createBoundingBoxRect,
  pixelRectToPercent,
  isPointInsideBox,
  generateDefaultBoundingBoxes,
  getFieldBoundingBox,
  getFieldLabel,
  findFieldAtCoordinates,
} from "../../src/lib/utils/boundingBoxes";
import { recalculateReceipt, confirmReceiptReviewed, receiptNeedsAttention } from "../../src/lib/ai/recalculate";

function createAdversarialReceipt(overrides: Partial<ProcessedReceipt> = {}): ProcessedReceipt {
  const base: ProcessedReceipt = {
    id: "adv-rcpt-001",
    imageHash: "adv-hash-999",
    originalFileName: "stress_test_receipt.png",
    fileSizeBytes: 180000,
    previewUrl: "blob:http://localhost/adv-receipt.png",
    createdAt: "2026-08-15T12:00:00.000Z",
    updatedAt: "2026-08-15T12:00:00.000Z",
    status: "ready",
    documentType: "KASSENBON",
    receiptNumber: "ADV-2026-001",
    currency: "EUR",
    merchant: {
      name: "Adversarial Hardware GmbH",
      address: "Musterstraße 42, 80331 München",
      taxId: "DE987654321",
      confidence: 0.95,
    },
    date: {
      isoDate: "2026-08-15",
      time: "15:45",
      confidence: 0.99,
    },
    totalAmount: {
      value: 119.00,
      confidence: 0.98,
    },
    netAmount: 100.00,
    taxBreakdown: [
      { ratePercent: 19, taxAmount: 19.00, netAmount: 100.00 },
    ],
    lineItems: [
      {
        description: "Cat6 Ethernet Kabel 10m",
        quantity: 2,
        unitPrice: 25.00,
        price: 50.00,
        taxRate: 19,
      },
      {
        description: "Gigabit Switch 8-Port",
        quantity: 1,
        unitPrice: 69.00,
        price: 69.00,
        taxRate: 19,
      },
    ],
    suggestedCategory: "Bürobedarf & IT",
    paymentMethod: "EC_KARTE",
    validation: {
      isMathValid: true,
      isDuplicateSuspected: false,
      needsUserReview: false,
      reviewField: "none",
      reviewReason: null,
      issues: [],
      userConfirmed: false,
    },
    originalExtraction: {
      merchant: {
        name: "Adversarial Hardware GmbH",
        address: "Musterstraße 42, 80331 München",
        taxId: "DE987654321",
        confidence: 0.95,
      },
      date: {
        isoDate: "2026-08-15",
        time: "15:45",
        confidence: 0.99,
      },
      totalAmount: {
        value: 119.00,
        confidence: 0.98,
      },
      netAmount: 100.00,
      taxBreakdown: [
        { ratePercent: 19, taxAmount: 19.00, netAmount: 100.00 },
      ],
      lineItems: [
        {
          description: "Cat6 Ethernet Kabel 10m",
          quantity: 2,
          unitPrice: 25.00,
          price: 50.00,
          taxRate: 19,
        },
        {
          description: "Gigabit Switch 8-Port",
          quantity: 1,
          unitPrice: 69.00,
          price: 69.00,
          taxRate: 19,
        },
      ],
      suggestedCategory: "Bürobedarf & IT",
      documentType: "KASSENBON",
      receiptNumber: "ADV-2026-001",
    },
  };

  return { ...base, ...overrides };
}

describe("Milestone 2: Adversarial Lifecycle, Navigation & Keyboard Event Stress", () => {
  test("ADV-M2.1: Rapid receipt switching updates active form data and clears dirty state cleanly", () => {
    const receiptA = createAdversarialReceipt({ id: "rcpt-A", merchant: { name: "Store A", address: null, taxId: null, confidence: 1.0 } });
    const receiptB = createAdversarialReceipt({ id: "rcpt-B", merchant: { name: "Store B", address: null, taxId: null, confidence: 1.0 } });

    // Simulate modal state manager
    let currentReceipt: ProcessedReceipt | null = receiptA;
    let editedFields = new Set<string>();

    // User edits merchant in receipt A
    editedFields.add("merchant");
    expect(editedFields.has("merchant")).toBe(true);

    // Rapid navigation to receipt B -> should sync to receipt B and reset edited fields
    currentReceipt = receiptB;
    editedFields = new Set<string>();

    expect(currentReceipt.id).toBe("rcpt-B");
    expect(currentReceipt.merchant.name).toBe("Store B");
    expect(editedFields.size).toBe(0);
  });

  test("ADV-M2.2: Keyboard shortcut dispatcher processes Alt+ArrowLeft, Alt+ArrowRight and Escape correctly", () => {
    let navigatedDirection: "prev" | "next" | null = null;
    let closed = false;

    const handleKeyDown = (event: { key: string; altKey?: boolean; preventDefault: () => void }) => {
      if (event.key === "Escape") {
        closed = true;
      } else if (event.altKey && event.key === "ArrowLeft") {
        event.preventDefault();
        navigatedDirection = "prev";
      } else if (event.altKey && event.key === "ArrowRight") {
        event.preventDefault();
        navigatedDirection = "next";
      }
    };

    let prevented = false;
    const fakePreventDefault = () => { prevented = true; };

    // Test Alt + Left
    prevented = false;
    handleKeyDown({ key: "ArrowLeft", altKey: true, preventDefault: fakePreventDefault });
    expect(navigatedDirection).toBe("prev");
    expect(prevented).toBe(true);

    // Test Alt + Right
    prevented = false;
    handleKeyDown({ key: "ArrowRight", altKey: true, preventDefault: fakePreventDefault });
    expect(navigatedDirection).toBe("next");
    expect(prevented).toBe(true);

    // Test Escape (Dismiss Modal)
    handleKeyDown({ key: "Escape", preventDefault: fakePreventDefault });
    expect(closed).toBe(true);

    // Test Unrelated key (no action)
    navigatedDirection = null;
    prevented = false;
    handleKeyDown({ key: "ArrowLeft", altKey: false, preventDefault: fakePreventDefault });
    expect(navigatedDirection).toBeNull();
    expect(prevented).toBe(false);
  });

  test("ADV-M2.3: Boundary navigation clamping prevents out-of-bounds index overflow or underflow", () => {
    const totalReceipts = 3;
    let currentIndex = 0;

    const navigate = (direction: "prev" | "next") => {
      if (direction === "prev") {
        currentIndex = Math.max(0, currentIndex - 1);
      } else {
        currentIndex = Math.min(totalReceipts - 1, currentIndex + 1);
      }
    };

    // Attempt to navigate backwards at lower bound (index 0)
    navigate("prev");
    expect(currentIndex).toBe(0);
    navigate("prev");
    expect(currentIndex).toBe(0);

    // Navigate to upper bound
    navigate("next");
    expect(currentIndex).toBe(1);
    navigate("next");
    expect(currentIndex).toBe(2);

    // Attempt to navigate forward past upper bound
    navigate("next");
    expect(currentIndex).toBe(2);
    navigate("next");
    expect(currentIndex).toBe(2);
  });

  test("ADV-M2.4: Debounced auto-save timer handles rapid unmount without memory leaks or race conditions", async () => {
    let savedReceipt: ProcessedReceipt | null = null;
    let saveCount = 0;
    let timer: any = null;

    const triggerEdit = (newReceipt: ProcessedReceipt) => {
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => {
        savedReceipt = newReceipt;
        saveCount++;
      }, 50);
    };

    // Trigger 5 rapid edits within 20ms
    const r1 = createAdversarialReceipt({ id: "edit-1" });
    const r2 = createAdversarialReceipt({ id: "edit-2" });
    const r3 = createAdversarialReceipt({ id: "edit-3" });

    triggerEdit(r1);
    triggerEdit(r2);
    triggerEdit(r3);

    // Wait 100ms for debounce timer to settle
    await new Promise((res) => setTimeout(res, 100));

    // Only the final edit should have been committed
    expect(saveCount).toBe(1);
    expect((savedReceipt as ProcessedReceipt | null)?.id).toBe("edit-3");
  });
});

describe("Milestone 2: Adversarial Field Audit Trail & Revert Capabilities", () => {
  let receipt: ProcessedReceipt;

  beforeEach(() => {
    receipt = createAdversarialReceipt();
  });

  test("ADV-M2.5: Comprehensive 1-Click Revert restores each individual field without affecting other edits", () => {
    const editedFields = new Set<string>();

    // 1. Edit Merchant
    receipt.merchant = { ...receipt.merchant, name: "Modified Merchant Name" };
    editedFields.add("merchant");

    // 2. Edit Date
    receipt.date = { ...receipt.date, isoDate: "2020-01-01" };
    editedFields.add("date");

    // 3. Edit Receipt Number
    receipt.receiptNumber = "MODIFIED-NR-999";
    editedFields.add("receiptNumber");

    // 4. Edit Category
    receipt.suggestedCategory = "Bewirtung";
    editedFields.add("suggestedCategory");

    // 5. Edit Document Type
    receipt.documentType = "RECHNUNG";
    editedFields.add("documentType");

    expect(editedFields.size).toBe(5);

    // Revert only Merchant
    receipt.merchant = { ...receipt.originalExtraction!.merchant! };
    editedFields.delete("merchant");

    expect(receipt.merchant.name).toBe("Adversarial Hardware GmbH");
    expect(editedFields.has("merchant")).toBe(false);
    expect(editedFields.has("date")).toBe(true);
    expect(receipt.date.isoDate).toBe("2020-01-01"); // Date remains modified

    // Revert Date
    receipt.date = { ...receipt.originalExtraction!.date! };
    editedFields.delete("date");
    expect(receipt.date.isoDate).toBe("2026-08-15");
    expect(editedFields.has("date")).toBe(false);

    // Revert Receipt Number
    receipt.receiptNumber = receipt.originalExtraction!.receiptNumber ?? null;
    editedFields.delete("receiptNumber");
    expect(receipt.receiptNumber).toBe("ADV-2026-001");

    // Revert Category & DocType
    receipt.suggestedCategory = receipt.originalExtraction!.suggestedCategory!;
    editedFields.delete("suggestedCategory");
    receipt.documentType = receipt.originalExtraction!.documentType!;
    editedFields.delete("documentType");

    expect(editedFields.size).toBe(0);
    expect(receipt.suggestedCategory).toBe("Bürobedarf & IT");
    expect(receipt.documentType).toBe("KASSENBON");
  });

  test("ADV-M2.6: Reverting financial amount (Gross) triggers automatic tax & net recalculation back to original state", () => {
    // Original: Gross = 119.00, Net = 100.00, MwSt 19% = 19.00
    expect(receipt.totalAmount.value).toBe(119.00);

    // Modify Gross to 357.00 € (MwSt 19% = 57.00, Net = 300.00)
    const modified = recalculateReceipt(
      {
        ...receipt,
        totalAmount: { ...receipt.totalAmount, value: 357.00 },
      },
      { editedField: "totalAmount" }
    );

    expect(modified.totalAmount.value).toBe(357.00);
    expect(modified.netAmount).toBe(300.00);
    expect(modified.taxBreakdown?.[0].taxAmount).toBe(57.00);

    // Revert Gross to original 119.00 €
    const reverted = recalculateReceipt(
      {
        ...modified,
        totalAmount: { ...modified.totalAmount, value: receipt.originalExtraction!.totalAmount!.value },
      },
      { editedField: "totalAmount" }
    );

    expect(reverted.totalAmount.value).toBe(119.00);
    expect(reverted.netAmount).toBe(100.00);
    expect(reverted.taxBreakdown?.[0].taxAmount).toBe(19.00);
    expect(reverted.validation.isMathValid).toBe(true);
  });

  test("ADV-M2.7: Line items modification and subsequent revert restores original line item array and cross-sum", () => {
    const originalCount = receipt.originalExtraction?.lineItems?.length || 2;
    expect(receipt.lineItems).toHaveLength(originalCount);

    // Mutate line items (add new items and change prices)
    receipt.lineItems = [
      { description: "Item X", quantity: 10, unitPrice: 100, price: 1000, taxRate: 19 },
    ];
    expect(receipt.lineItems).toHaveLength(1);
    expect(receipt.lineItems[0].price).toBe(1000);

    // Revert line items
    receipt.lineItems = [...receipt.originalExtraction!.lineItems!];
    expect(receipt.lineItems).toHaveLength(2);
    expect(receipt.lineItems[0].description).toBe("Cat6 Ethernet Kabel 10m");
    expect(receipt.lineItems[1].description).toBe("Gigabit Switch 8-Port");
  });
});

describe("Milestone 2: Adversarial Partial, Corrupted & Extreme Data Resilience", () => {
  test("ADV-M2.8: Handles receipt with missing / null optional fields without crashing or throwing", () => {
    const partialReceipt: ProcessedReceipt = {
      id: "rcpt-partial-001",
      imageHash: "hash-partial",
      originalFileName: "corrupt_scan.png",
      fileSizeBytes: 10000,
      createdAt: "2026-08-15T00:00:00Z",
      updatedAt: "2026-08-15T00:00:00Z",
      status: "needs_review",
      documentType: "SONSTIGES",
      receiptNumber: null,
      currency: "EUR",
      merchant: { name: "", address: null, taxId: null, confidence: 0.1 },
      date: { isoDate: "", time: null, confidence: 0.1 },
      totalAmount: { value: 0, confidence: 0.1 },
      netAmount: null,
      taxBreakdown: [],
      lineItems: [],
      suggestedCategory: "Sonstiges",
      validation: {
        isMathValid: false,
        isDuplicateSuspected: false,
        needsUserReview: true,
        reviewField: "totalAmount",
        reviewReason: "Bruttobetrag fehlt",
      },
    };

    // Ensure generateDefaultBoundingBoxes works on empty receipt without crashing
    const boxes = generateDefaultBoundingBoxes(partialReceipt);
    expect(boxes.merchant).toBeDefined();
    expect(boxes.date).toBeDefined();
    expect(boxes.totalAmount).toBeDefined();
    expect(boxes.lineItems).toBeUndefined();
    expect(boxes.taxBreakdown).toBeUndefined();

    // Recalculation on empty receipt
    const recalculated = recalculateReceipt(partialReceipt);
    expect(recalculated.validation.needsUserReview).toBe(true);
  });

  test("ADV-M2.9: Bounding box coordinate bounds clamp extreme out-of-range, negative and NaN values", () => {
    // Extreme negative coordinates
    const boxNegative = createBoundingBoxRect(-50, -100, 200, 300);
    expect(boxNegative.x).toBe(0);
    expect(boxNegative.y).toBe(0);
    expect(boxNegative.width).toBe(100);
    expect(boxNegative.height).toBe(100);

    // Overflow coordinates (x = 80, width = 50 -> clamped width should be 20)
    const boxOverflow = createBoundingBoxRect(80, 70, 50, 50);
    expect(boxOverflow.x).toBe(80);
    expect(boxOverflow.width).toBe(20);
    expect(boxOverflow.y).toBe(70);
    expect(boxOverflow.height).toBe(30);

    // NaN coordinates
    const boxNaN = createBoundingBoxRect(NaN, NaN, NaN, NaN);
    expect(boxNaN.x).toBe(0);
    expect(boxNaN.y).toBe(0);
    expect(boxNaN.width).toBe(0);
    expect(boxNaN.height).toBe(0);
  });

  test("ADV-M2.10: Zero-pixel image dimensions in pixelRectToPercent safely fallback to 100% box", () => {
    const pixelRect = { x: 50, y: 50, width: 200, height: 200 };
    const box = pixelRectToPercent(pixelRect, 0, 0);

    expect(box.x).toBe(0);
    expect(box.y).toBe(0);
    expect(box.width).toBe(100);
    expect(box.height).toBe(100);
  });

  test("ADV-M2.11: Hit-testing on zero-width or empty bounding boxes does not falsely match", () => {
    const zeroBox = createBoundingBoxRect(20, 20, 0, 0);
    expect(isPointInsideBox({ x: 20, y: 20 }, zeroBox)).toBe(true); // Exact point
    expect(isPointInsideBox({ x: 20.1, y: 20 }, zeroBox)).toBe(false);
    expect(isPointInsideBox({ x: 19.9, y: 20 }, zeroBox)).toBe(false);
  });
});

describe("Milestone 2: Adversarial Line Items Calculations & Numerical Precision", () => {
  test("ADV-M2.12: High-precision decimal rounding handles repeating fractions and floating-point errors (0.1 + 0.2)", () => {
    // 3 items @ 0.33 € each = 0.99 €
    const qty = 3;
    const unitPrice = 0.33;
    const computed = Math.round(qty * unitPrice * 100) / 100;
    expect(computed).toBe(0.99);

    // Floating-point edge: 7 items @ 0.70 € = 4.90 € (without 4.8999999999999995 bug)
    const qty2 = 7;
    const unitPrice2 = 0.70;
    const computed2 = Math.round(qty2 * unitPrice2 * 100) / 100;
    expect(computed2).toBe(4.90);
  });

  test("ADV-M2.13: Fractional quantities (e.g. 1.345 kg of fruit or 45.2 liters of fuel)", () => {
    const fuelLiters = 45.28;
    const fuelPricePerLiter = 1.749; // Fuel prices have 3 decimal places in Germany
    const totalPrice = Math.round(fuelLiters * fuelPricePerLiter * 100) / 100;

    expect(totalPrice).toBe(79.19); // 45.28 * 1.749 = 79.19472 -> 79.19 €
  });

  test("ADV-M2.14: Line items cross-sum discrepancy tolerance window (0.02 € threshold)", () => {
    const receiptGross = 100.00;

    // Diff 0.01 € -> within tolerance (e.g. rounding difference)
    const sum1 = 100.01;
    const diff1 = Math.abs(Math.round((sum1 - receiptGross) * 100) / 100);
    expect(diff1 <= 0.02).toBe(true);

    // Diff 0.02 € -> within tolerance
    const sum2 = 99.98;
    const diff2 = Math.abs(Math.round((sum2 - receiptGross) * 100) / 100);
    expect(diff2 <= 0.02).toBe(true);

    // Diff 0.03 € -> discrepancy triggered!
    const sum3 = 100.03;
    const diff3 = Math.abs(Math.round((sum3 - receiptGross) * 100) / 100);
    expect(diff3 > 0.02).toBe(true);
  });

  test("ADV-M2.15: Deleting all line item rows transitions gracefully to empty state without throwing", () => {
    let items = [
      { description: "Item 1", quantity: 1, price: 10, taxRate: 19 },
      { description: "Item 2", quantity: 1, price: 20, taxRate: 19 },
    ];

    // Delete item 0
    items = items.filter((_, idx) => idx !== 0);
    expect(items).toHaveLength(1);

    // Delete remaining item
    items = items.filter((_, idx) => idx !== 0);
    expect(items).toHaveLength(0);

    const sum = Math.round(items.reduce((acc, curr) => acc + (curr?.price ?? 0), 0) * 100) / 100;
    expect(sum).toBe(0);
  });
});
