import { mkdtemp, readFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";

import { createCanvas } from "@napi-rs/canvas";
import { describe, expect, it, vi } from "vitest";

import {
  buildPdfDiagnosticArtifacts,
  classifyPdfTextLayer,
  renderPdfFileFirstPageToPng,
  trimImageWhitespace,
} from "./pdf-diagnostics.ts";

function makeSparsePdfText() {
  return "嵌缝与螺栓防水构造图\n005A";
}

function makeDensePdfText() {
  return [
    "采购技术规格书",
    "本文件说明塑料保护罩采购要求，包含材质、性能、执行标准、数量和验收要求。",
    "保护罩应采用低烟无卤原生材料制造，燃烧性能不低于 B1 级。",
    "供货时应提供合格证、检测报告和材质证明。",
  ].join("\n");
}

function makeWhitespacePng() {
  const canvas = createCanvas(120, 80);
  const context = canvas.getContext("2d");
  context.fillStyle = "white";
  context.fillRect(0, 0, 120, 80);
  context.fillStyle = "black";
  context.fillRect(44, 24, 24, 16);
  return canvas.toBuffer("image/png");
}

describe("pdf-diagnostics", () => {
  it("classifies sparse text-layer PDFs as CAD drawing PDFs", () => {
    const profile = classifyPdfTextLayer({
      text: makeSparsePdfText(),
      pageCount: 1,
      fileSizeBytes: 16_431_728,
    });

    expect(profile.file_type).toBe("cad_drawing_pdf");
    expect(profile.requires_visual_route).toBe(true);
    expect(profile.diagnostics).toEqual(expect.arrayContaining([
      expect.stringContaining("文本层较少"),
    ]));
  });

  it("classifies dense readable text-layer PDFs as text PDFs", () => {
    const profile = classifyPdfTextLayer({
      text: makeDensePdfText(),
      pageCount: 2,
      fileSizeBytes: 250_000,
    });

    expect(profile.file_type).toBe("text_pdf");
    expect(profile.requires_visual_route).toBe(false);
    expect(profile.text_layer_char_count).toBeGreaterThan(80);
  });

  it("trims whitespace and reports crop metadata for rendered page images", async () => {
    const source = makeWhitespacePng();
    const result = await trimImageWhitespace(source, { padding: 4 });

    expect(result.trimmedBytes.length).toBeLessThan(source.length);
    expect(result.source).toMatchObject({ width: 120, height: 80 });
    expect(result.crop.width).toBeLessThan(120);
    expect(result.crop.height).toBeLessThan(80);
    expect(result.dataUrl).toMatch(/^data:image\/png;base64,/);
  });

  it("falls back when pdfjs rendering fails and reports the fallback renderer", async () => {
    const tempDir = await mkdtemp(path.join(os.tmpdir(), "pdf-diagnostics-"));
    const fallbackBytes = makeWhitespacePng();
    const pdfPath = path.join(tempDir, "sample.pdf");

    const rendered = await renderPdfFileFirstPageToPng(pdfPath, {
      renderWithPdfJs: vi.fn(async () => {
        throw new Error("pdfjs failed");
      }),
      renderWithPoppler: vi.fn(async () => {
        throw new Error("poppler unavailable");
      }),
      renderWithQuickLook: vi.fn(async () => fallbackBytes),
    });

    expect(rendered.renderer).toBe("quicklook");
    expect(rendered.bytes).toEqual(fallbackBytes);
    expect(rendered.dataUrl).toMatch(/^data:image\/png;base64,/);
  });

  it("returns diagnostic artifacts with low/high renders and a trimmed high-res data URL", async () => {
    const tempDir = await mkdtemp(path.join(os.tmpdir(), "pdf-diagnostics-artifacts-"));
    const png = makeWhitespacePng();
    const renderWithPdfJs = vi.fn(async () => png);

    const artifacts = await buildPdfDiagnosticArtifacts({
      pdfPath: path.join(tempDir, "sample.pdf"),
      sourceFileName: "塑料保护罩覆盖.pdf",
      fileSizeBytes: 16_431_728,
      outputDir: tempDir,
      parseTextLayer: async () => ({
        text: makeSparsePdfText(),
        pageCount: 1,
      }),
      renderWithPdfJs,
    });

    expect(artifacts.profile).toMatchObject({
      file_type: "cad_drawing_pdf",
      requires_visual_route: true,
      source_file_name: "塑料保护罩覆盖.pdf",
    });
    expect(renderWithPdfJs).toHaveBeenCalledTimes(2);
    expect(artifacts.renders).toHaveLength(2);
    expect(artifacts.trimmed_high_res.dataUrl).toMatch(/^data:image\/png;base64,/);
    expect(artifacts.trimmed_high_res.output_path).toBeTruthy();

    const written = await readFile(artifacts.trimmed_high_res.output_path);
    expect(written.subarray(0, 8)).toEqual(
      Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
    );
  });
});
