/**
 * WP-09 Host Session ≠ Run — multi-run, claim, SessionRecord compatibility.
 */

import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, mkdir } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";

import {
  RunCompatibilityFacade,
  RunCoordinator,
  hostSessionKey,
  RunCoordinator as FromPublic,
  type HostSessionRef,
} from "../index.ts";
import { SessionStore } from "../../session/SessionStore.ts";
import { SwitchboardPaths } from "../paths/SwitchboardPaths.ts";

async function tempEnv(): Promise<{
  paths: SwitchboardPaths;
  sessionsDir: string;
}> {
  const root = await mkdtemp(join(tmpdir(), "sb-wp09-"));
  const persistDir = join(root, ".codeflowmu", "state");
  const sessionsDir = join(root, ".codeflowmu", "state", "sessions");
  await mkdir(persistDir, { recursive: true });
  await mkdir(sessionsDir, { recursive: true });
  return { paths: new SwitchboardPaths({ persistDir }), sessionsDir };
}

function host(externalSessionId = "ext-host-sess-1"): HostSessionRef {
  return {
    hostId: "host-mem",
    externalSessionId,
    mode: "local",
  };
}

describe("WP-09 RunCoordinator exports", () => {
  it("re-exports from switchboard barrel", () => {
    assert.equal(FromPublic, RunCoordinator);
  });
});

describe("WP-09 multi-run on one Host Session", () => {
  it("allows sequential runs on the same Host Session with independent state/evidence", async () => {
    const { paths } = await tempEnv();
    const coord = new RunCoordinator({ paths });
    const h = host();

    const r1 = await coord.beginRun({
      workerId: "DEV-01",
      doorbellId: "db-1",
      bindingId: "bind-1",
      host: h,
      taskId: "TASK-A",
      sourceFactRefs: ["TASK-A"],
    });
    assert.equal(r1.status, "started");
    if (r1.status !== "started") return;

    await coord.markRunning("DEV-01", r1.run.runId, "ext-run-1");
    await coord.appendEvidence("DEV-01", r1.run.runId, {
      type: "host.assistant",
      payload: { text: "first" },
    });
    await coord.settleRun("DEV-01", r1.run.runId, "completed");

    const r2 = await coord.beginRun({
      workerId: "DEV-01",
      doorbellId: "db-2",
      bindingId: "bind-1",
      host: h,
      taskId: "TASK-B",
      sourceFactRefs: ["TASK-B"],
    });
    assert.equal(r2.status, "started");
    if (r2.status !== "started") return;

    await coord.markRunning("DEV-01", r2.run.runId, "ext-run-2");
    await coord.appendEvidence("DEV-01", r2.run.runId, {
      type: "host.assistant",
      payload: { text: "second" },
    });
    await coord.settleRun("DEV-01", r2.run.runId, "failed", {
      kind: "unknown",
      message: "boom",
      retryable: false,
    });

    const all = await coord.listRunsForHostSession(h);
    assert.equal(all.length, 2);
    assert.equal(all[0]!.runId, r1.run.runId);
    assert.equal(all[0]!.state, "completed");
    assert.equal(all[1]!.state, "failed");
    assert.notEqual(all[0]!.deliveryAttemptId, all[1]!.deliveryAttemptId);

    const ev1 = await coord.listEvidence("DEV-01", r1.run.runId);
    const ev2 = await coord.listEvidence("DEV-01", r2.run.runId);
    assert.equal(ev1.length, 1);
    assert.equal(ev2.length, 1);
    assert.equal((ev1[0]!.payload as { text: string }).text, "first");
    assert.equal((ev2[0]!.payload as { text: string }).text, "second");
  });
});

describe("WP-09 claim occupancy", () => {
  it("rejects concurrent active run on same Host Session", async () => {
    const { paths } = await tempEnv();
    const coord = new RunCoordinator({ paths });
    const h = host("ext-claim-1");

    const first = await coord.beginRun({
      workerId: "DEV-01",
      doorbellId: "db-a",
      bindingId: "bind-1",
      host: h,
    });
    assert.equal(first.status, "started");

    const second = await coord.beginRun({
      workerId: "DEV-01",
      doorbellId: "db-b",
      bindingId: "bind-1",
      host: h,
    });
    assert.equal(second.status, "claim_conflict");
    if (second.status === "claim_conflict") {
      assert.match(second.reason, /active run already in flight/);
    }
  });

  it("rejects cross-worker claim race on same Host Session", async () => {
    const { paths } = await tempEnv();
    const coord = new RunCoordinator({ paths });
    const h = host("ext-claim-2");

    const first = await coord.beginRun({
      workerId: "DEV-01",
      doorbellId: "db-a",
      bindingId: "bind-1",
      host: h,
    });
    assert.equal(first.status, "started");

    const other = await coord.beginRun({
      workerId: "QA-01",
      doorbellId: "db-q",
      bindingId: "bind-q",
      host: h,
    });
    assert.equal(other.status, "claim_conflict");
    if (other.status === "claim_conflict") {
      assert.equal(other.heldBy, "DEV-01");
    }

    if (first.status === "started") {
      await coord.settleRun("DEV-01", first.run.runId, "completed");
    }
    const after = await coord.beginRun({
      workerId: "QA-01",
      doorbellId: "db-q2",
      bindingId: "bind-q",
      host: h,
    });
    assert.equal(after.status, "started");
  });
});

describe("WP-09 SessionRecord compatibility", () => {
  it("round-trips SessionStore and keeps Host Session ≠ session_id", async () => {
    const { paths, sessionsDir } = await tempEnv();
    const coord = new RunCoordinator({ paths });
    const store = new SessionStore({ dir: sessionsDir });
    const facade = new RunCompatibilityFacade({
      coordinator: coord,
      sessionStore: store,
    });
    const h = host("ext-compat-1");

    const begun = await coord.beginRun({
      workerId: "DEV-01",
      doorbellId: "db-c",
      bindingId: "bind-1",
      host: h,
      taskId: "TASK-20260805-011",
    });
    assert.equal(begun.status, "started");
    if (begun.status !== "started") return;
    await coord.markRunning("DEV-01", begun.run.runId);
    await coord.settleRun("DEV-01", begun.run.runId, "completed");

    const written = await facade.writeCompatibleSession("DEV-01", h);
    assert.ok(written.protocol.session_id.startsWith("session-"));
    assert.notEqual(written.protocol.session_id, h.externalSessionId);
    assert.equal(written.protocol.agent_id, "DEV-01");
    assert.equal(written.protocol.runs.length, 1);
    assert.equal(written.protocol.runs[0]!.status, "finished");

    const loaded = await facade.readCompatibleSession(
      written.protocol.session_id,
    );
    assert.ok(loaded);
    assert.equal(loaded!.protocol.session_id, written.protocol.session_id);
    assert.equal(loaded!.protocol.runs[0]!.run_id, begun.run.runId);

    const projection = await facade.project("DEV-01", h);
    assert.equal(
      facade.classifyId({
        value: "DEV-01",
        workerId: "DEV-01",
        host: h,
        runIds: projection.run_ids,
        sessionId: projection.session_id,
      }),
      "worker",
    );
    assert.equal(
      facade.classifyId({
        value: h.externalSessionId,
        workerId: "DEV-01",
        host: h,
        runIds: projection.run_ids,
        sessionId: projection.session_id,
      }),
      "host_session",
    );
    assert.equal(
      facade.classifyId({
        value: projection.session_id,
        workerId: "DEV-01",
        host: h,
        runIds: projection.run_ids,
        sessionId: projection.session_id,
      }),
      "session_record",
    );
    assert.equal(
      facade.classifyId({
        value: begun.run.runId,
        workerId: "DEV-01",
        host: h,
        runIds: projection.run_ids,
        sessionId: projection.session_id,
      }),
      "run",
    );
    assert.equal(hostSessionKey(h), projection.host_session_key);
  });
});
