import { afterEach, beforeEach, expect, test } from "bun:test";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

let directory: string;
const project = new URL("../", import.meta.url);
beforeEach(() => {
  directory = mkdtempSync(join(tmpdir(), "minabot-env-test-"));
  // Run the real package commands with disposable entrypoints that report only fixture values.
  writeFileSync(join(directory, "package.json"), readFileSync(new URL("package.json", project)));
  writeFileSync(join(directory, "bunfig.toml"), readFileSync(new URL("bunfig.toml", project)));
  mkdirSync(join(directory, "scripts"));
  mkdirSync(join(directory, "src"));
  for (const [file, stage] of [["scripts/migrate.ts", "migration"], ["scripts/start.ts", "server"], ["src/index.ts", "server"]]) {
    writeFileSync(join(directory, file!), `console.log(JSON.stringify({ stage: ${JSON.stringify(stage)}, mode: process.env.NODE_ENV, origin: process.env.APP_ORIGIN, database: process.env.DATABASE_PATH, shared: process.env.MINABOT_ENV_TEST_SHARED })); process.exit(0);`);
  }
  writeFileSync(join(directory, ".env"), "APP_ORIGIN=https://shared.example\nDATABASE_PATH=shared.sqlite\nMINABOT_ENV_TEST_SHARED=shared\n");
  writeFileSync(join(directory, ".env.development"), "APP_ORIGIN=http://127.0.0.1:4000\nDATABASE_PATH=development.sqlite\n");
  writeFileSync(join(directory, ".env.production"), "APP_ORIGIN=https://production.example\nDATABASE_PATH=production.sqlite\n");
});
afterEach(() => rmSync(directory, { recursive: true, force: true }));

function run(command: string, overrides: Record<string, string> = {}) {
  const env = { ...process.env };
  for (const key of ["NODE_ENV", "APP_ORIGIN", "DATABASE_PATH", "MINABOT_ENV_TEST_SHARED", "BUN_OPTIONS"]) delete env[key];
  const result = Bun.spawnSync([process.execPath, "run", command], {
    cwd: directory, env: { ...env, ...overrides }, timeout: 10_000,
    stdout: "pipe", stderr: "pipe",
  });
  expect(result.exitCode).toBe(0);
  return result.stdout.toString().trim().split("\n").map(line => JSON.parse(line));
}

test.each(["development", "production"])("%s loads the same environment before migration and server startup", mode => {
  const records = run(mode === "production" ? "start" : "dev");
  expect(records).toEqual(["migration", "server"].map(stage => ({
    stage, mode,
    origin: mode === "production" ? "https://production.example" : "http://127.0.0.1:4000",
    database: `${mode}.sqlite`, shared: "shared",
  })));
});

test("database commands select their corresponding environment", () => {
  expect(run("db:migrate")[0].database).toBe("development.sqlite");
  expect(run("db:migrate:production")[0].database).toBe("production.sqlite");
});

test("exported deployment variables override environment files", () => {
  const records = run("start", { APP_ORIGIN: "https://deployed.example", DATABASE_PATH: "deployed.sqlite" });
  expect(records.every(record => record.origin === "https://deployed.example" && record.database === "deployed.sqlite")).toBe(true);
});

test("production never preloads development values when its file is absent", () => {
  rmSync(join(directory, ".env.production"));
  const records = run("start");
  expect(records.every(record => record.origin === "https://shared.example" && record.database === "shared.sqlite")).toBe(true);
});
