import fs from "fs";
import os from "os";
import path from "path";
import { afterEach, describe, expect, it } from "vitest";
import { prepareRuntimeService } from "./runtime-preparation.mjs";
import { resolveRuntimeInstanceConfigs } from "./template.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 writeFile(filePath, contents) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.writeFileSync(filePath, contents);
}

function makeResolvedRuntimeConfig(productDir, prepareConfig) {
  const rawConfig = {
    name: "api",
    productDir,
    testkit: {
      envFiles: [],
      serviceEnv: {},
      runtime: {
        instances: 1,
        maxConcurrentTasks: 1,
        prepare: prepareConfig,
      },
      local: {
        cwd: ".",
        start: "node {prepareDir}/server.mjs",
        port: 3010,
        baseUrl: "http://127.0.0.1:{port}",
        readyUrl: "http://127.0.0.1:{port}/health",
        env: {},
      },
    },
  };

  const runtimeDir = path.join(productDir, ".testkit", "_graphs", "api", "runtimes", "runtime-1");
  fs.mkdirSync(runtimeDir, { recursive: true });
  return resolveRuntimeInstanceConfigs([rawConfig], "runtime-1", runtimeDir, {
    graphDirName: "api",
    portNamespaceIndex: 0,
    portNamespaceStride: 1,
  })[0];
}

describe("runtime preparation", () => {
  it("runs prepare steps once and reuses the manifest until inputs change", async () => {
    const productDir = makeTempDir("testkit-runtime-prepare-");
    writeFile(path.join(productDir, "src", "message.txt"), "hello one\n");
    writeFile(
      path.join(productDir, "scripts", "prepare.mjs"),
      [
        'import fs from "fs";',
        'import path from "path";',
        'const prepareDir = process.argv[2];',
        'const productDir = process.cwd();',
        'const message = fs.readFileSync(path.join(productDir, "src", "message.txt"), "utf8").trim();',
        'const counterDir = path.join(productDir, ".runtime", "prepared");',
        'const counterFile = path.join(counterDir, "build-count.txt");',
        'const current = fs.existsSync(counterFile) ? Number.parseInt(fs.readFileSync(counterFile, "utf8"), 10) || 0 : 0;',
        'fs.mkdirSync(counterDir, { recursive: true });',
        'fs.mkdirSync(prepareDir, { recursive: true });',
        'fs.writeFileSync(counterFile, `${current + 1}\\n`);',
        'fs.writeFileSync(path.join(prepareDir, "message.txt"), `${message}\\n`);',
      ].join("\n")
    );

    const config = makeResolvedRuntimeConfig(productDir, {
      inputs: ["src/message.txt"],
      steps: [
        {
          kind: "command",
          cmd: "node scripts/prepare.mjs {prepareDir}",
        },
      ],
    });

    await prepareRuntimeService(config);
    expect(
      fs.readFileSync(path.join(productDir, ".runtime", "prepared", "build-count.txt"), "utf8").trim()
    ).toBe("1");
    expect(fs.readFileSync(path.join(config.testkit.prepareDir, "message.txt"), "utf8").trim()).toBe(
      "hello one"
    );

    await prepareRuntimeService(config);
    expect(
      fs.readFileSync(path.join(productDir, ".runtime", "prepared", "build-count.txt"), "utf8").trim()
    ).toBe("1");

    fs.writeFileSync(path.join(productDir, "src", "message.txt"), "hello two\n");
    await prepareRuntimeService(config);
    expect(
      fs.readFileSync(path.join(productDir, ".runtime", "prepared", "build-count.txt"), "utf8").trim()
    ).toBe("2");
    expect(fs.readFileSync(path.join(config.testkit.prepareDir, "message.txt"), "utf8").trim()).toBe(
      "hello two"
    );
  });

  it("cleans incomplete prepared output when a prepare step fails", async () => {
    const productDir = makeTempDir("testkit-runtime-prepare-fail-");
    writeFile(
      path.join(productDir, "scripts", "prepare-fail.mjs"),
      [
        'import fs from "fs";',
        'import path from "path";',
        'const prepareDir = process.argv[2];',
        'fs.mkdirSync(prepareDir, { recursive: true });',
        'fs.writeFileSync(path.join(prepareDir, "partial.txt"), "partial\\n");',
        'throw new Error("prepare failed");',
      ].join("\n")
    );

    const config = makeResolvedRuntimeConfig(productDir, {
      steps: [
        {
          kind: "command",
          cmd: "node scripts/prepare-fail.mjs {prepareDir}",
        },
      ],
    });

    await expect(prepareRuntimeService(config)).rejects.toThrow("Command failed with exit code 1");
    expect(fs.existsSync(config.testkit.prepareDir)).toBe(false);
    expect(fs.existsSync(path.join(productDir, ".testkit", "_graphs", "api", "runtimes", "runtime-1", "services", "api", "prepared", "prepare-manifest.json"))).toBe(false);
  });
});
