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

import { describe, test, expect } from "./runner";
import {
  AI_IMAGE_LONG_EDGE_PX,
  AI_IMAGE_MIN_WIDTH_PX,
  computeReceiptResize,
} from "../../src/lib/image/resize";
import {
  contentBoundingBox,
  estimateSkewDegrees,
  needsContrastBoost,
} from "../../src/lib/image/enhance";
import { 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();
  });
});

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);
  });
});
