/**
 * Sprint C: image quality — min width, contrast gate, crop box, deskew.
 */

import { describe, test, expect } from "./runner";
import sharp from "sharp";
import {
  AI_IMAGE_LONG_EDGE_PX,
  AI_IMAGE_MIN_WIDTH_PX,
  computeReceiptResize,
} from "../../src/lib/image/resize";
import {
  contentBoundingBox,
  detectPaperQuadrilateral,
  estimateSkewDegrees,
  needsContrastBoost,
  paperReceiptBoundingBox,
} from "../../src/lib/image/enhance";
import { assessReceiptDetailQuality, processReceiptImage } from "../../src/lib/image/processor";

describe("Sprint C — Mindestbreite", () => {
  test("Querformat bleibt am 1536-Long-Edge", () => {
    const out = computeReceiptResize(2000, 1400);
    expect(out.width).toBe(1536);
    expect(out.height).toBe(1075);
  });

  test("quadratische kleine Fotos werden nicht hochskaliert", () => {
    expect(computeReceiptResize(400, 400)).toEqual({ width: 400, height: 400 });
  });

  test("hoher Thermobon wird breiter als bei reinem Long-Edge-Cap", () => {
    const srcW = 800;
    const srcH = 3500;
    const longEdgeOnlyW = Math.round(srcW * (AI_IMAGE_LONG_EDGE_PX / srcH));
    const out = computeReceiptResize(srcW, srcH);
    expect(longEdgeOnlyW).toBeLessThan(400);
    expect(out.width).toBeGreaterThan(longEdgeOnlyW);
    expect(out.width).toBeGreaterThanOrEqual(650);
    expect(Math.max(out.width, out.height)).toBeLessThanOrEqual(4096);
  });

  test("Min-Width-Konstante ist 1100", () => {
    expect(AI_IMAGE_MIN_WIDTH_PX).toBe(1100);
  });
});

describe("Sprint C — Kontrast", () => {
  test("verwaschenes Thermobild (niedrige Stdev) bekommt Boost", () => {
    expect(
      needsContrastBoost({
        channels: [{ mean: 170, stdev: 12, min: 140, max: 200 }],
      })
    ).toBe(true);
  });

  test("farbstarkes Foto bekommt keinen Boost", () => {
    expect(
      needsContrastBoost({
        channels: [
          { mean: 90, stdev: 55, min: 10, max: 240 },
          { mean: 100, stdev: 60, min: 8, max: 250 },
          { mean: 80, stdev: 50, min: 5, max: 230 },
        ],
      })
    ).toBe(false);
  });
});

describe("Sprint C — Crop", () => {
  test("dunkles Rechteck auf weißem Grund wird als Inhalt erkannt", () => {
    const w = 80;
    const h = 80;
    const data = new Uint8Array(w * h).fill(250);
    for (let y = 18; y < 62; y++) {
      for (let x = 16; x < 64; x++) {
        data[y * w + x] = 20;
      }
    }
    const box = contentBoundingBox(data, w, h);
    expect(box).not.toBeNull();
    expect(box!.left).toBeLessThanOrEqual(16);
    expect(box!.top).toBeLessThanOrEqual(18);
    expect(box!.left + box!.width).toBeGreaterThanOrEqual(64);
    expect(box!.top + box!.height).toBeGreaterThanOrEqual(62);
  });

  test("einfarbiges Bild wird nicht beschnitten", () => {
    const data = new Uint8Array(40 * 40).fill(200);
    expect(contentBoundingBox(data, 40, 40)).toBeNull();
  });

  test("heller Thermobon wird auf strukturiertem farbigem Untergrund erkannt", () => {
    const w = 100;
    const h = 140;
    const data = new Uint8Array(w * h * 3);
    for (let y = 0; y < h; y++) {
      for (let x = 0; x < w; x++) {
        const i = (y * w + x) * 3;
        const onPaper = x >= 32 && x < 68;
        data[i] = onPaper ? 200 : 180;
        data[i + 1] = onPaper ? 207 : 132;
        data[i + 2] = onPaper ? 214 : 82;
      }
    }
    const box = paperReceiptBoundingBox(data, w, h, 3);
    expect(box).not.toBeNull();
    expect(box!.left).toBeLessThanOrEqual(32);
    expect(box!.left + box!.width).toBeGreaterThanOrEqual(68);
    expect(box!.width).toBeLessThan(60);
  });

  test("Perspektive, Schatten, Falte und gemusterter Untergrund werden konservativ erkannt", () => {
    const w = 120;
    const h = 180;
    const data = new Uint8Array(w * h * 3);
    for (let y = 0; y < h; y++) {
      const progress = y / (h - 1);
      const left = Math.round(32 - progress * 10);
      const right = Math.round(88 + progress * 10);
      for (let x = 0; x < w; x++) {
        const offset = (y * w + x) * 3;
        const onPaper = x >= left && x <= right;
        const folded = Math.abs(y - 92) < 2;
        const shadow = 165 + Math.round(progress * 65);
        data[offset] = onPaper ? (folded ? 125 : shadow) : 115 + ((x + y) % 18);
        data[offset + 1] = onPaper ? (folded ? 125 : shadow) : 72 + ((x * 3 + y) % 14);
        data[offset + 2] = onPaper ? (folded ? 125 : shadow) : 35 + ((x + y * 2) % 11);
      }
    }
    const quad = detectPaperQuadrilateral(data, w, h, 3);
    expect(quad).not.toBeNull();
    expect(quad!.topLeft.x).toBeGreaterThan(quad!.bottomLeft.x);
    expect(quad!.topRight.x).toBeLessThan(quad!.bottomRight.x);
    expect(quad!.confidence).toBeGreaterThanOrEqual(0.72);
  });

  test("unsichere helle Vollfläche wird nicht als Perspektivkorrektur behandelt", () => {
    const data = new Uint8Array(100 * 140 * 3).fill(220);
    expect(detectPaperQuadrilateral(data, 100, 140, 3)).toBeNull();
  });
});

describe("Sprint C — lokale Lesbarkeit", () => {
  test("Unschärfe erhält einen niedrigeren Qualitätswert als klare Druckzeilen", async () => {
    const width = 200;
    const height = 300;
    const raw = Buffer.alloc(width * height, 235);
    for (let y = 12; y < height - 12; y += 18) {
      for (let yy = y; yy < y + 3; yy++) {
        raw.fill(25, yy * width + 18, yy * width + width - 18);
      }
    }
    const crisp = await sharp(raw, { raw: { width, height, channels: 1 } }).jpeg().toBuffer();
    const blurred = await sharp(crisp).blur(4).jpeg().toBuffer();
    expect(await assessReceiptDetailQuality(crisp)).toBeGreaterThan(
      await assessReceiptDetailQuality(blurred)
    );
  });
});

describe("Sprint C — Deskew", () => {
  test("waagerechte Textzeilen → Winkel ~ 0", () => {
    const w = 64;
    const h = 80;
    const data = new Uint8Array(w * h);
    for (let y = 0; y < h; y++) {
      const dark = y % 8 < 3;
      data.fill(dark ? 15 : 230, y * w, y * w + w);
    }
    expect(Math.abs(estimateSkewDegrees(data, w, h))).toBeLessThan(1);
  });
});

describe("Sprint C — Pipeline", () => {
  test("1×1-PNG überlebt Deskew/Crop/Resize", async () => {
    const png = Buffer.from(
      "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
      "base64"
    );
    const result = await processReceiptImage(png, "image/png");
    expect(result.mimeType).toBe("image/jpeg");
    expect(result.width).toBeGreaterThan(0);
    expect(result.height).toBeGreaterThan(0);
  });
});
