import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { Database } from "bun:sqlite";
import { createHash } from "node:crypto";
import type { Server } from "bun";
import { mkdtempSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import {
  createControllerRuntime,
  handleJobReport,
  handleSpineConfigReport,
  startServer,
} from "./server";
import { CapabilityService } from "./capability";
import { ControllerStatusSchema } from "./status";
import { ControllerStore } from "./store";
import {
  EvidenceTrustSchema,
  signEvidenceForTest,
  type EvidencePayload,
} from "./evidence";

const TOKEN = "test-token-abc123";
const PORT = 28787;

let server: Server<undefined>;
let store: ControllerStore;
let dir: string;
let dbPath: string;

function url(path: string): string {
  return `http://127.0.0.1:${PORT}${path}`;
}

function authHeaders(): Record<string, string> {
  return { authorization: `Bearer ${TOKEN}` };
}

describe.serial("HTTP API", () => {
  beforeAll(() => {
    dir = mkdtempSync(join(tmpdir(), "controller-server-"));
    dbPath = join(dir, "state.sqlite");
    store = new ControllerStore(dbPath);
    store.upsertHost({ hostname: "debian1", state: "available", slotsTotal: 4 });
    server = startServer({ host: "127.0.0.1", port: PORT, token: TOKEN, store });
  });

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

describe("health", () => {
  test("GET /health returns 401 without auth", async () => {
    const res = await fetch(url("/health"));
    expect(res.status).toBe(401);
  });

  test("GET /health returns ok with valid bearer", async () => {
    const res = await fetch(url("/health"), { headers: authHeaders() });
    expect(res.status).toBe(200);
    const body = (await res.json()) as { ok: boolean };
    expect(body).toEqual({ ok: true });
  });
});

describe("delivery evidence admission", () => {
  test("valid generic bearer cannot admit caller-supplied evidence", async () => {
    const revision = store.getRevision();
    const res = await fetch(url("/delivery/evidence"), {
      method: "POST",
      headers: { ...authHeaders(), "content-type": "application/json" },
      body: JSON.stringify({
        deployment: { deploymentId: "forged-deployment" },
        receipts: [{ receiptId: "forged-receipt" }],
      }),
    });

    expect(res.status).toBe(403);
    expect(await res.json()).toEqual({
      error: "delivery-evidence-admission-unavailable",
    });
    expect(store.getRevision()).toBe(revision);
    expect(store.getDeliveryDeployment("forged-deployment")).toBeNull();
    expect(store.getDeliveryFeatureReceipt("forged-receipt")).toBeNull();
  });
});

describe("config health", () => {
  test("corrupt config with override records exactly one incident", async () => {
    const corruptDbPath = join(dir, "corrupt-config.sqlite");
    const runtime = await createControllerRuntime({
      dbPath: corruptDbPath,
      configHealth: {
        config: { bindHost: "127.0.0.1", port: 8787, enabled: true, notify: { mode: "both" } },
        degraded: true,
        incident: {
          kind: "config-invalid-toml",
          detail: "corrupt; BUILD_REMOTE_LOCAL_FALLBACK=1 honored",
        },
        legitimatelyDisabled: false,
        localFallbackOverride: true,
      },
    });
    const db = new Database(corruptDbPath, { readonly: true });
    const row = db
      .query<{ count: number }, []>("SELECT COUNT(*) AS count FROM incidents")
      .get();
    expect(row?.count).toBe(1);
    db.close();
    runtime.store.close();
  });
});

describe("status auth", () => {
  test("GET /status without Authorization returns 401", async () => {
    const res = await fetch(url("/status"));
    expect(res.status).toBe(401);
  });

  test("GET /status with wrong bearer token returns 401", async () => {
    const res = await fetch(url("/status"), {
      headers: { authorization: "Bearer wrong-token" },
    });
    expect(res.status).toBe(401);
  });

  test("GET /status with valid bearer returns schema-valid ControllerStatus", async () => {
    const res = await fetch(url("/status"), {
      headers: authHeaders(),
    });
    expect(res.status).toBe(200);

    const body = await res.json();
    const parsed = ControllerStatusSchema.parse(body);
    expect(parsed.hosts.debian1?.state).toBe("available");
    expect(parsed.jobs).toEqual([]);
    expect(parsed.lease.active).toBe(false);
  });
});

describe("transition API", () => {
  test("POST /transition/box-drain commits and bumps revision", async () => {
    const statusRes = await fetch(url("/status"), { headers: authHeaders() });
    const status = (await statusRes.json()) as { revision: number };
    const res = await fetch(url("/transition/box-drain"), {
      method: "POST",
      headers: { ...authHeaders(), "content-type": "application/json" },
      body: JSON.stringify({
        expectedRevision: String(status.revision),
        idempotencyKey: "server-drain-1",
        args: { host: "debian1" },
      }),
    });
    expect(res.status).toBe(200);
    const body = (await res.json()) as { revision: number; result: { to: string } };
    expect(body.revision).toBe(status.revision + 1);
    expect(body.result.to).toBe("draining");

    const after = await fetch(url("/status"), { headers: authHeaders() });
    const parsed = ControllerStatusSchema.parse(await after.json());
    expect(parsed.hosts.debian1?.state).toBe("draining");
  });

  test("stale revision returns 409", async () => {
    const res = await fetch(url("/transition/box-drain"), {
      method: "POST",
      headers: { ...authHeaders(), "content-type": "application/json" },
      body: JSON.stringify({
        expectedRevision: 0,
        idempotencyKey: "server-stale-1",
        args: { host: "debian1" },
      }),
    });
    expect(res.status).toBe(409);
    const body = (await res.json()) as { error: string; currentRevision: number };
    expect(body.error).toBe("stale-revision");
    expect(body.currentRevision).toBeGreaterThan(0);
  });

  test("invalid args return 422", async () => {
    const statusRes = await fetch(url("/status"), { headers: authHeaders() });
    const status = (await statusRes.json()) as { revision: number };
    const res = await fetch(url("/transition/box-drain"), {
      method: "POST",
      headers: { ...authHeaders(), "content-type": "application/json" },
      body: JSON.stringify({
        expectedRevision: status.revision,
        idempotencyKey: "server-bad-args",
        args: {},
      }),
    });
    expect(res.status).toBe(422);
    const body = (await res.json()) as { error: string };
    expect(body.error).toBe("invalid-args");
  });
});
});

function reportRequest(body: unknown): Request {
  return new Request("http://controller/jobs/report", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body),
  });
}

describe("report ingest", () => {
  test("derives identity, enrolls observed host, replays, and advances attempts", async () => {
    const reportDir = mkdtempSync(join(tmpdir(), "controller-report-"));
    const store = new ControllerStore(join(reportDir, "state.sqlite"));
    const capability = new CapabilityService(store);
    const started = {
      source: "remote-build", host: "new-box", key: "key", mirror: "mirror",
      repo: "owner/repo", snapshot: "42", argv: ["/usr/bin/bun", "test"],
      attempt: 1, stage: "started", startedAt: "2026-07-20T00:00:00.000Z",
      timeoutSec: 30,
    } as const;
    const id = createHash("sha256").update("keymirror", "utf8").digest("hex");

    const first = await handleJobReport(reportRequest(started), store, capability);
    expect(first.status).toBe(200);
    expect(await first.json()).toMatchObject({ ok: true, id });
    expect(store.getHost("new-box")).toEqual({
      hostname: "new-box", state: "maintenance", role: "builder", slotsTotal: 4,
      slotsUsed: 0, runningJobs: 0, ciJobsRunning: 0, healthStorage: true,
      healthRunner: true, healthOffload: true, capabilityOk: false, primary: false,
      capabilityReason: null, capabilityCheckedAt: null,
      enrolling: true, dispatchPaused: false, quarantinedCommands: [],
    });
    expect(store.hostEligible("new-box", "bun")).toEqual({
      eligible: false, reason: "host-not-available",
    });
    const revision = store.getRevision();
    const duplicate = await handleJobReport(reportRequest(started), store, capability);
    expect(duplicate.status).toBe(200);
    expect(store.getRevision()).toBe(revision);

    const finished = { ...started, stage: "finished", rc: 0, finishedAt: "2026-07-20T00:00:05.000Z" } as const;
    expect((await handleJobReport(reportRequest(finished), store, capability)).status).toBe(200);
    expect(store.getJob(id)).toMatchObject({ stage: "succeeded", attempt: 1, infraFailure: false });
    expect((await handleJobReport(reportRequest(started), store, capability)).status).toBe(200);
    expect(store.getJob(id)).toMatchObject({ stage: "running", attempt: 2, rc: null });
    expect(store.getJob(id)?.finishedAt).toBeUndefined();
    store.close();
    rmSync(reportDir, { recursive: true, force: true });
  });

  test("conflicts preserve job and append one event", async () => {
    const reportDir = mkdtempSync(join(tmpdir(), "controller-conflict-"));
    const store = new ControllerStore(join(reportDir, "state.sqlite"));
    const capability = new CapabilityService(store);
    const started = {
      source: "remote-build", host: "box", key: "key", mirror: "mirror",
      repo: "owner/repo", snapshot: "42", argv: ["bun"], attempt: 1,
      stage: "started", startedAt: "2026-07-20T00:00:00.000Z", timeoutSec: 30,
    } as const;
    await handleJobReport(reportRequest(started), store, capability);
    const before = store.getJob(createHash("sha256").update("keymirror").digest("hex"));
    const response = await handleJobReport(reportRequest({
      ...started, startedAt: "2026-07-20T00:00:01.000Z",
    }), store, capability);
    expect(response.status).toBe(409);
    expect(store.getJob(before!.id)).toEqual(before);
    expect(store.readEvents().filter(({ event }) => event.stage === "report-conflict")).toHaveLength(1);
    store.close();
    rmSync(reportDir, { recursive: true, force: true });
  });

  test("raw 126/127 reports never blame argv[0] without executor evidence", async () => {
    const reportDir = mkdtempSync(join(tmpdir(), "controller-breaker-report-"));
    const store = new ControllerStore(join(reportDir, "state.sqlite"));
    store.upsertHost({ hostname: "box", state: "available", capabilityOk: true });
    const capability = new CapabilityService(store);
    const finished = (key: string, rc: number) => ({
      source: "remote-build", host: "box", key, mirror: "mirror", repo: "owner/repo",
      snapshot: key, argv: ["/usr/bin/playwright"], attempt: 1, stage: "finished",
      rc, startedAt: "2026-07-20T00:00:00.000Z",
      finishedAt: "2026-07-20T00:00:01.000Z", timeoutSec: 30,
    });
    await handleJobReport(reportRequest(finished("one", 126)), store, capability);
    await handleJobReport(reportRequest(finished("two", 127)), store, capability);
    expect(store.hostEligible("box", "playwright")).toEqual({ eligible: true, reason: "eligible" });

    await handleJobReport(reportRequest({
      ...finished("three", 127),
      missingCommand: "playwright",
    }), store, capability);
    await handleJobReport(reportRequest({
      ...finished("four", 127),
      missingCommand: "playwright",
    }), store, capability);
    expect(store.hostEligible("box", "playwright")).toEqual({
      eligible: false, reason: "command-quarantined",
    });
    expect(store.hostEligible("box", "bun")).toEqual({ eligible: true, reason: "eligible" });
    expect(store.getHost("box")?.capabilityOk).toBe(true);
    capability.halfOpen("box", "playwright");
    expect(store.hostEligible("box", "playwright")).toEqual({ eligible: true, reason: "eligible" });
    await handleJobReport(reportRequest(finished("five", 0)), store, capability);
    expect(store.getCapabilityBreaker("box", "playwright")?.state).toBe("closed");
    expect(store.readEvents().map(({ event }) => event.reason)).toContain("capability-restored");
    store.close();
    rmSync(reportDir, { recursive: true, force: true });
  });

  test("rejects untrusted identity, unsafe mirrors, empty commands, and relative config paths", async () => {
    const reportDir = mkdtempSync(join(tmpdir(), "controller-invalid-report-"));
    const store = new ControllerStore(join(reportDir, "state.sqlite"));
    const capability = new CapabilityService(store);
    const base = {
      source: "remote-build", host: "box", key: "key", mirror: "mirror",
      repo: "owner/repo", snapshot: "1", argv: ["bun"], attempt: 1,
      stage: "started", startedAt: "2026-07-20T00:00:00.000Z", timeoutSec: 30,
    };
    for (const body of [
      { ...base, id: "caller-id" },
      { ...base, mirror: "" },
      { ...base, mirror: "a/b" },
      { ...base, mirror: "a\\b" },
      { ...base, mirror: "x".repeat(256) },
      { ...base, argv: [""] },
    ]) {
      expect((await handleJobReport(reportRequest(body), store, capability)).status).toBe(400);
    }
    expect((await handleSpineConfigReport(reportRequest({
      source: "remote-build", stage: "missing", configPath: "relative.json",
      observedAt: "2026-07-20T00:00:00.000Z",
    }), store)).status).toBe(400);
    expect(store.listHosts()).toEqual([]);
    expect(store.listJobs()).toEqual([]);
    store.close();
    rmSync(reportDir, { recursive: true, force: true });
  });

  test("event failure rolls back whole job report", async () => {
    const reportDir = mkdtempSync(join(tmpdir(), "controller-rollback-report-"));
    const db = join(reportDir, "state.sqlite");
    const store = new ControllerStore(db, { eventWriteFails: true });
    const capability = new CapabilityService(store);
    const response = await handleJobReport(reportRequest({
      source: "remote-build", host: "box", key: "key", mirror: "mirror",
      repo: "owner/repo", snapshot: "1", argv: ["bun"], attempt: 1,
      stage: "started", startedAt: "2026-07-20T00:00:00.000Z", timeoutSec: 30,
    }), store, capability);
    expect(response.status).toBe(500);
    expect(await response.json()).toEqual({ error: "report-write-failed" });
    expect(store.listHosts()).toEqual([]);
    expect(store.listJobs()).toEqual([]);
    expect(store.getRevision()).toBe(0);
    store.close();
    rmSync(reportDir, { recursive: true, force: true });
  });

  test("config reports enforce replay and incident sentinel ownership", async () => {
    const reportDir = mkdtempSync(join(tmpdir(), "controller-config-report-"));
    const store = new ControllerStore(join(reportDir, "state.sqlite"));
    const invalid = {
      source: "remote-build", stage: "invalid", kind: "config-invalid-json",
      detail: "bad", configPath: "/etc/build.json", preservedPath: "/tmp/build.bad",
      observedAt: "2026-07-20T00:00:00.000Z", override: true,
    } as const;
    const first = await handleSpineConfigReport(reportRequest(invalid), store);
    expect(first.status).toBe(200);
    expect(store.getIncident("spine-config:/etc/build.json")).toMatchObject({ state: "open", count: 1 });
    expect(store.getMetaSnapshot()).toMatchObject({
      desired: "degraded", observed: "degraded", dispatchState: "paused",
      dispatchDetail: "spine-config-invalid:/etc/build.json", reconcilerHealthy: false,
    });
    const revision = store.getRevision();
    await handleSpineConfigReport(reportRequest(invalid), store);
    expect(store.getRevision()).toBe(revision);
    const valid = {
      source: "remote-build", stage: "valid", configPath: "/etc/build.json",
      observedAt: "2026-07-20T00:01:00.000Z", disabled: false, override: false,
    } as const;
    await handleSpineConfigReport(reportRequest(valid), store);
    expect(store.getIncident("spine-config:/etc/build.json")?.state).toBe("resolved");
    expect(store.getMetaSnapshot()).toMatchObject({
      desired: "available", observed: "available", dispatchState: "healthy",
      dispatchDetail: null, reconcilerHealthy: true,
    });
    const missing = {
      source: "remote-build", stage: "missing", configPath: "/etc/missing.json",
      observedAt: "2026-07-20T00:02:00.000Z",
    } as const;
    await handleSpineConfigReport(reportRequest(missing), store);
    expect(store.getIncident("spine-config:/etc/missing.json")).toBeNull();
    store.close();
    rmSync(reportDir, { recursive: true, force: true });
  });
});
async function signedEvidenceFixture() {
  const pair = await crypto.subtle.generateKey(
    "Ed25519",
    true,
    ["sign", "verify"],
  ) as CryptoKeyPair;
  const publicKey = Buffer.from(
    await crypto.subtle.exportKey("raw", pair.publicKey),
  ).toString("base64url");
  const trust = EvidenceTrustSchema.parse({
    authorityToken: "dedicated-evidence-token",
    keys: [{
      keyId: "key-1",
      publicKey,
      issuer: "issuer",
      audience: "controller",
      repository: "overdeck",
      workflow: "deploy",
    }],
  });
  const observedAt = new Date(Date.now() - 1_000).toISOString();
  const payload: EvidencePayload = {
    attestationId: "attestation-1",
    keyId: "key-1",
    issuer: "issuer",
    audience: "controller",
    repository: "overdeck",
    workflow: "deploy",
    featureId: "dark-delivery",
    definitionDigest: "d".repeat(64),
    targetId: "controller",
    candidateSha: "a".repeat(40),
    candidateTree: "b".repeat(40),
    deployedSha: "a".repeat(40),
    deployedTree: "b".repeat(40),
    artifactDigest: "c".repeat(64),
    result: "PASSED",
    issuedAt: new Date(Date.now() - 5_000).toISOString(),
    expiresAt: new Date(Date.now() + 60_000).toISOString(),
    deployment: {
      deploymentId: "deployment-1",
      status: "ACTIVE",
      observedAt,
      evidenceReference: "deployment-evidence",
    },
    receipts: [{
      receiptId: "acceptance-1",
      kind: "ACCEPTANCE",
      checkId: "acceptance",
      result: "PASSED",
      observedAt,
      evidenceReference: "acceptance-evidence",
    }, {
      receiptId: "smoke-1",
      kind: "SMOKE",
      checkId: "smoke",
      result: "PASSED",
      observedAt,
      evidenceReference: "smoke-evidence",
    }],
  };
  return {
    trust,
    envelope: {
      payload,
      signature: await signEvidenceForTest(payload, pair.privateKey),
    },
  };
}

describe("configured delivery evidence admission", () => {
  test("requires a dedicated authority distinct from the generic bearer", async () => {
    const { trust, envelope } = await signedEvidenceFixture();
    const localStore = new ControllerStore(":memory:");
    const local = startServer({
      host: "127.0.0.1",
      port: 0,
      token: TOKEN,
      store: localStore,
      evidenceTrust: trust,
    });
    try {
      for (const authority of [undefined, "wrong-token"]) {
        const headers: Record<string, string> = {
          authorization: `Bearer ${TOKEN}`,
          "content-type": "application/json",
        };
        if (authority) headers["x-delivery-evidence-authority"] = authority;
        const response = await fetch(
          `http://127.0.0.1:${local.port}/delivery/evidence`,
          { method: "POST", headers, body: JSON.stringify(envelope) },
        );
        expect(response.status).toBe(403);
        expect(await response.json()).toEqual({
          error: "delivery-evidence-admission-unavailable",
        });
      }
      expect(localStore.getDeliveryDeployment("deployment-1")).toBeNull();
    } finally {
      local.stop(true);
      localStore.close();
    }
  });

  test("refuses a dedicated token equal to the generic bearer", async () => {
    const { trust } = await signedEvidenceFixture();
    const local = startServer({
      host: "127.0.0.1",
      port: 0,
      token: TOKEN,
      evidenceTrust: { ...trust, authorityToken: TOKEN },
    });
    try {
      const response = await fetch(
        `http://127.0.0.1:${local.port}/delivery/evidence`,
        {
          method: "POST",
          headers: {
            authorization: `Bearer ${TOKEN}`,
            "x-delivery-evidence-authority": TOKEN,
            "content-type": "application/json",
          },
          body: "{}",
        },
      );
      expect(response.status).toBe(403);
      expect(await response.json()).toEqual({
        error: "delivery-evidence-admission-unavailable",
      });
    } finally {
      local.stop(true);
    }
  });

  test("admits a valid envelope once and rejects replay atomically", async () => {
    const { trust, envelope } = await signedEvidenceFixture();
    const localStore = new ControllerStore(":memory:");
    const local = startServer({
      host: "127.0.0.1",
      port: 0,
      token: TOKEN,
      store: localStore,
      evidenceTrust: trust,
    });
    const request = () => fetch(
      `http://127.0.0.1:${local.port}/delivery/evidence`,
      {
        method: "POST",
        headers: {
          authorization: `Bearer ${TOKEN}`,
          "x-delivery-evidence-authority": trust.authorityToken,
          "content-type": "application/json",
        },
        body: JSON.stringify(envelope),
      },
    );
    try {
      const admitted = await request();
      expect(admitted.status).toBe(201);
      expect(await admitted.json()).toEqual({ admitted: true });
      const deployment = localStore.getDeliveryDeployment("deployment-1");
      const acceptance = localStore.getDeliveryFeatureReceipt("acceptance-1");
      const smoke = localStore.getDeliveryFeatureReceipt("smoke-1");
      expect(deployment).not.toBeNull();
      expect(acceptance).not.toBeNull();
      expect(smoke).not.toBeNull();

      const replay = await request();
      expect(replay.status).toBe(403);
      expect(await replay.json()).toEqual({
        error: "delivery-evidence-admission-rejected",
      });
      expect(localStore.getDeliveryDeployment("deployment-1")).toEqual(deployment);
      expect(localStore.getDeliveryFeatureReceipt("acceptance-1")).toEqual(acceptance);
      expect(localStore.getDeliveryFeatureReceipt("smoke-1")).toEqual(smoke);
    } finally {
      local.stop(true);
      localStore.close();
    }
  });
});
