import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { CollectorState } from "../src/state";
import { Journal } from "../src/journal";
import { startServer, type CollectorServer } from "../src/server";
import { readProviderAccounts, validateRoutingRules } from "../src/routing-config";

const TOKEN = "routing-test-token";
let dir: string;
let server: CollectorServer;

const validRules = {
  version: "routing/v2",
  projects: { overdeck: "work" },
  default: "personal",
  fallback_chain: ["work"],
  fallback_trigger: "broken_or_quota_exhausted",
  missing_health_is_available: true,
  quota_exhausted_threshold_pct: 95,
  account_caps: { work: { "5h": 80 } },
} as const;

beforeEach(() => {
  dir = mkdtempSync(join(tmpdir(), "overdeck-routing-config-"));
  writeFileSync(join(dir, "accounts.json"), JSON.stringify({ accounts: [
    { slug: "personal", alias: "Personal" }, { slug: "work", alias: "Work" },
  ], deleted_legacy_slugs: [] }));
  writeFileSync(join(dir, "claude_accounts.json"), JSON.stringify({ accounts: [
    { slug: "claude-main", alias: "Claude main" },
  ], deleted_legacy_slugs: [] }));
  writeFileSync(join(dir, "routing_rules.json"), JSON.stringify(validRules));
  writeFileSync(join(dir, "claude_routing_rules.json"), JSON.stringify({ ...validRules, projects: {}, default: "claude-main", fallback_chain: [], account_caps: {} }));
  const state = new CollectorState(new Journal(join(dir, "items.jsonl")));
  server = startServer({ host: "127.0.0.1", port: 0, token: TOKEN, state, routingRuntimeDir: dir });
});

afterEach(() => {
  server.stop(true);
  rmSync(dir, { recursive: true, force: true });
});

function request(path: string, init?: RequestInit) {
  return fetch(`http://127.0.0.1:${server.port}${path}`, {
    ...init,
    headers: { authorization: `Bearer ${TOKEN}`, "content-type": "application/json", ...init?.headers },
  });
}

describe("routing config endpoints", () => {
  test("reads rules with the provider account registry", async () => {
    const response = await request("/config/routing/codex");
    expect(response.status).toBe(200);
    expect(await response.json()).toEqual({ provider: "codex", accounts: ["personal", "work"], rules: validRules });
  });

  test("rejects an unknown account slug without changing the file", async () => {
    const before = readFileSync(join(dir, "routing_rules.json"), "utf8");
    const response = await request("/config/routing/codex", {
      method: "POST", body: JSON.stringify({ ...validRules, default: "unknown" }),
    });
    expect(response.status).toBe(400);
    expect(await response.text()).toContain("unknown codex account slug");
    expect(readFileSync(join(dir, "routing_rules.json"), "utf8")).toBe(before);
  });

  test("rejects malformed and extra fields without changing the file", async () => {
    const before = readFileSync(join(dir, "routing_rules.json"), "utf8");
    const response = await request("/config/routing/codex", {
      method: "POST", body: JSON.stringify({ ...validRules, fallback_chain: "work", hidden: true }),
    });
    expect(response.status).toBe(400);
    expect(readFileSync(join(dir, "routing_rules.json"), "utf8")).toBe(before);
  });

  test("atomically replaces the complete routing file", async () => {
    const updated = { ...validRules, projects: { collector: "personal" }, quota_exhausted_threshold_pct: 90 };
    const response = await request("/config/routing/codex", { method: "POST", body: JSON.stringify(updated) });
    expect(response.status).toBe(200);
    expect(JSON.parse(readFileSync(join(dir, "routing_rules.json"), "utf8"))).toEqual(updated);
    expect(readdirSync(dir).filter((name) => name.endsWith(".tmp"))).toEqual([]);
  });

  test("rejects noncanonical account slugs", async () => {
    writeFileSync(join(dir, "accounts.json"), JSON.stringify({
      accounts: [{ slug: ".*", alias: "Injected" }],
      deleted_legacy_slugs: [],
    }));
    await expect(readProviderAccounts(dir, "codex")).rejects.toThrow();
    await expect(validateRoutingRules(dir, "codex", validRules)).rejects.toThrow();
  });

  test("rejects invalid alias min-length registry for both routing and incidents parsers", async () => {
    writeFileSync(join(dir, "accounts.json"), JSON.stringify({
      accounts: [
        { slug: "personal", alias: "" },
        { slug: "work", alias: "Work" },
      ],
      deleted_legacy_slugs: [],
    }));
    await expect(readProviderAccounts(dir, "codex")).rejects.toThrow();
    await expect(validateRoutingRules(dir, "codex", validRules)).rejects.toThrow();
    const before = readFileSync(join(dir, "routing_rules.json"), "utf8");
    const response = await request("/config/routing/codex", {
      method: "POST", body: JSON.stringify(validRules),
    });
    expect(response.status).toBe(400);
    expect(readFileSync(join(dir, "routing_rules.json"), "utf8")).toBe(before);
  });
});
