/**
 * Sprint A: scanner robustness — persistence blob, imageHash length,
 * scan error mapping, file-size constants.
 */

import { describe, test, expect } from "./runner";
import { DocumentReadError, UnsupportedFileTypeError } from "../../src/lib/ingest/acceptedTypes";
import { SANITIZE_LIMITS, sanitizeReceipt } from "../../src/lib/ingest/sanitize";
import {
  IMAGE_HASH_MAX_LENGTH,
  dbRowToProcessedReceipt,
  pageImageHash,
  toExtractionJson,
} from "../../src/lib/storage/receiptRow";
import { jsonForScanError } from "../../src/lib/http/scanErrors";
import { processReceiptDocument } from "../../src/lib/image/processor";
import { HEIC_DECODE_ERROR_MESSAGE } from "../../src/lib/image/heic";
import { MAX_RECEIPTS_JSON_BYTES, MAX_UPLOAD_BYTES, MAX_UPLOAD_MB } from "../../src/lib/limits";
import { PENDING_VALIDATION, ProcessedReceipt } from "../../src/lib/schema/receipt";

function sampleReceipt(overrides: Partial<ProcessedReceipt> = {}): ProcessedReceipt {
  return {
    id: "rcpt_sprint_a",
    merchant: {
      name: "Trattoria Bella Vista",
      address: "Marktplatz 12, 10115 Berlin",
      taxId: "DE987654321",
      confidence: 0.92,
    },
    date: { isoDate: "2026-08-12", time: "20:15", confidence: 0.91 },
    documentType: "BEWIRTUNGSBELEG",
    receiptNumber: "TR-44201",
    currency: "EUR",
    totalAmount: { value: 31.8, confidence: 0.96 },
    netAmount: 26.72,
    tipAmount: 5,
    taxBreakdown: [{ ratePercent: 19, taxAmount: 5.08, netAmount: 26.72 }],
    lineItems: [{ description: "Pasta", quantity: 1, price: 31.8, taxRate: 19 }],
    suggestedCategory: "Bewirtung",
    hospitality: { occasion: "Geschäftsesen", participants: "Müller, Schmidt" },
    validation: { ...PENDING_VALIDATION },
    imageHash: "a".repeat(64) + "_p1",
    originalFileName: "bewirtung.jpg",
    fileSizeBytes: 2048,
    previewUrl: "data:image/jpeg;base64,abc",
    createdAt: "2026-08-12T10:00:00.000Z",
    updatedAt: "2026-08-12T10:00:00.000Z",
    status: "ready",
    ...overrides,
  };
}

describe("Sprint A — Dateigrößen", () => {
  test("Upload-Limit ist 10 MB", () => {
    expect(MAX_UPLOAD_MB).toBe(10);
    expect(MAX_UPLOAD_BYTES).toBe(10 * 1024 * 1024);
  });

  test("Receipts-JSON-Limit fasst eine max-große Preview", () => {
    expect(MAX_RECEIPTS_JSON_BYTES).toBeGreaterThan(SANITIZE_LIMITS.previewUrl);
    expect(MAX_RECEIPTS_JSON_BYTES).toBeGreaterThan(5 * 1024 * 1024);
  });
});

describe("Sprint A — imageHash", () => {
  test("Sanitize-Limit und DB-Limit sind 128", () => {
    expect(SANITIZE_LIMITS.imageHash).toBe(128);
    expect(IMAGE_HASH_MAX_LENGTH).toBe(128);
  });

  test("Mehrseiten-Hash (SHA-256 + _p20) bleibt unter 128 und wird akzeptiert", () => {
    const hash = pageImageHash("a".repeat(64), 20, 20);
    expect(hash.length).toBe(68);
    expect(hash.length).toBeLessThanOrEqual(IMAGE_HASH_MAX_LENGTH);
    const sanitized = sanitizeReceipt(sampleReceipt({ imageHash: hash }));
    expect(sanitized).not.toBeNull();
    expect(sanitized!.imageHash).toBe(hash);
  });

  test("Einzelseite behält den reinen SHA-256", () => {
    const source = "b".repeat(64);
    expect(pageImageHash(source, 1, 1)).toBe(source);
  });
});

describe("Sprint A — extraction_json", () => {
  test("toExtractionJson entfernt die Preview, behält Adresse/Tax-ID/Zeit/Hospitality", () => {
    const blob = toExtractionJson(sampleReceipt());
    expect(blob.previewUrl).toBeUndefined();
    expect((blob.merchant as ProcessedReceipt["merchant"]).address).toBe("Marktplatz 12, 10115 Berlin");
    expect((blob.merchant as ProcessedReceipt["merchant"]).taxId).toBe("DE987654321");
    expect((blob.date as ProcessedReceipt["date"]).time).toBe("20:15");
    expect((blob.hospitality as ProcessedReceipt["hospitality"])?.occasion).toBe("Geschäftsesen");
    expect(blob.originalFileName).toBe("bewirtung.jpg");
    expect((blob.merchant as ProcessedReceipt["merchant"]).confidence).toBe(0.92);
  });

  test("GET rekonstruiert Felder aus extraction_json statt sie zu nullen", () => {
    const original = sampleReceipt();
    const row = {
      id: original.id,
      projectId: null,
      imageHash: original.imageHash,
      storageUrl: "data:image/avif;base64,preview",
      merchantName: original.merchant.name,
      receiptDate: original.date.isoDate,
      receiptNumber: original.receiptNumber,
      documentType: original.documentType,
      category: original.suggestedCategory,
      currency: original.currency,
      totalAmount: "31.80",
      netAmount: "26.72",
      tipAmount: "5.00",
      taxBreakdownJson: original.taxBreakdown,
      lineItemsJson: original.lineItems,
      validationJson: original.validation,
      rawOcrText: null,
      paymentMethod: null,
      isMathValid: true,
      needsReview: false,
      createdAt: new Date(original.createdAt),
      updatedAt: new Date(original.updatedAt),
      extractionJson: toExtractionJson(original),
    };

    const restored = dbRowToProcessedReceipt(row);
    expect(restored.merchant.address).toBe("Marktplatz 12, 10115 Berlin");
    expect(restored.merchant.taxId).toBe("DE987654321");
    expect(restored.merchant.confidence).toBe(0.92);
    expect(restored.date.time).toBe("20:15");
    expect(restored.date.confidence).toBe(0.91);
    expect(restored.hospitality?.participants).toBe("Müller, Schmidt");
    expect(restored.originalFileName).toBe("bewirtung.jpg");
    expect(restored.fileSizeBytes).toBe(2048);
    expect(restored.previewUrl).toBe("data:image/avif;base64,preview");
    expect(restored.tipAmount).toBe(5);
  });

  test("Altdatensätze ohne extraction_json bleiben lesbar", () => {
    const restored = dbRowToProcessedReceipt({
      id: "legacy",
      projectId: null,
      imageHash: "c".repeat(64),
      storageUrl: null,
      merchantName: "REWE",
      receiptDate: "2026-01-01",
      receiptNumber: null,
      documentType: "KASSENBON",
      category: "Sonstiges",
      currency: "EUR",
      totalAmount: "10.00",
      netAmount: null,
      tipAmount: null,
      taxBreakdownJson: [],
      lineItemsJson: [],
      validationJson: null,
      rawOcrText: null,
      paymentMethod: null,
      isMathValid: true,
      needsReview: false,
      createdAt: new Date("2026-01-01T00:00:00.000Z"),
      updatedAt: new Date("2026-01-01T00:00:00.000Z"),
    });
    expect(restored.merchant.name).toBe("REWE");
    expect(restored.merchant.address).toBeNull();
    expect(restored.totalAmount.value).toBe(10);
  });
});

describe("Sprint A — DocumentReadError", () => {
  test("DocumentReadError wird als 422 mit der echten Meldung gemeldet", async () => {
    const res = jsonForScanError(new DocumentReadError("Das PDF konnte nicht gelesen werden."));
    expect(res.status).toBe(422);
    const body = await res.json();
    expect(body.error).toBe("Das PDF konnte nicht gelesen werden.");
  });

  test("UnsupportedFileTypeError bleibt 415", async () => {
    const res = jsonForScanError(new UnsupportedFileTypeError("Dateityp nicht erlaubt.", "gif"));
    expect(res.status).toBe(415);
  });

  test("unbekannte Fehler bleiben 500 ohne interne Details", async () => {
    const res = jsonForScanError(new Error("ECONNRESET from OpenRouter"));
    expect(res.status).toBe(500);
    const body = await res.json();
    expect(body.error).toBe("Fehler bei der Belegverarbeitung");
  });

  test("kaputtes HEIC wird als DocumentReadError abgelehnt, nicht als unbekannter Typ", async () => {
    const fakeHeic = Buffer.from("\x00\x00\x00\x18ftypheic\x00\x00\x00\x00", "latin1");
    try {
      await processReceiptDocument(fakeHeic, "image/heic");
      throw new Error("expected DocumentReadError");
    } catch (err) {
      expect(err).toBeInstanceOf(DocumentReadError);
      expect((err as DocumentReadError).message).toBe(HEIC_DECODE_ERROR_MESSAGE);
    }
  });
});
