import fs from "fs";
import os from "os";
import path from "path";
import { execa } from "execa";
import { afterEach, describe, expect, it } from "vitest";
import {
  applyToolchainEnv,
  normalizeRuntimeToolchain,
  normalizeToolchainRegistry,
  resolveConfiguredToolchain,
} from "./index.mjs";

const tempDirs = [];

afterEach(() => {
  while (tempDirs.length > 0) {
    fs.rmSync(tempDirs.pop(), { recursive: true, force: true });
  }
});

function makeTempDir(prefix) {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
  tempDirs.push(dir);
  return dir;
}

function writeJson(filePath, value) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
}

describe("toolchains", () => {
  it("normalizes named toolchain profiles and runtime references", () => {
    const registry = normalizeToolchainRegistry({
      frontend: {
        cwd: "frontend",
        install: "download",
        node: "20.19.5",
      },
    });

    expect(normalizeRuntimeToolchain("frontend", "Service \"web\" runtime.toolchain", registry)).toEqual(
      expect.objectContaining({
        kind: "node",
        cwd: "frontend",
        install: "download",
        node: "20.19.5",
        refName: "frontend",
      })
    );
  });

  it("fails clearly when host versions do not satisfy a require-host toolchain", async () => {
    const productDir = makeTempDir("testkit-toolchain-host-");
    writeJson(path.join(productDir, "package.json"), {
      name: "toolchain-host-product",
      engines: {
        node: ">=20.19.5 <21",
        npm: ">=10.8.2 <11",
      },
    });

    const config = {
      name: "frontend",
      productDir,
      testkit: {
        runtime: {
          toolchain: {
            kind: "node",
            cwd: ".",
            detect: "auto",
            install: "require-host",
          },
        },
      },
    };

    await expect(
      resolveConfiguredToolchain(config, {
        hostNodeVersion: "18.19.0",
        hostNpmVersion: "10.8.2",
      })
    ).rejects.toThrow("requires Node >=20.19.5 <21");
  });

  it("provisions a downloaded Node toolchain from a deterministic range minimum", async () => {
    const productDir = makeTempDir("testkit-toolchain-download-");
    writeJson(path.join(productDir, "package.json"), {
      name: "toolchain-download-product",
      engines: {
        node: ">=20.19.5 <21",
        npm: ">=10.8.2 <11",
      },
    });

    const archiveBuffer = await buildFakeNodeArchive();
    const config = {
      name: "frontend",
      productDir,
      testkit: {
        runtime: {
          toolchain: {
            kind: "node",
            cwd: ".",
            detect: "auto",
            install: "download",
          },
        },
      },
    };

    const resolved = await resolveConfiguredToolchain(config, {
      fetchImpl: async () => ({
        ok: true,
        status: 200,
        arrayBuffer: async () => archiveBuffer,
      }),
    });

    expect(resolved).toMatchObject({
      kind: "node",
      install: "download",
      nodeVersion: "20.19.5",
      npmVersion: "10.8.2",
      nodeSource: "package.json#engines.node",
      npmSource: "package.json#engines.npm",
    });
    expect(fs.existsSync(resolved.nodeExecutable)).toBe(true);
    expect(applyToolchainEnv({ PATH: "/usr/bin" }, resolved).PATH).toContain(
      path.dirname(resolved.nodeExecutable)
    );
  });
});

async function buildFakeNodeArchive() {
  const root = makeTempDir("testkit-node-archive-root-");
  const platform = process.platform === "darwin" ? "darwin" : process.platform;
  const arch = process.arch;
  const bundleDir = path.join(root, `node-v20.19.5-${platform}-${arch}`);
  const binDir = path.join(bundleDir, "bin");
  fs.mkdirSync(binDir, { recursive: true });

  fs.writeFileSync(
    path.join(binDir, "node"),
    [
      "#!/usr/bin/env bash",
      'if [[ \"$1\" == *\"template-step-module-runner.mjs\" ]]; then',
      "  shift",
      "  exec node \"$@\"",
      "fi",
      'if [[ \"$1\" == *\"npm-cli.js\" ]]; then',
      "  shift",
      "  exec node \"$@\"",
      "fi",
      "exec node \"$@\"",
    ].join("\n"),
    { mode: 0o755 }
  );
  fs.writeFileSync(
    path.join(binDir, "npm"),
    ['#!/usr/bin/env bash', 'echo "10.8.2"'].join("\n"),
    { mode: 0o755 }
  );

  const archivePath = path.join(root, "node-v20.19.5-linux-x64.tar.gz");
  await execa("tar", ["-czf", archivePath, "-C", root, path.basename(bundleDir)]);
  return fs.readFileSync(archivePath);
}
