import { createHash } from "node:crypto";
import { describe, expect, test } from "bun:test";
import { KanboardRpcError } from "./kanboard-client";
import { type IncidentsReadiness } from "./bootstrap";
import {
  createIncidentService,
  IncidentIdempotencyConflictError,
  IncidentMutationError,
  IncidentResolutionConflictError,
  type BriefConfig,
  type FileIncidentRequest,
} from "./incident-service";
import { BriefAssemblyError } from "./dispatch-brief";
import type { LoadedIncidentOptions } from "./dispatch-options";
import { InvalidIncidentTypeError } from "./dispatch-options";
import type {
  KanboardBatchResult,
  KanboardCall,
  KanboardParams,
  KanboardProcedure,
  KanboardRpcClient,
  KanboardResult,
} from "./kanboard-client";
import { IncidentLaunchError } from "./incident-launcher";
import { createIncidentStatusWriter } from "./status-writer";
import { IncidentStatusStore } from "./status-store";
import { mkdtempSync } from "node:fs";
import { join } from "node:path";
import { IncidentMutationStore } from "./incident-mutation-store";
import type { VerifiedResolutionEvidence } from "./resolution-learning";

function verifiedResolution(request: { incidentId: string; incidentType: string | null; artifact: string; summary?: string }): Promise<VerifiedResolutionEvidence> {
  return Promise.resolve({ incidentId: request.incidentId, incidentType: request.incidentType, artifact: request.artifact, artifactSha256: "a".repeat(64), sourceIncidentSha256: "b".repeat(64), operatorNote: request.summary?.trim() || null, howSolved: "fixed", evidence: ["verified test evidence"], targetSkill: null });
}

async function recordResolutionLearning(): Promise<void> {}

type TaskRecord = KanboardResult<"getTask">;
type TaskMetadata = KanboardResult<"getTaskMetadata">;
type TaskComment = KanboardResult<"getAllComments">[number];
type RecordedCall = { method: string; params: Record<string, unknown> };

function sharedMutationStore(): IncidentMutationStore {
  return new IncidentMutationStore(join(mkdtempSync("/tmp/overdeck-incident-service-mutation-"), "mutations.sqlite"));
}

function loadIncidentOptionsFixture(overrides: Partial<LoadedIncidentOptions> = {}): () => Promise<LoadedIncidentOptions> {
  const internal = new Map<string, LoadedIncidentOptions["internal"] extends Map<string, infer V> ? V : never>([
    ["codex\0gpt-5.6-sol\0high\0work\0safe", { wrapperModel: "gpt-5.6-sol-high", permissionMode: "safe", fixed: false, wrapper: "wrappers/codex.sh", timeoutSeconds: 3600 }],
    ["claude\0fable\0high\0main\0safe", { wrapperModel: "fable-high", permissionMode: "safe", fixed: false, wrapper: "wrappers/claude.sh", timeoutSeconds: 3600 }],
    ["claude\0fable\0high\0main\0unsafe", { wrapperModel: "fable-high", permissionMode: "unsafe", fixed: false, wrapper: "wrappers/claude.sh", timeoutSeconds: 3600 }],
  ]);
  return async () => ({
    types: [{ id: "resource-overload", title: "Resource overload", keywords: ["cpu"] }],
    clis: [
      { id: "codex", label: "Codex", models: [{ id: "gpt-5.6-sol", efforts: ["high"] }], accounts: [{ slug: "work", label: "Work", ready: true, fixed: false }], permissionModes: ["safe"] },
      { id: "claude", label: "Claude", models: [{ id: "fable", efforts: ["high"] }], accounts: [{ slug: "main", label: "Main", ready: true, fixed: false }], permissionModes: ["safe", "unsafe"] },
    ],
    sourcePaths: [],
    internal,
    ...overrides,
  });
}

function filedDispatchMetadata(overrides: Partial<TaskMetadata> = {}): TaskMetadata {
  return metadataFixture({
    "overdeck.incident_id": "dispatch-me",
    "overdeck.cli": "codex",
    "overdeck.model": "gpt-5.6-sol",
    "overdeck.reasoning_effort": "high",
    "overdeck.account": "work",
    "overdeck.unsafe": "0",
    "overdeck.wrapper_model": "gpt-5.6-sol-high",
    "overdeck.request_sha256": "abc",
    ...overrides,
  });
}

function testStatusWriter(client: KanboardRpcClient, readiness: IncidentsReadiness) {
  const store = new IncidentStatusStore(join(mkdtempSync("/tmp/overdeck-incident-service-"), "status.sqlite"));
  return createIncidentStatusWriter(client, readiness, store);
}

interface FakeClientSeed {
  tasksByStatus: Record<number, TaskRecord[]>;
  taskById: Partial<Record<number, TaskRecord>>;
  metadataByTaskId: Partial<Record<number, TaskMetadata>>;
  commentsByTaskId?: Partial<Record<number, TaskComment[]>>;
  getTaskErrors?: Partial<Record<number, Error>>;
  getTaskResults?: Partial<Record<number, unknown>>;
  removeTaskError?: Error;
  removeTaskResult?: unknown;
  closeTaskResult?: boolean;
  closeTaskUpdatesActive?: boolean;
}

function baseReadiness(overrides: Partial<IncidentsReadiness> = {}): IncidentsReadiness {
  return {
    status: "ready",
    drift: [],
    projectId: 77,
    columnIds: {
      Filed: 101,
      Dispatching: 102,
      Running: 103,
      "Needs attention": 104,
      Resolved: 105,
    },
    swimlaneIds: {
      "P0 · Critical": 201,
      "P1 · High": 202,
      "P2 · Normal": 203,
      "P3 · Low": 204,
    },
    agentUserId: 801,
    ...overrides,
  };
}

function taskFixture(overrides: Partial<TaskRecord> = {}): TaskRecord {
  const base: TaskRecord = {
    id: 100,
    title: "Fixture",
    description: "Base incident description",
    date_creation: 1710000000,
    color_id: "blue",
    project_id: 77,
    column_id: 101,
    owner_id: 7,
    position: 1,
    is_active: true,
    date_completed: null,
    score: 10,
    date_due: 0,
    category_id: 0,
    creator_id: 11,
    date_modification: 1710000300,
    reference: "R-1",
    date_started: 1710000000,
    time_spent: null,
    time_estimated: null,
    swimlane_id: 201,
    date_moved: 1710000000,
    recurrence_status: null,
    recurrence_counter: null,
    recurrence_parent: 0,
    priority: 1,
    external_provider: null,
    external_uri: null,
    url: "https://example.test/task/1",
    color: {
      name: "blue",
      background: "#123456",
      border: "#654321",
    },
  };

  const { color, ...taskOverrides } = overrides;
  return {
    ...base,
    ...taskOverrides,
    color: {
      ...base.color,
      ...(color ?? {}),
    },
  } as TaskRecord;
}

function metadataFixture(overrides: Partial<TaskMetadata> = {}): TaskMetadata {
  return {
    ...overrides,
  } as TaskMetadata;
}

function briefConfigFixture(): BriefConfig {
  const assets: Record<string, string> = {
    "taxonomy.json": "[]",
    "dispatch-template.md": "Incident {{incident_id}}\n{{situation}}\n{{placement_map}}\n{{never_touch}}",
    "placement-map.md": "placement",
    "never-touch.md": "never touch",
  };
  return {
    deps: { readAsset: (name) => assets[name] ?? null, listKb: () => [], skillExists: () => true },
    provenance: () => '{"sha":"fixture-sha"}',
  };
}

function commentFixture(overrides: Partial<TaskComment> = {}): TaskComment {
  const base: TaskComment = {
    id: 1000,
    date_creation: 1710000450,
    date_modification: 1710000450,
    task_id: 100,
    user_id: 3,
    comment: "Incident lifecycle note",
    visibility: "public",
    username: "operator",
    name: null,
    email: null,
    avatar_path: null,
  };
  return {
    ...base,
    ...overrides,
  } as TaskComment;
}

function createFakeClient(seed: FakeClientSeed) {
  const calls: RecordedCall[] = [];
  let createdTaskId = 500;

  const client: KanboardRpcClient = {
    async call<M extends KanboardProcedure>(
      method: M,
      params: KanboardParams<M>,
    ): Promise<KanboardResult<M>> {
      calls.push({ method, params: params as Record<string, unknown> });

      if (method === "overdeck.listTasksBounded") {
        const page = params as KanboardParams<"overdeck.listTasksBounded">;
        const statuses = page.statusId === null ? [1, 0] : [page.statusId];
        const query = page.query.toLowerCase();
        const tasks = statuses.flatMap((status) => seed.tasksByStatus[status] ?? [])
          .filter((task) => page.priority === null || task.priority === page.priority)
          .filter((task) => query === "" || task.title.toLowerCase().includes(query) || task.description.toLowerCase().includes(query) || task.reference.toLowerCase().includes(query))
          .sort((left, right) => right.id - left.id);
        const highWater = page.highWater ?? (tasks[0]?.id ?? 0);
        const bounded = tasks.filter((task) => task.id <= highWater && (page.cursor === null || task.id < page.cursor));
        const selected = bounded.slice(0, page.limit);
        return {
          tasks: selected,
          high_water: highWater,
          next_cursor: selected.length < bounded.length ? selected.at(-1)!.id : null,
          exhausted: selected.length >= bounded.length,
          complete: true,
          order: "id_desc",
        } as KanboardResult<M>;
      }

      if (method === "getAllTasks") {
        return (seed.tasksByStatus[(params as KanboardParams<"getAllTasks">).status_id] ?? []) as KanboardResult<M>;
      }

      if (method === "getTaskByReference") {
        const { project_id, reference } = params as KanboardParams<"getTaskByReference">;
        const task = Object.values(seed.taskById).find(
          (candidate) => candidate?.project_id === project_id && candidate.reference === reference,
        );
        if (!task) {
          throw new KanboardRpcError("RPC_NULL_RESULT", `no task for reference ${reference}`);
        }
        return task as KanboardResult<M>;
      }

      if (method === "getTask") {
        const id = (params as KanboardParams<"getTask">).task_id;
        const fail = seed.getTaskErrors?.[id];
        if (fail) {
          throw fail;
        }
        if (Object.hasOwn(seed.getTaskResults ?? {}, id)) {
          return seed.getTaskResults![id] as KanboardResult<M>;
        }
        const task = seed.taskById[id];
        if (!task) {
          throw new KanboardRpcError("RPC_NULL_RESULT", `task missing: ${id}`);
        }
        return task as KanboardResult<M>;
      }

      if (method === "getTaskMetadata") {
        const id = (params as KanboardParams<"getTaskMetadata">).task_id;
        return ((seed.metadataByTaskId[id] ?? {}) as TaskMetadata) as KanboardResult<M>;
      }

      if (method === "createTask") {
        const create = params as KanboardParams<"createTask">;
        const id = createdTaskId++;
        seed.taskById[id] = taskFixture({
          id,
          title: create.title,
          description: create.description,
          reference: create.reference ?? "",
          column_id: create.column_id,
          swimlane_id: create.swimlane_id,
          priority: create.priority,
        });
        return id as KanboardResult<M>;
      }
      if (method === "moveTaskPosition") {
        const move = params as KanboardParams<"moveTaskPosition">;
        const task = seed.taskById[move.task_id];
        if (task) seed.taskById[move.task_id] = { ...task, column_id: move.column_id };
        return true as KanboardResult<M>;
      }
      if (method === "removeTask") {
        if (seed.removeTaskError) throw seed.removeTaskError;
        const { task_id } = params as KanboardParams<"removeTask">;
        if (seed.removeTaskResult === undefined || seed.removeTaskResult === true) delete seed.taskById[task_id];
        return (seed.removeTaskResult ?? true) as KanboardResult<M>;
      }
      if (method === "updateTask" || method === "createComment") return true as KanboardResult<M>;
      if (method === "closeTask") {
        const { task_id } = params as KanboardParams<"closeTask">;
        const task = seed.taskById[task_id];
        if (task && seed.closeTaskUpdatesActive !== false) {
          seed.taskById[task_id] = { ...task, is_active: false };
        }
        return (seed.closeTaskResult ?? true) as KanboardResult<M>;
      }
      if (method === "saveTaskMetadata") {
        const { task_id, values } = params as KanboardParams<"saveTaskMetadata">;
        seed.metadataByTaskId[task_id] = values;
        return true as KanboardResult<M>;
      }

      const taskId = (params as KanboardParams<"getAllComments">).task_id;
      const comments = seed.commentsByTaskId?.[taskId] ?? [];
      return comments as KanboardResult<M>;
    },

    async batch<C extends readonly KanboardCall[]>(callsArg: C): Promise<KanboardBatchResult<C>> {
      const output: unknown[] = [];
      for (const item of callsArg) {
        output.push(await client.call(item.method as never, item.params as never));
      }
      return output as KanboardBatchResult<C>;
    },
  };

  return { client, calls };
}

describe("incident service", () => {
  test("returns no incidents for empty project and does not fetch comments", async () => {
    const readiness = baseReadiness();
    const { client, calls } = createFakeClient({
      tasksByStatus: {
        0: [],
        1: [],
      },
      taskById: {},
      metadataByTaskId: {},
    });

    const service = createIncidentService({ client, readiness });
    const result = await service.listIncidents({ scope: "all" });

    expect(result.incidents).toEqual([]);
    expect(result.coverage).toEqual({ stale: false });
    expect(calls.some((call) => call.method === "getAllComments")).toBe(false);
  });

  test("hydrates only the bounded page after task-level filtering", async () => {
    const readiness = baseReadiness();
    const tasks = Array.from({ length: 120 }, (_, index) => taskFixture({
      id: 1_000 + index,
      title: index % 2 === 0 ? `Needle ${index}` : `Other ${index}`,
      swimlane_id: readiness.swimlaneIds[index % 3 === 0 ? "P1 · High" : "P2 · Normal"],
      priority: index % 3 === 0 ? 1 : 2,
      date_modification: 1_000 + index,
    }));
    const { client, calls } = createFakeClient({
      tasksByStatus: { 0: [], 1: tasks },
      taskById: Object.fromEntries(tasks.map((task) => [task.id, task])),
      metadataByTaskId: Object.fromEntries(tasks.map((task) => [task.id, metadataFixture()])),
    });

    const result = await createIncidentService({ client, readiness }).listIncidents({
      scope: "active", query: "needle", priority: "P1", limit: 3,
    });

    expect(result.incidents).toHaveLength(3);
    expect(calls.filter((call) => call.method === "overdeck.listTasksBounded")).toEqual([{
      method: "overdeck.listTasksBounded",
      params: { projectId: readiness.projectId, statusId: 1, limit: 3, cursor: null, highWater: null, query: "needle", priority: 1 },
    }]);
    expect(calls.filter((call) => call.method === "getAllTasks")).toHaveLength(0);
    expect(calls.filter((call) => call.method === "getTaskMetadata")).toHaveLength(3);
  });

  test("maps metadata-absent task as filed and keeps null timestamps", async () => {
    const readiness = baseReadiness();
    const incidentTask = taskFixture({
      id: 101,
      column_id: readiness.columnIds.Filed,
      swimlane_id: readiness.swimlaneIds["P1 · High"],
      title: "Startup incident",
      description: "Overdeck-Incident: 123\n",
      date_creation: 0,
      date_modification: 0,
      date_completed: null,
      priority: 1,
    });

    const { client } = createFakeClient({
      tasksByStatus: {
        0: [],
        1: [incidentTask],
      },
      taskById: { 101: incidentTask },
      metadataByTaskId: { 101: metadataFixture() },
    });

    const service = createIncidentService({ client, readiness });
    const result = await service.listIncidents({ scope: "active" });

    expect(result.incidents).toHaveLength(1);
    expect(result.incidents[0]!.state).toBe("filed");
    expect(result.incidents[0]!.createdAt).toBeNull();
    expect(result.incidents[0]!.updatedAt).toBeNull();
    expect(result.incidents[0]!.resolvedAt).toBeNull();
  });

  test("marks state mismatch between column and metadata as needs-attention", async () => {
    const readiness = baseReadiness();
    const incidentTask = taskFixture({
      id: 102,
      column_id: readiness.columnIds.Running,
      swimlane_id: readiness.swimlaneIds["P1 · High"],
      priority: 1,
      reference: "run-case",
    });
    const { client } = createFakeClient({
      tasksByStatus: {
        0: [],
        1: [incidentTask],
      },
      taskById: { 102: incidentTask },
      metadataByTaskId: { 102: metadataFixture({ "overdeck.dispatch_state": "starting" }) },
    });
    const service = createIncidentService({ client, readiness });

    const result = await service.listIncidents({ scope: "active" });

    const incident = result.incidents[0]!;
    expect(incident.state).toBe("needs-attention");
    expect(incident.coverage.detail).toContain("state-column-metadata-mismatch");
  });

});

describe("deleteIncident recovery authority", () => {
  function seedDeletion(store: IncidentMutationStore, incidentId: string, taskId: number, phase: "intent" | "removed" = "intent"): void {
    const key = `task:77:${taskId}`;
    expect(store.acquire(key, "stale-holder", 0, 1)).toBe(true);
    expect(store.claimDeletion(incidentId, taskId, key, "stale-holder", 0)).toBe("intent");
    if (phase === "removed") expect(store.markDeletionRemoved(incidentId, taskId, key, "stale-holder", 0)).toBe(true);
  }

  test.each([
    ["false result", new KanboardRpcError("RPC_FALSE_RESULT", "false result")],
    ["auth", new KanboardRpcError("HTTP_STATUS", "HTTP 401")],
    ["transport", new KanboardRpcError("TRANSPORT_ERROR", "transport unavailable")],
    ["malformed", new KanboardRpcError("MALFORMED_JSON", "malformed result")],
    ["backend", new KanboardRpcError("JSON_RPC_ERROR", "backend failed")],
  ])("rethrows %s failure instead of treating bound task as absent", async (_name, failure) => {
    const store = sharedMutationStore();
    seedDeletion(store, "delete-errors", 210);
    const { client } = createFakeClient({
      tasksByStatus: { 0: [], 1: [] }, taskById: {}, metadataByTaskId: {}, getTaskErrors: { 210: failure },
    });

    await expect(createIncidentService({ client, readiness: baseReadiness(), mutationStore: store, now: () => 10 }).deleteIncident("delete-errors")).rejects.toBe(failure);
    expect(store.getDeletion("delete-errors")?.phase).toBe("intent");
  });

  test("treats only RPC_NULL_RESULT as bound-task absence and resumes removed intent", async () => {
    const store = sharedMutationStore();
    seedDeletion(store, "delete-null", 211, "removed");
    const { client } = createFakeClient({ tasksByStatus: { 0: [], 1: [] }, taskById: {}, metadataByTaskId: {} });

    await createIncidentService({ client, readiness: baseReadiness(), mutationStore: store, now: () => 10 }).deleteIncident("delete-null");

    expect(store.getDeletion("delete-null")?.phase).toBe("finalized");
  });

  test("rethrows false remove result and preserves deletion intent", async () => {
    const store = sharedMutationStore();
    const task = taskFixture({ id: 212, reference: "delete-false", is_active: false, column_id: 105 });
    const { client } = createFakeClient({
      tasksByStatus: { 0: [task], 1: [] }, taskById: { 212: task }, metadataByTaskId: { 212: metadataFixture({ "overdeck.incident_id": "delete-false", "overdeck.dispatch_state": "resolved" }) }, removeTaskResult: false,
    });

    await expect(createIncidentService({ client, readiness: baseReadiness(), mutationStore: store }).deleteIncident("delete-false")).rejects.toMatchObject({ code: "invalid-request" });
    expect(store.getDeletion("delete-false")?.phase).toBe("intent");
  });

  test("quarantines stale intent when a validated inactive successor reuses the canonical ID", async () => {
    const store = sharedMutationStore();
    seedDeletion(store, "reused-id", 213);
    const successor = taskFixture({ id: 214, reference: "reused-id", is_active: false, column_id: 105 });
    const { client, calls } = createFakeClient({
      tasksByStatus: { 0: [successor], 1: [] }, taskById: { 214: successor }, metadataByTaskId: { 214: metadataFixture({ "overdeck.incident_id": "reused-id", "overdeck.dispatch_state": "resolved" }) },
    });

    await expect(createIncidentService({ client, readiness: baseReadiness(), mutationStore: store, now: () => 10 }).deleteIncident("reused-id")).rejects.toMatchObject({ code: "invalid-request" });

    expect(store.getDeletion("reused-id")).toBeNull();
    expect(calls.some((call) => call.method === "removeTask")).toBe(false);
  });

  test("refuses inactive Kanboard deletion while the exact incident unit remains active", async () => {
    const store = sharedMutationStore();
    const task = taskFixture({ id: 216, reference: "unit-active", is_active: false, column_id: 105 });
    const { client, calls } = createFakeClient({
      tasksByStatus: { 0: [task], 1: [] }, taskById: { 216: task }, metadataByTaskId: { 216: metadataFixture({ "overdeck.incident_id": "unit-active", "overdeck.dispatch_id": "dispatch-216", "overdeck.dispatch_state": "resolved" }) },
    });
    let inspected = 0;
    const service = createIncidentService({ client, readiness: baseReadiness(), mutationStore: store, launcher: { launch: async () => { throw new Error("unused"); }, isActive: async (identity) => { inspected += 1; expect(identity).toEqual({ incidentId: "unit-active", dispatchId: "dispatch-216" }); return true; } } });

    await expect(service.deleteIncident("unit-active")).rejects.toMatchObject({ code: "invalid-request" });

    expect(inspected).toBe(1);
    expect(calls.some((call) => call.method === "removeTask")).toBe(false);
  });

  test("refuses removed-intent finalization while its exact incident unit remains active", async () => {
    const store = sharedMutationStore();
    seedDeletion(store, "recovery-unit-active", 217, "removed");
    expect(store.claimLaunch("recovery-unit-active", 217, "dispatch-217", 0).dispatchId).toBe("dispatch-217");
    const { client } = createFakeClient({ tasksByStatus: { 0: [], 1: [] }, taskById: {}, metadataByTaskId: {} });
    const service = createIncidentService({ client, readiness: baseReadiness(), mutationStore: store, now: () => 10, launcher: { launch: async () => { throw new Error("unused"); }, isActive: async (identity) => { expect(identity).toEqual({ incidentId: "recovery-unit-active", dispatchId: "dispatch-217" }); return true; } } });

    await expect(service.deleteIncident("recovery-unit-active")).rejects.toMatchObject({ code: "invalid-request" });

    expect(store.getDeletion("recovery-unit-active")?.phase).toBe("removed");
  });

  test("binds deletion lease to marker task and refuses competing adoption after lease loss", async () => {
    const store = sharedMutationStore();
    seedDeletion(store, "marker-bound", 215);
    const acquired: string[] = [];
    const acquire = store.acquire.bind(store);
    store.acquire = ((key, holder, nowMs, leaseMs) => {
      acquired.push(key);
      return acquire(key, holder, nowMs, leaseMs);
    }) as IncidentMutationStore["acquire"];
    const failure = new Error("transport unavailable");
    const { client } = createFakeClient({ tasksByStatus: { 0: [], 1: [] }, taskById: {}, metadataByTaskId: {}, getTaskErrors: { 215: failure } });

    await expect(createIncidentService({ client, readiness: baseReadiness(), mutationStore: store, now: () => 10 }).deleteIncident("marker-bound")).rejects.toBe(failure);

    expect(acquired).toContain("task:77:215");
    expect(() => store.adoptDeletion("marker-bound", 215, "intent", "stale-holder", "task:77:215", "competitor", 10)).toThrow();
    expect(store.getDeletion("marker-bound")?.holder).toBe("stale-holder");
  });
});

describe("dispatchIncident concurrency and authority", () => {
  test("concurrent dispatch aliases lease the canonical task and launch exactly once", async () => {
    const readiness = baseReadiness();
    const filed = taskFixture({ id: 140, reference: "race-dispatch", column_id: 101, swimlane_id: 203, priority: 2 });
    const metadata = filedDispatchMetadata({ "overdeck.incident_id": "race-dispatch" });
    let launches = 0;
    let releaseLaunch: (() => void) | undefined;
    const launchGate = new Promise<void>((resolve) => { releaseLaunch = resolve; });
    const { client } = createFakeClient({
      tasksByStatus: { 0: [], 1: [filed] },
      taskById: { 140: filed },
      metadataByTaskId: { 140: metadata },
    });
    const service = createIncidentService({
      client,
      readiness,
      brief: briefConfigFixture(),
      loadIncidentOptions: loadIncidentOptionsFixture(),
      launcher: {
        launch: async () => {
          launches += 1;
          await launchGate;
          return { dispatchId: "dispatch-140", unit: "overdeck-incident-race-dispatch.service", acceptedAt: new Date().toISOString() };
        },
      },
      workspaceFor: (incidentId) => `/tmp/${incidentId}`,
      wrapperFor: () => "/opt/harness/wrappers/codex.sh",
      recordStatus: async () => undefined,
    });

    const first = service.dispatchIncident("race-dispatch");
    await new Promise((resolve) => setTimeout(resolve, 0));
    const second = service.dispatchIncident("140");
    releaseLaunch?.();
    const [one, two] = await Promise.allSettled([first, second]);

    expect(launches).toBe(1);
    const fulfilled = [one, two].filter((result) => result.status === "fulfilled");
    const rejected = [one, two].filter((result) => result.status === "rejected");
    expect(fulfilled).toHaveLength(1);
    expect(rejected).toHaveLength(1);
    expect((rejected[0] as PromiseRejectedResult).reason).toMatchObject({ code: "not-filed" });
  });

  test("concurrent file and dispatch launch exactly once", async () => {
    const readiness = baseReadiness();
    const request = {
      requestId: "6ba7b810-9dad-41d1-80b4-00c04fd430c8",
      title: "Disk filling",
      description: "collector disk at 92%",
      cli: "claude",
      model: "fable",
      reasoningEffort: "high",
      account: "main",
      unsafe: false,
      priority: "P2" as const,
    };
    let launches = 0;
    let releaseLaunch: (() => void) | undefined;
    const launchGate = new Promise<void>((resolve) => { releaseLaunch = resolve; });
    const { client } = createFakeClient({ tasksByStatus: { 0: [], 1: [] }, taskById: {}, metadataByTaskId: {} });
    const service = createIncidentService({
      client,
      readiness,
      brief: briefConfigFixture(),
      loadIncidentOptions: loadIncidentOptionsFixture(),
      launcher: {
        launch: async () => {
          launches += 1;
          await launchGate;
          return { dispatchId: "dispatch-race", unit: "overdeck-incident-race.service", acceptedAt: new Date().toISOString() };
        },
      },
      workspaceFor: (incidentId) => `/tmp/${incidentId}`,
      wrapperFor: () => "/opt/harness/wrappers/codex.sh",
      recordStatus: async () => undefined,
    });

    const file = service.fileIncident(request);
    await new Promise((resolve) => setTimeout(resolve, 0));
    const dispatch = service.dispatchIncident(request.requestId);
    releaseLaunch?.();
    await Promise.allSettled([file, dispatch]);

    expect(launches).toBe(1);
  });

  test("two service instances sharing a mutation store create exactly one filing task", async () => {
    const readiness = baseReadiness();
    const request = {
      requestId: "6ba7b810-9dad-41d1-80b4-00c04fd430c8",
      title: "Disk filling",
      description: "collector disk at 92%",
      cli: "claude",
      model: "fable",
      reasoningEffort: "high",
      account: "main",
      unsafe: false,
      priority: "P2" as const,
    };
    const { client, calls } = createFakeClient({ tasksByStatus: { 0: [], 1: [] }, taskById: {}, metadataByTaskId: {} });
    const mutationStore = sharedMutationStore();
    const serviceOptions = {
      client,
      readiness,
      loadIncidentOptions: loadIncidentOptionsFixture(),
      mutationStore,
    };
    const first = createIncidentService(serviceOptions);
    const second = createIncidentService(serviceOptions);

    const [one, two] = await Promise.all([
      first.fileIncident(request),
      second.fileIncident(request),
    ]);

    expect(one.id).toBe(request.requestId);
    expect(two.id).toBe(request.requestId);
    expect(calls.filter((call) => call.method === "createTask")).toHaveLength(1);
  });

  test("fails closed when incident dispatch authority is not configured", async () => {
    const readiness = baseReadiness();
    const filed = taskFixture({ id: 141, reference: "no-authority", column_id: 101 });
    const { client } = createFakeClient({
      tasksByStatus: { 0: [], 1: [filed] },
      taskById: { 141: filed },
      metadataByTaskId: { 141: filedDispatchMetadata({ "overdeck.incident_id": "no-authority" }) },
    });
    const service = createIncidentService({ client, readiness, brief: briefConfigFixture() });

    await expect(service.dispatchIncident("no-authority")).rejects.toMatchObject({ code: "incident-dispatch-options-stale" });
  });

  test("revalidates persisted incident_type against current taxonomy before dispatch mutation", async () => {
    const readiness = baseReadiness();
    const filed = taskFixture({ id: 142, reference: "stale-type", column_id: 101 });
    const { client, calls } = createFakeClient({
      tasksByStatus: { 0: [], 1: [filed] },
      taskById: { 142: filed },
      metadataByTaskId: {
        142: filedDispatchMetadata({
          "overdeck.incident_id": "stale-type",
          "overdeck.incident_type": "removed-type",
        }),
      },
    });
    const service = createIncidentService({
      client,
      readiness,
      brief: briefConfigFixture(),
      loadIncidentOptions: loadIncidentOptionsFixture(),
    });

    await expect(service.dispatchIncident("stale-type")).rejects.toMatchObject({ code: "incident-dispatch-options-stale" });
    expect(calls.some((call) => call.method === "saveTaskMetadata" || call.method === "moveTaskPosition")).toBe(false);
  });

  test("two service instances sharing a mutation store launch exactly once", async () => {
    const readiness = baseReadiness();
    const filed = taskFixture({ id: 150, reference: "shared-store", column_id: 101, swimlane_id: 203, priority: 2 });
    const metadata = filedDispatchMetadata({ "overdeck.incident_id": "shared-store" });
    let launches = 0;
    let releaseLaunch: (() => void) | undefined;
    const launchGate = new Promise<void>((resolve) => { releaseLaunch = resolve; });
    const { client } = createFakeClient({
      tasksByStatus: { 0: [], 1: [filed] },
      taskById: { 150: filed },
      metadataByTaskId: { 150: metadata },
    });
    const mutationStore = sharedMutationStore();
    const serviceOptions = {
      client,
      readiness,
      brief: briefConfigFixture(),
      loadIncidentOptions: loadIncidentOptionsFixture(),
      mutationStore,
      launcher: {
        launch: async () => {
          launches += 1;
          await launchGate;
          return { dispatchId: "dispatch-150", unit: "overdeck-incident-shared-store.service", acceptedAt: new Date().toISOString() };
        },
      },
      workspaceFor: (incidentId: string) => `/tmp/${incidentId}`,
      wrapperFor: () => "/opt/harness/wrappers/codex.sh",
      recordStatus: async () => undefined,
    };
    const first = createIncidentService(serviceOptions);
    const second = createIncidentService(serviceOptions);

    const race = Promise.allSettled([
      first.dispatchIncident("shared-store"),
      second.dispatchIncident("shared-store"),
    ]);
    await new Promise((resolve) => setTimeout(resolve, 0));
    releaseLaunch?.();
    const [one, two] = await race;

    expect(launches).toBe(1);
    const fulfilled = [one, two].filter((result) => result.status === "fulfilled");
    const rejected = [one, two].filter((result) => result.status === "rejected");
    expect(fulfilled).toHaveLength(1);
    expect(rejected).toHaveLength(1);
    expect((rejected[0] as PromiseRejectedResult).reason).toMatchObject({ code: "not-filed" });
  });
});
