import { describe, expect, test } from "bun:test";
import { mkdtempSync } from "node:fs";
import { join } from "node:path";
import { IncidentStatusCasError, IncidentStatusStore } from "./status-store";
import type { IncidentStatusUpdate } from "./status-writer";

function update(revision: number, state: IncidentStatusUpdate["state"]): IncidentStatusUpdate {
  return {
    incidentId: "INC-race", taskId: 42, dispatchId: "dispatch-1",
    revision, expectedRevision: revision - 1, state,
    at: `2026-08-09T00:00:0${revision}.000Z`,
    ...(state === "running" ? { heartbeatAt: `2026-08-09T00:00:0${revision}.000Z` } : { completedAt: `2026-08-09T00:00:0${revision}.000Z` }),
  };
}

function stores() {
  const path = join(mkdtempSync("/tmp/overdeck-status-store-"), "status.sqlite");
  return [new IncidentStatusStore(path), new IncidentStatusStore(path)] as const;
}

describe("incident durable status CAS/outbox", () => {
  test("terminal wins deterministic interleaving; stale heartbeat cannot overwrite it", () => {
    const [heartbeat, terminal] = stores();
    heartbeat.commit(update(2, "running"));
    terminal.commit(update(3, "resolved"));

    expect(() => heartbeat.commit(update(3, "running"))).toThrow(IncidentStatusCasError);
    expect(() => heartbeat.commit(update(4, "running"))).toThrow("terminal incident cannot return to running");
  });

  test("leased older projection blocks newer receipt until released", () => {
    const [first, second] = stores();
    first.commit(update(2, "running"));
    const heartbeat = first.claim("INC-race", "heartbeat-worker", 1_000, 30_000);
    expect(heartbeat?.revision).toBe(2);

    second.commit(update(3, "resolved"));
    expect(second.claim("INC-race", "terminal-worker", 1_001, 30_000)).toBeNull();

    first.projected(heartbeat!.operationId, "heartbeat-worker", "2026-08-09T00:00:02.000Z", 1_001);
    expect(second.claim("INC-race", "terminal-worker", 1_002, 30_000)?.revision).toBe(3);
  });

  test("rejects a revision targeting a different task", () => {
    const [store] = stores();
    store.commit(update(2, "running"));
    expect(() => store.commit({ ...update(3, "resolved"), taskId: 99 })).toThrow("different task");
  });

  test("expired worker cannot renew or mark a reclaimed receipt projected", () => {
    const [first, second] = stores();
    first.commit(update(2, "running"));
    const stale = first.claim("INC-race", "stale", 1_000, 10)!;
    const current = second.claim("INC-race", "current", 1_011, 10)!;

    expect(() => first.renew(stale.operationId, "stale", 1_011, 10)).toThrow("projection lease lost");
    expect(() => first.projected(stale.operationId, "stale", "2026-08-09T00:00:02.000Z", 1_011)).toThrow("projection lease lost");
    second.projected(current.operationId, "current", "2026-08-09T00:00:02.000Z", 1_011);
  });

  test("exact replay returns same receipt and creates no second outbox operation", () => {
    const [store] = stores();
    const value = update(2, "running");
    expect(store.commit(value)).toEqual(store.commit(value));
    const claimed = store.claim("INC-race", "worker", 1_000);
    expect(claimed?.revision).toBe(2);
    store.projected(claimed!.operationId, "worker", "2026-08-09T00:00:02.000Z", 1_001);
    expect(store.claim("INC-race", "worker", 1_001)).toBeNull();
  });

  test("resolved can never reopen, including at a higher revision", () => {
    const [store] = stores();
    store.commit(update(2, "resolved"));
    expect(() => store.commit(update(3, "needs-attention"))).toThrow("resolved incident cannot reopen");
  });
});
