import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { resolveNodeBinPath } from "@/utils/node-version.js";

/**
 * resolveNodeBinPath reads .nvmrc (then package.json engines) and resolves
 * against the NVM versions directory. We create a fake NVM tree so the
 * function can match installed versions and find the bin/ directory.
 *
 * NOTE: The source does NOT read `.node-version` — only `.nvmrc` and
 * `package.json` `engines.node`.
 */
describe("resolveNodeBinPath", () => {
  let repoPath: string;
  let nvmDir: string;
  let originalNvmDir: string | undefined;

  beforeEach(() => {
    repoPath = mkdtempSync(join(tmpdir(), "hu-node-repo-"));
    nvmDir = mkdtempSync(join(tmpdir(), "hu-fake-nvm-"));
    originalNvmDir = process.env.NVM_DIR;
    process.env.NVM_DIR = nvmDir;
  });

  afterEach(() => {
    rmSync(repoPath, { recursive: true, force: true });
    rmSync(nvmDir, { recursive: true, force: true });
    if (originalNvmDir === undefined) {
      delete process.env.NVM_DIR;
    } else {
      process.env.NVM_DIR = originalNvmDir;
    }
  });

  function installFakeNode(version: string): void {
    const binDir = join(nvmDir, "versions", "node", `v${version}`, "bin");
    mkdirSync(binDir, { recursive: true });
  }

  test("returns null when no .nvmrc and no package.json", () => {
    installFakeNode("20.10.0");
    expect(resolveNodeBinPath(repoPath)).toBeNull();
  });

  test(".nvmrc with leading v (v20.10.0) resolves to matching bin path", () => {
    installFakeNode("20.10.0");
    writeFileSync(join(repoPath, ".nvmrc"), "v20.10.0\n");

    const result = resolveNodeBinPath(repoPath);
    expect(result).not.toBeNull();
    expect(result!.requested).toBe("20.10.0");
    expect(result!.resolved).toBe("20.10.0");
    expect(result!.binPath).toBe(join(nvmDir, "versions", "node", "v20.10.0", "bin"));
  });

  test(".nvmrc without leading v (20.10.0) resolves", () => {
    installFakeNode("20.10.0");
    writeFileSync(join(repoPath, ".nvmrc"), "20.10.0\n");

    const result = resolveNodeBinPath(repoPath);
    expect(result).not.toBeNull();
    expect(result!.requested).toBe("20.10.0");
    expect(result!.resolved).toBe("20.10.0");
  });

  test("package.json engines.node is used when no .nvmrc exists", () => {
    installFakeNode("18.17.1");
    writeFileSync(
      join(repoPath, "package.json"),
      JSON.stringify({ engines: { node: ">=18.17.1" } }),
    );

    const result = resolveNodeBinPath(repoPath);
    expect(result).not.toBeNull();
    expect(result!.requested).toBe("18.17.1");
    expect(result!.resolved).toBe("18.17.1");
  });

  test(".nvmrc takes precedence over package.json engines", () => {
    installFakeNode("20.10.0");
    installFakeNode("18.17.1");
    writeFileSync(join(repoPath, ".nvmrc"), "20.10.0\n");
    writeFileSync(
      join(repoPath, "package.json"),
      JSON.stringify({ engines: { node: ">=18.17.1" } }),
    );

    const result = resolveNodeBinPath(repoPath);
    expect(result).not.toBeNull();
    expect(result!.requested).toBe("20.10.0");
  });

  test("falls back to major-version match when exact version not installed", () => {
    installFakeNode("20.11.0");
    writeFileSync(join(repoPath, ".nvmrc"), "20.10.0\n");

    const result = resolveNodeBinPath(repoPath);
    expect(result).not.toBeNull();
    expect(result!.requested).toBe("20.10.0");
    expect(result!.resolved).toBe("20.11.0");
  });

  test("lts/* returns null (parseVersion cannot parse it)", () => {
    installFakeNode("20.10.0");
    writeFileSync(join(repoPath, ".nvmrc"), "lts/*\n");

    expect(resolveNodeBinPath(repoPath)).toBeNull();
  });

  test("empty .nvmrc returns null", () => {
    installFakeNode("20.10.0");
    writeFileSync(join(repoPath, ".nvmrc"), "  \n");

    expect(resolveNodeBinPath(repoPath)).toBeNull();
  });

  test("non-existent repoPath returns null (no throw)", () => {
    installFakeNode("20.10.0");
    const bogus = join(tmpdir(), "hu-does-not-exist-" + Date.now());
    expect(resolveNodeBinPath(bogus)).toBeNull();
  });

  test("returns null when NVM versions directory does not exist", () => {
    // nvmDir exists but has no versions/node/ subdirectory
    writeFileSync(join(repoPath, ".nvmrc"), "20.10.0\n");
    expect(resolveNodeBinPath(repoPath)).toBeNull();
  });
});
