import { afterEach, beforeEach, describe, expect, setDefaultTimeout, test } from "bun:test";
import { mkdtempSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { AuditWriteError, ControllerStore, DeliveryEvidenceAdmissionService, StaleRevisionError } from "./store";
import { definitionDigest, type FeatureDefinition } from "./delivery/definitions";

setDefaultTimeout(20_000);

describe("ControllerStore", () => {
  let dir: string;
  let dbPath: string;
  let store: ControllerStore;

  beforeEach(() => {
    dir = mkdtempSync(join(tmpdir(), "controller-store-"));
    dbPath = join(dir, "state.sqlite");
    store = new ControllerStore(dbPath);
  });

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

  test("opens with revision 0 and empty fleet", () => {
    expect(store.getRevision()).toBe(0);
    expect(store.listHosts()).toEqual([]);
    expect(store.getLease().active).toBe(false);
  });

  test("drops wrapper-named breakers on open and keeps executable ones", () => {
    for (const command of ["bash", "env", "playwright"]) {
      store.setCapabilityBreaker({
        hostname: "debian3",
        command,
        state: "half-open",
        failureCount: 2,
        missingEventEmitted: true,
      });
    }
    store.close();
    store = new ControllerStore(dbPath);

    expect(store.getCapabilityBreaker("debian3", "bash")).toBeNull();
    expect(store.getCapabilityBreaker("debian3", "env")).toBeNull();
    expect(store.getCapabilityBreaker("debian3", "playwright")?.state).toBe("half-open");
  });

  test("enforces exact receipt authority at the feature state boundary", () => {
    const definition: FeatureDefinition = { id: "dark-delivery", changeClass: "behavior-change", owner: "owner", fallback: "off", defaultState: "OFF", readinessChecks: ["check", "extra"], observability: ["ledger"], reviewDate: "2026-09-01", cleanupTask: "#5", rollbackWindowDays: 7, removalCondition: "remove", introduced: { task: "#5", sha: "f".repeat(40) }, activationPolicy: "auto-after-receipts" };
    const digest = definitionDigest(definition);
    const sha = "a".repeat(40);
    const tree = "b".repeat(40);
    const artifact = "c".repeat(64);
    const deployment = { deploymentId: "deploy", targetId: "controller", deployedSha: sha, deployedTree: tree, artifactDigest: artifact, status: "ACTIVE" as const, observedAt: "2026-08-11T00:00:00.000Z", evidenceReference: "deploy" };
    const base = { featureId: "dark-delivery", definitionDigest: digest, checkId: "check", candidateSha: sha, candidateTree: tree, deploymentId: "deploy", targetId: "controller", deployedSha: sha, deployedTree: tree, artifactDigest: artifact, result: "PASSED" as const, observedAt: "2026-08-11T00:00:00.000Z", evidenceReference: "evidence" };
    new DeliveryEvidenceAdmissionService(store).admit(deployment, [{ ...base, receiptId: "acceptance", kind: "ACCEPTANCE" }, { ...base, receiptId: "smoke-good", kind: "SMOKE" }], "test-attestation");
    const unauthorizedStore = store as unknown as { transitionDeliveryFeatureStateAuthorized(capability: symbol, input: unknown): void };
    expect(() => unauthorizedStore.transitionDeliveryFeatureStateAuthorized(Symbol("forged"), { definition: { definition, digest, path: "test.json" }, state: "INTERNAL", targetId: "controller", acceptanceReceiptId: "acceptance", smokeReceiptId: "smoke-good", expectedRevision: 0 })).toThrow(/delivery store capability required/);
    expect(store.getDeliveryFeatureState("dark-delivery")).toBeNull();
  });

  test("persists revision across reopen", () => {
    store.commitTransition({
      journalId: store.journalIntent({
        verb: "admission-reconcile",
        idempotencyKey: "seed-1",
        args: {},
        expectedRevision: 0,
      }),
      idempotencyKey: "seed-1",
      result: { reconciled: true },
      apply: () => {},
    });
    expect(store.getRevision()).toBe(1);
    store.close();

    const reopened = new ControllerStore(dbPath);
    expect(reopened.getRevision()).toBe(1);
    reopened.close();
  });

  test("rejects permanent fallback lease", () => {
    expect(() =>
      store.setLease({ active: true, expiresAt: "", host: "laptop", reason: "spill" }),
    ).toThrow(/must always expire/);
  });

  test("expires fallback lease after ttl", () => {
    let now = Date.now();
    const timed = new ControllerStore(dbPath, { now: () => now });
    timed.setLease({
      active: true,
      expiresAt: new Date(now + 1000).toISOString(),
      host: "laptop",
      reason: "spill",
    });
    expect(timed.getLease().active).toBe(true);
    now += 2000;
    expect(timed.getLease().active).toBe(false);
    timed.close();
  });

  test("idempotency replay survives reopen within 24h", () => {
    const key = "idem-1";
    store.commitTransition({
      journalId: store.journalIntent({
        verb: "admission-reconcile",
        idempotencyKey: key,
        args: {},
        expectedRevision: 0,
      }),
      idempotencyKey: key,
      result: { reconciled: true, advanced: [] },
      apply: () => {},
    });
    store.close();

    const reopened = new ControllerStore(dbPath);
    const replay = reopened.getIdempotencyResult(key);
    expect(replay?.revision).toBe(1);
    expect(replay?.result).toEqual({ reconciled: true, advanced: [] });
    reopened.close();
  });

  test("audit write failure throws AuditWriteError", () => {
    const failing = new ControllerStore(dbPath, { auditWriteFails: true });
    expect(() =>
      failing.journalIntent({
        verb: "box-drain",
        idempotencyKey: "audit-fail",
        args: { host: "debian1" },
        expectedRevision: 0,
      }),
    ).toThrow(AuditWriteError);
    failing.close();
  });

  test("mutations are transactional", () => {
    store.upsertHost({ hostname: "debian1", state: "available" });
    expect(store.getHost("debian1")?.state).toBe("available");
    store.setHostState("debian1", "draining");
    expect(store.getHost("debian1")?.state).toBe("draining");
  });

  test("nested mutations share one transaction and return values", () => {
    const value = store.mutate(() => {
      store.upsertHost({ hostname: "nested", state: "maintenance" });
      return store.getHost("nested")?.hostname;
    });
    expect(value).toBe("nested");
  });

  test("persists report job fields and succeeded releases reservations", () => {
    store.upsertJob({
      id: "report-job",
      key: "key-1",
      mirror: "mirror-1",
      repo: "owner/repo",
      host: "remote",
      snapshot: "42",
      stage: "succeeded",
      attempt: 2,
      rc: 0,
      infraFailure: false,
      startedAt: "2026-07-20T00:00:00.000Z",
      finishedAt: "2026-07-20T00:00:05.000Z",
      lastReportAt: "2026-07-20T00:00:06.000Z",
      timeoutSec: 30,
    });
    expect(store.getJob("report-job")).toMatchObject({
      key: "key-1",
      mirror: "mirror-1",
      stage: "succeeded",
      startedAt: "2026-07-20T00:00:00.000Z",
      finishedAt: "2026-07-20T00:00:05.000Z",
      lastReportAt: "2026-07-20T00:00:06.000Z",
      timeoutSec: 30,
    });
  });

  test("stores only the latest spine config hash per path", () => {
    expect(store.getSpineConfigState("/etc/overdeck.json")).toBeNull();
    store.setSpineConfigState({
      configPath: "/etc/overdeck.json",
      lastAppliedBodyHash: "abc",
    });
    expect(store.getSpineConfigState("/etc/overdeck.json")).toEqual({
      configPath: "/etc/overdeck.json",
      lastAppliedBodyHash: "abc",
    });
  });

  test("host eligibility evaluates guards in contract order without reservations", () => {
    expect(store.hostEligible("missing", "node")).toEqual({
      eligible: false,
      reason: "unknown-host",
    });
    store.upsertHost({
      hostname: "builder",
      state: "available",
      capabilityOk: true,
      slotsTotal: 1,
    });
    expect(store.hostEligible("builder", "node")).toEqual({
      eligible: true,
      reason: "eligible",
    });
    store.setCapabilityBreaker({
      hostname: "builder",
      command: "node",
      state: "open",
      failureCount: 2,
      missingEventEmitted: true,
    });
    expect(store.hostEligible("builder", "node")).toEqual({
      eligible: false,
      reason: "command-quarantined",
    });
    expect(store.hostEligible("builder", "bun")).toEqual({
      eligible: true,
      reason: "eligible",
    });
    expect(store.listHostSlotReservations()).toEqual([]);
  });

  test("transition commit CAS cancels stale journal without state writes", () => {
    const journalId = store.journalIntent({
      verb: "admission-reconcile",
      idempotencyKey: "cas-stale",
      args: {},
      expectedRevision: 0,
    });
    store.appendEvent({
      ts: "2026-07-20T00:00:00.000Z",
      job: "",
      repo: "",
      host: "",
      snapshot: "",
      attempt: 0,
      stage: "external",
      reason: "external",
      rc: null,
      durationSeconds: 0,
    });
    expect(() => store.commitTransition({
      journalId,
      idempotencyKey: "cas-stale",
      acceptedRevision: 0,
      result: {},
      apply: () => store.upsertHost({ hostname: "should-not-exist" }),
    })).toThrow(StaleRevisionError);
    expect(store.getHost("should-not-exist")).toBeNull();
    expect(store.listPendingJournals()).toEqual([]);
    expect(store.getIdempotencyResult("cas-stale")).toBeNull();
  });

  test("clears spine-config degradation whose config file no longer exists", () => {
    const vanished = join(dir, "vanished", "invalid.json");
    const present = join(dir, "state.sqlite");
    store.setSpineConfigDegraded(vanished);
    store.upsertIncident({
      key: `spine-config:${vanished}`,
      lastSeen: "2026-07-21T02:56:26.319Z",
      affectedJobs: [],
      remediation: "fix config",
      cooldownUntil: null,
      autoResolveCondition: `valid report for ${vanished}`,
      state: "open",
    });
    store.upsertIncident({
      key: `spine-config:${present}`,
      lastSeen: "2026-07-21T02:56:26.319Z",
      affectedJobs: [],
      remediation: "fix config",
      cooldownUntil: null,
      autoResolveCondition: `valid report for ${present}`,
      state: "open",
    });

    expect(store.clearVanishedSpineConfigDegradation("2026-08-08T00:00:00.000Z")).toEqual([
      vanished,
    ]);
    expect(store.getMetaSnapshot().dispatchState).toBe("healthy");
    expect(store.getIncident(`spine-config:${vanished}`)?.state).toBe("resolved");
    expect(store.getIncident(`spine-config:${present}`)?.state).toBe("open");
  });

  test("emits only one config incident", () => {
    expect(store.emitConfigIncident("config-invalid-shape", "bad")).toBe(true);
    expect(store.emitConfigIncident("config-invalid-shape", "bad")).toBe(false);
    expect(store.hasConfigIncident()).toBe(true);
    expect(store.getMetaSnapshot().observed).toBe("degraded");
  });
});
