/**
 * WP-12 — per-worker Mailbox is queue truth; shared JSON is derived only.
 */

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

import {
  AgentQueueProjection,
  LegacyQueueCommandBridge,
  assertDerivedProjection,
  sortPendingByPriorityFifo,
} from "../index.js";
import { SwitchboardPaths } from "../../paths/SwitchboardPaths.js";

async function mkEnv(): Promise<{
  root: string;
  persistDir: string;
  paths: SwitchboardPaths;
}> {
  const root = join(
    tmpdir(),
    `wp12-q-${Date.now()}-${Math.random().toString(16).slice(2)}`,
  );
  const persistDir = join(root, ".codeflowmu", "state");
  await fs.mkdir(persistDir, { recursive: true });
  return { root, persistDir, paths: new SwitchboardPaths({ persistDir }) };
}

describe("WP-12 Mailbox queue truth", () => {
  let root = "";
  let paths!: SwitchboardPaths;

  before(async () => {
    const env = await mkEnv();
    root = env.root;
    paths = env.paths;
  });

  after(async () => {
    if (root) await fs.rm(root, { recursive: true, force: true });
  });

  it("documents that doorbell-buffer is NOT the delivery queue", () => {
    assert.equal(
      LegacyQueueCommandBridge.DELIVERY_QUEUE_IS,
      "per-worker DoorbellMailbox (file doorbells)",
    );
    assert.match(
      LegacyQueueCommandBridge.DELIVERY_QUEUE_IS_NOT,
      /doorbell-buffer/i,
    );
  });

  it("FIFO within same priority", async () => {
    const bridge = new LegacyQueueCommandBridge({
      paths,
      projectRoot: root,
      workerIds: ["DEV-01"],
    });
    await bridge.enqueue({
      workerId: "DEV-01",
      taskId: "TASK-20260805-101",
      dedupeKey: "dk-fifo-1",
      priority: "P2",
      createdAt: "2026-08-05T10:00:00.000Z",
      doorbellId: "db-fifo-a",
    });
    await bridge.enqueue({
      workerId: "DEV-01",
      taskId: "TASK-20260805-102",
      dedupeKey: "dk-fifo-2",
      priority: "P2",
      createdAt: "2026-08-05T10:00:01.000Z",
      doorbellId: "db-fifo-b",
    });
    const first = await bridge.claimNext("DEV-01");
    assert.equal(first.status, "claimed");
    if (first.status === "claimed") {
      assert.equal(first.message.doorbellId, "db-fifo-a");
    }
  });

  it("priority P0 before P1 before P2 before P3 (P3 lowest)", async () => {
    const env = await mkEnv();
    const bridge = new LegacyQueueCommandBridge({
      paths: env.paths,
      projectRoot: env.root,
      workerIds: ["DEV-02"],
      writeDerived: false,
    });
    await bridge.enqueue({
      workerId: "DEV-02",
      taskId: "TASK-P3",
      dedupeKey: "dk-p3",
      priority: "P3",
      createdAt: "2026-08-05T09:00:00.000Z",
      doorbellId: "db-p3",
    });
    await bridge.enqueue({
      workerId: "DEV-02",
      taskId: "TASK-P1",
      dedupeKey: "dk-p1",
      priority: "P1",
      createdAt: "2026-08-05T09:00:05.000Z",
      doorbellId: "db-p1",
    });
    await bridge.enqueue({
      workerId: "DEV-02",
      taskId: "TASK-P0",
      dedupeKey: "dk-p0",
      priority: "P0",
      createdAt: "2026-08-05T09:00:10.000Z",
      doorbellId: "db-p0",
    });
    await bridge.enqueue({
      workerId: "DEV-02",
      taskId: "TASK-P2",
      dedupeKey: "dk-p2",
      priority: "P2",
      createdAt: "2026-08-05T09:00:01.000Z",
      doorbellId: "db-p2",
    });

    const pending = await bridge.mailbox.list("DEV-02", "pending");
    const ordered = sortPendingByPriorityFifo(pending);
    assert.deepEqual(
      ordered.map((m) => m.priority),
      ["P0", "P1", "P2", "P3"],
    );

    const claim = await bridge.claimNext("DEV-02");
    assert.equal(claim.status, "claimed");
    if (claim.status === "claimed") {
      assert.equal(claim.message.priority, "P0");
    }
    await fs.rm(env.root, { recursive: true, force: true });
  });

  it("dedupes by dedupeKey (no second doorbell)", async () => {
    const env = await mkEnv();
    const bridge = new LegacyQueueCommandBridge({
      paths: env.paths,
      projectRoot: env.root,
      workerIds: ["QA-01"],
      writeDerived: false,
    });
    const a = await bridge.enqueue({
      workerId: "QA-01",
      taskId: "TASK-DUP",
      dedupeKey: "same-key",
      doorbellId: "db-dup-1",
    });
    const b = await bridge.enqueue({
      workerId: "QA-01",
      taskId: "TASK-DUP",
      dedupeKey: "same-key",
      doorbellId: "db-dup-2",
    });
    assert.equal(a.accepted, true);
    assert.equal(b.accepted, false);
    assert.equal(b.enqueue.status, "duplicate");
    assert.equal((await bridge.mailbox.list("QA-01", "pending")).length, 1);
    await fs.rm(env.root, { recursive: true, force: true });
  });

  it("worker busy keeps later work queued (parallel workers isolated)", async () => {
    const env = await mkEnv();
    const bridge = new LegacyQueueCommandBridge({
      paths: env.paths,
      projectRoot: env.root,
      workerIds: ["DEV-A", "DEV-B"],
      writeDerived: false,
    });

    await bridge.enqueue({
      workerId: "DEV-A",
      taskId: "TASK-A1",
      dedupeKey: "a1",
      doorbellId: "db-a1",
      createdAt: "2026-08-05T11:00:00.000Z",
    });
    await bridge.enqueue({
      workerId: "DEV-A",
      taskId: "TASK-A2",
      dedupeKey: "a2",
      doorbellId: "db-a2",
      createdAt: "2026-08-05T11:00:01.000Z",
    });
    await bridge.enqueue({
      workerId: "DEV-B",
      taskId: "TASK-B1",
      dedupeKey: "b1",
      doorbellId: "db-b1",
    });

    // Concurrent-style claims on different workers
    const [ca, cb] = await Promise.all([
      bridge.claimNext("DEV-A"),
      bridge.claimNext("DEV-B"),
    ]);
    assert.equal(ca.status, "claimed");
    assert.equal(cb.status, "claimed");

    const busy = await bridge.claimNext("DEV-A");
    assert.equal(busy.status, "busy");
    assert.equal((await bridge.mailbox.list("DEV-A", "pending")).length, 1);
    assert.equal((await bridge.mailbox.list("DEV-B", "pending")).length, 0);

    // Parallel workers do not overwrite each other's mailbox files
    assert.equal(
      (await bridge.mailbox.list("DEV-A", "claimed"))[0]?.doorbellId,
      "db-a1",
    );
    assert.equal(
      (await bridge.mailbox.list("DEV-B", "claimed"))[0]?.doorbellId,
      "db-b1",
    );
    await fs.rm(env.root, { recursive: true, force: true });
  });

  it("pause / resume via hold markers", async () => {
    const env = await mkEnv();
    const bridge = new LegacyQueueCommandBridge({
      paths: env.paths,
      projectRoot: env.root,
      workerIds: ["OPS-01"],
    });
    await bridge.enqueue({
      workerId: "OPS-01",
      taskId: "TASK-PAUSE",
      dedupeKey: "dk-pause",
      doorbellId: "db-pause",
    });
    await bridge.pause({
      workerId: "OPS-01",
      taskId: "TASK-PAUSE",
      pauseReason: "admin hold",
    });
    const empty = await bridge.claimNext("OPS-01");
    assert.equal(empty.status, "empty");

    const proj = await bridge.readProjection();
    assert.ok(proj.paused["TASK-PAUSE"]);
    assert.equal(proj.agents["OPS-01"]?.queue.length, 0);

    await bridge.resume("OPS-01", "TASK-PAUSE");
    const again = await bridge.claimNext("OPS-01");
    assert.equal(again.status, "claimed");
    await fs.rm(env.root, { recursive: true, force: true });
  });

  it("derived JSON matches mailbox and is marked non-authoritative", async () => {
    const env = await mkEnv();
    const bridge = new LegacyQueueCommandBridge({
      paths: env.paths,
      projectRoot: env.root,
      workerIds: ["DEV-01"],
      writeDerived: true,
    });
    await bridge.enqueue({
      workerId: "DEV-01",
      taskId: "TASK-DERIVED",
      dedupeKey: "dk-der",
      priority: "P1",
      doorbellId: "db-der",
    });
    const path = join(
      env.root,
      ".codeflowmu",
      "pm-governance",
      "agent-task-queue.json",
    );
    const raw = JSON.parse(await fs.readFile(path, "utf8"));
    assertDerivedProjection(raw);
    const slot = raw.agents["DEV-01"];
    assert.ok(slot);
    assert.equal(slot.queue.length, 1);
    assert.equal(slot.queue[0]?.task_id, "TASK-DERIVED");
    assert.equal(raw.authoritative, false);
    assert.equal(raw.source, "switchboard-mailboxes");

    // Mutate derived file — Core truth remains mailbox
    slot.queue = [];
    await fs.writeFile(path, JSON.stringify(raw), "utf8");
    const fromMailbox = await new AgentQueueProjection({
      paths: env.paths,
    }).project(["DEV-01"]);
    assert.equal(fromMailbox.agents["DEV-01"]?.queue.length, 1);
    await fs.rm(env.root, { recursive: true, force: true });
  });

  it("survives restart: recover orphans then re-project", async () => {
    const env = await mkEnv();
    const bridge = new LegacyQueueCommandBridge({
      paths: env.paths,
      projectRoot: env.root,
      workerIds: ["DEV-01"],
      writeDerived: false,
    });
    await bridge.enqueue({
      workerId: "DEV-01",
      taskId: "TASK-RESTART",
      dedupeKey: "dk-rs",
      doorbellId: "db-rs",
    });
    // Inject orphan tmp
    const pendingDir = env.paths.mailboxDir("DEV-01", "pending");
    await fs.writeFile(join(pendingDir, "junk.md.tmp"), "partial", "utf8");

    const recovered = await bridge.mailbox.recoverOrphans("DEV-01");
    assert.ok(recovered.some((n) => n.includes("junk.md.tmp")));

    // New bridge instance = "runtime restart"
    const restarted = new LegacyQueueCommandBridge({
      paths: env.paths,
      projectRoot: env.root,
      workerIds: ["DEV-01"],
      writeDerived: false,
    });
    const proj = await restarted.readProjection();
    assert.equal(proj.agents["DEV-01"]?.queue.length, 1);
    assert.equal(proj.agents["DEV-01"]?.queue[0]?.task_id, "TASK-RESTART");
    await fs.rm(env.root, { recursive: true, force: true });
  });

  it("clearRunning settles claimed without dropping pending", async () => {
    const env = await mkEnv();
    const bridge = new LegacyQueueCommandBridge({
      paths: env.paths,
      projectRoot: env.root,
      workerIds: ["DEV-01"],
      writeDerived: false,
    });
    await bridge.enqueue({
      workerId: "DEV-01",
      taskId: "TASK-RUN",
      dedupeKey: "dk-run",
      doorbellId: "db-run",
      createdAt: "2026-08-05T12:00:00.000Z",
    });
    await bridge.enqueue({
      workerId: "DEV-01",
      taskId: "TASK-WAIT",
      dedupeKey: "dk-wait",
      doorbellId: "db-wait",
      createdAt: "2026-08-05T12:00:01.000Z",
    });
    await bridge.claimNext("DEV-01");
    const cleared = await bridge.clearRunning("DEV-01");
    assert.equal(cleared.status, "settled");
    assert.equal((await bridge.mailbox.list("DEV-01", "settled")).length, 1);
    assert.equal((await bridge.mailbox.list("DEV-01", "pending")).length, 1);
    await fs.rm(env.root, { recursive: true, force: true });
  });
});
