import { strict as assert } from "node:assert";
import { mkdtempSync, rmSync, existsSync, readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { test } from "node:test";
import {
  LIBRARY_LUT_OFFLINE_CODE,
  RESOLVABLE_PRESET_IDS,
  freezeLibraryLut,
  matchColorLook,
  readBundledLutIndex,
} from "./lut-preset-provider.mjs";
import { buildCube } from "./cube-build.mjs";
import { validateCube, validateCubeFile } from "./cube-validate.mjs";

const REPO_ROOT = join(import.meta.dirname, "..", "..", "..", "..");

function corePresetIdsFromSource() {
  const src = readFileSync(join(REPO_ROOT, "packages/core/src/colorGrading.ts"), "utf8");
  const match = src.match(/export type HfColorGradingPresetId =([\s\S]*?);/);
  assert.ok(match, "core preset union should be readable");
  return [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
}

test("warm daylight and warm natural light resolve to the core warm-daylight preset", () => {
  assert.deepEqual(matchColorLook("warm daylight"), {
    kind: "preset",
    preset: "warm-daylight",
    score: 2,
  });
  assert.equal(matchColorLook("warm natural light").preset, "warm-daylight");
});

test("high contrast punchy resolves to deep-contrast", () => {
  assert.equal(matchColorLook("high contrast punchy").preset, "deep-contrast");
});

test("removed preset phrases resolve to surviving looks", () => {
  assert.equal(matchColorLook("natural lift").preset, "soft-boost");
  assert.equal(matchColorLook("fresh pop").preset, "bright-pop");
  assert.equal(matchColorLook("warm clean").preset, "warm-daylight");
  assert.equal(matchColorLook("cool clean").preset, "clean-studio");
});

test("complete-filter intent aliases resolve deterministically", () => {
  assert.equal(matchColorLook("analog tape").preset, "vhs-playback");
  assert.equal(matchColorLook("creator video").preset, "creator-camcorder");
});

test("library look freezes a validated cube from params offline (--local-only)", async () => {
  const projectDir = mkdtempSync(join(tmpdir(), "mu-lut-provider-"));
  try {
    const match = matchColorLook("teal orange blockbuster");
    assert.equal(match.kind, "library");
    // localOnly forces the deterministic params path (no network); online, the
    // same look downloads its .cube from the CDN url (via "url").
    const frozen = await freezeLibraryLut(match, { projectDir, type: "grade", localOnly: true });
    assert.match(frozen.localPath, /^\.media\/luts\/grade_001\.cube$/);
    assert.ok(existsSync(join(projectDir, frozen.localPath)));
    assert.equal(validateCubeFile(join(projectDir, frozen.localPath)).ok, true);
    assert.equal(frozen.lut.src, frozen.localPath);
    assert.equal(frozen.metadata.provenance.via, "params-fallback");
  } finally {
    rmSync(projectDir, { recursive: true, force: true });
  }
});

test("every resolver preset exists in packages/core/src/colorGrading.ts", () => {
  const corePresetIds = corePresetIdsFromSource();
  assert.deepEqual(
    RESOLVABLE_PRESET_IDS.filter((id) => !corePresetIds.includes(id)),
    [],
  );
  for (const id of RESOLVABLE_PRESET_IDS) {
    const match = matchColorLook(id);
    assert.equal(match.kind, "preset");
    assert.equal(match.preset, id);
  }
});

test("zero-overlap intent returns no preset or library match", () => {
  assert.equal(matchColorLook("zqxv imaginary neutron look"), null);
});

test("bundled LUT index entries resolve from params or url", () => {
  for (const entry of readBundledLutIndex()) {
    assert.ok(entry.id);
    assert.ok(entry.description);
    assert.ok(entry.params || entry.url, `${entry.id} should define params or url`);
    if (entry.params) {
      assert.equal(typeof entry.params, "object");
      assert.equal(validateCube(buildCube(entry.params)).ok, true, `${entry.id} params validate`);
    }
    if (entry.url) assert.equal(typeof entry.url, "string");
  }
});

test("url library entries respect localOnly and freeze through fetch", async () => {
  const projectDir = mkdtempSync(join(tmpdir(), "mu-lut-url-provider-"));
  const match = {
    kind: "library",
    id: "cdn-look",
    description: "CDN-hosted look",
    tags: ["cdn"],
    intensity: 0.7,
    url: "https://example.invalid/look.cube",
  };
  const originalFetch = globalThis.fetch;
  let fetchCalls = 0;
  try {
    globalThis.fetch = async () => {
      fetchCalls++;
      throw new Error("network should be skipped under localOnly");
    };
    await assert.rejects(
      freezeLibraryLut(match, { projectDir, type: "lut", localOnly: true }),
      (err) => {
        assert.equal(err.code, LIBRARY_LUT_OFFLINE_CODE);
        assert.match(err.message, /--local-only/);
        return true;
      },
    );
    assert.equal(fetchCalls, 0);

    const cube = buildCube({ contrast: 0.1 });
    const body = Buffer.from(cube);
    globalThis.fetch = async (url) => {
      fetchCalls++;
      assert.equal(url, match.url);
      return {
        ok: true,
        headers: { get: () => String(body.length) },
        body: [body],
      };
    };

    const frozen = await freezeLibraryLut(match, { projectDir, type: "lut" });
    assert.equal(fetchCalls, 1);
    assert.match(frozen.localPath, /^\.media\/luts\/lut_001\.cube$/);
    assert.equal(validateCubeFile(join(projectDir, frozen.localPath)).ok, true);
    assert.equal(frozen.metadata.provenance.via, "url");
  } finally {
    globalThis.fetch = originalFetch;
    rmSync(projectDir, { recursive: true, force: true });
  }
});

test("failed URL library freeze releases its reservation", async () => {
  const projectDir = mkdtempSync(join(tmpdir(), "mu-lut-failure-"));
  const originalFetch = globalThis.fetch;
  try {
    globalThis.fetch = async () => ({
      ok: true,
      headers: { get: () => "12" },
      body: [Buffer.from("not a cube\n")],
    });
    await assert.rejects(
      freezeLibraryLut(
        {
          kind: "library",
          id: "broken-cdn-look",
          description: "Broken CDN look",
          tags: ["broken"],
          intensity: 1,
          url: "https://example.com/broken.cube",
        },
        { projectDir, type: "lut" },
      ),
      /failed to freeze library LUT/,
    );
    assert.deepStrictEqual(readdirSync(join(projectDir, ".media/luts")), []);
  } finally {
    globalThis.fetch = originalFetch;
    rmSync(projectDir, { recursive: true, force: true });
  }
});
