import assert from "node:assert/strict";
import { test } from "bun:test";

import {
  buildWorkbenchProjectionEventFacts,
  recordWorkbenchSessionOwner,
  writeWorkbenchSessionAdmissionFact
} from "./workbench-projection-writer.ts";
import { projectionOutboxRealtimeEvents } from "./server-workbench-realtime-http.ts";

test("HTTP lifecycle records ownership without writing projection facts", async () => {
  const calls = [];
  const accessController = {
    async recordAgentSessionOwner(input) {
      calls.push(input);
      return { id: input.sessionId, ownerUserId: input.ownerUserId, status: input.status };
    }
  };
  let projectionWrites = 0;
  const owner = await recordWorkbenchSessionOwner({
    accessController,
    runtimeStore: { writeWorkbenchFacts() { projectionWrites += 1; } },
    traceId: "trc_owner_only",
    ownerUserId: "usr_owner",
    ownerRole: "user",
    sessionId: "ses_owner_only",
    projectId: "prj_owner",
    status: "completed",
    session: { source: "http-result" }
  });

  assert.equal(owner.id, "ses_owner_only");
  assert.equal(calls.length, 1);
  assert.equal(calls[0].traceId, "trc_owner_only");
  assert.equal(projectionWrites, 0);
});

test("admission persists only the dedicated session scope fact", async () => {
  const calls = [];
  const runtimeStore = {
    async writeWorkbenchSessionAdmissionFact(params, meta) {
      calls.push({ params, meta });
      return { written: true, admissionOnly: true };
    },
    writeWorkbenchFacts() {
      throw new Error("generic projection writer must not be used by admission");
    }
  };
  const result = await writeWorkbenchSessionAdmissionFact({
    runtimeStore,
    session: {
      id: "ses_admission_only",
      ownerUserId: "usr_owner",
      ownerRole: "user",
      projectId: "prj_owner",
      conversationId: "cnv_owner",
      lastTraceId: "trc_admission_only",
      status: "running",
      session: { source: "manual-session-create", launchContext: { source: "workbench" } },
      createdAt: "2026-07-10T10:00:00.000Z",
      updatedAt: "2026-07-10T10:00:01.000Z"
    }
  });

  assert.equal(result.admissionOnly, true);
  assert.equal(calls.length, 1);
  const fact = calls[0].params.fact;
  assert.equal(fact.sessionId, "ses_admission_only");
  assert.equal(fact.ownerUserId, "usr_owner");
  assert.equal(fact.lastTraceId, "trc_admission_only");
  assert.equal(fact.projectedSeq, 0);
  assert.equal(fact.sourceEventId, "ses_admission_only:session-admission");
  assert.deepEqual(fact.sessionJson.launchContext, { source: "workbench", valuesRedacted: true });
});

test("admission fails closed when the dedicated durable entry is absent", async () => {
  await assert.rejects(
    writeWorkbenchSessionAdmissionFact({ runtimeStore: { writeWorkbenchFacts() {} }, session: { id: "ses_missing_entry" } }),
    (error) => error?.code === "workbench_admission_store_unconfigured"
  );
});

test("Kafka projector builds running facts from one canonical source event", () => {
  const built = buildWorkbenchProjectionEventFacts({
    projectedSeq: 4,
    projectedAt: "2026-07-10T10:00:02.000Z",
    event: {
      traceId: "trc_running",
      sessionId: "ses_running",
      runId: "run_running",
      commandId: "cmd_running",
      sourceEventId: "evt_running",
      sourceSeq: 7,
      type: "assistant",
      eventType: "assistant",
      status: "running",
      text: "committed delta",
      createdAt: "2026-07-10T10:00:01.000Z"
    }
  });

  assert.equal(built.written, true);
  assert.equal(built.facts.sessions[0].lastTraceId, "trc_running");
  assert.equal(built.facts.messages[0].text, "committed delta");
  assert.equal(built.facts.turns[0].terminal, false);
  assert.equal(built.facts.checkpoints[0].projectedSeq, 4);
  assert.equal(built.facts.traceEvents[0].sourceEventId, "evt_running");
});

test("authoritative assistant final seals terminal with a final response", () => {
  const built = buildWorkbenchProjectionEventFacts({
    projectedSeq: 5,
    projectedAt: "2026-07-10T10:00:04.000Z",
    previousCheckpoint: {
      traceId: "trc_terminal",
      sessionId: "ses_terminal",
      projectedSeq: 4,
      sourceSeq: 4,
      startedAt: "2026-07-10T10:00:00.000Z",
      lastEventAt: "2026-07-10T10:00:03.000Z",
      timing: { startedAt: "2026-07-10T10:00:00.000Z", lastEventAt: "2026-07-10T10:00:03.000Z", finishedAt: null, durationMs: null }
    },
    event: {
      traceId: "trc_terminal",
      sessionId: "ses_terminal",
      sourceEventId: "evt_terminal",
      sourceSeq: 5,
      type: "assistant",
      eventType: "assistant",
      status: "completed",
      terminal: true,
      replyAuthority: true,
      text: "durable final answer",
      createdAt: "2026-07-10T10:00:04.000Z"
    }
  });

  const turn = built.facts.turns[0];
  assert.equal(turn.terminal, true);
  assert.equal(turn.sealed, true);
  assert.equal(turn.finalResponse.text, "durable final answer");
  assert.equal(built.facts.messages[0].terminal, true);
  assert.equal(built.facts.parts[0].partType, "final_response");
  assert.equal(built.facts.checkpoints[0].projectionStatus, "caught_up");
});

test("canonical event time owns running, terminal, replay, and duration", () => {
  const running = buildWorkbenchProjectionEventFacts({
    projectedSeq: 20,
    projectedAt: "2026-07-10T10:17:00.000Z",
    event: {
      traceId: "trc_canonical_duration",
      sessionId: "ses_canonical_duration",
      sourceEventId: "evt_canonical_running",
      sourceSeq: 41,
      type: "assistant",
      eventType: "assistant_progress",
      status: "running",
      createdAt: "2026-07-10T10:00:01.000Z",
      startedAt: "2026-07-10T09:43:00.000Z",
      durationMs: 1020000
    }
  });
  const runningCheckpoint = running.facts.checkpoints[0];
  assert.equal(runningCheckpoint.startedAt, "2026-07-10T10:00:01.000Z");
  assert.equal(runningCheckpoint.lastEventAt, "2026-07-10T10:00:01.000Z");
  assert.equal(runningCheckpoint.durationMs, null);

  const terminal = buildWorkbenchProjectionEventFacts({
    projectedSeq: 21,
    projectedAt: "2026-07-10T10:18:00.000Z",
    previousCheckpoint: runningCheckpoint,
    event: {
      traceId: "trc_canonical_duration",
      sessionId: "ses_canonical_duration",
      sourceEventId: "evt_canonical_terminal",
      sourceSeq: 42,
      type: "assistant",
      eventType: "assistant",
      status: "completed",
      terminal: true,
      replyAuthority: true,
      text: "canonical final",
      createdAt: "2026-07-10T10:00:04.000Z",
      finishedAt: "2026-07-10T10:18:00.000Z",
      durationMs: 1020000
    }
  });
  const terminalCheckpoint = terminal.facts.checkpoints[0];
  assert.equal(terminalCheckpoint.startedAt, "2026-07-10T10:00:01.000Z");
  assert.equal(terminalCheckpoint.finishedAt, "2026-07-10T10:00:04.000Z");
  assert.equal(terminalCheckpoint.durationMs, 3000);

  const [replayed] = projectionOutboxRealtimeEvents({
    events: [{
      outboxSeq: 72,
      entityFamily: "traceEvents",
      entityId: terminal.facts.traceEvents[0].id,
      projectedSeq: 21,
      projectionRevision: 1,
      traceId: "trc_canonical_duration",
      sessionId: "ses_canonical_duration",
      commitType: "event",
      terminal: true,
      sealed: true,
      payload: { family: "traceEvents", fact: terminal.facts.traceEvents[0] }
    }]
  });
  assert.deepEqual(replayed.payload.cursor, { outboxSeq: 72, traceSeq: 21 });
  assert.equal(replayed.payload.event.durationMs, 3000);
  const [refreshed] = projectionOutboxRealtimeEvents({
    events: [{
      outboxSeq: 72,
      entityFamily: "traceEvents",
      entityId: terminal.facts.traceEvents[0].id,
      projectedSeq: 21,
      projectionRevision: 1,
      traceId: "trc_canonical_duration",
      sessionId: "ses_canonical_duration",
      commitType: "event",
      terminal: true,
      sealed: true,
      payload: { family: "traceEvents", fact: terminal.facts.traceEvents[0] }
    }]
  });
  assert.deepEqual(refreshed.payload.cursor, replayed.payload.cursor);
  assert.equal(refreshed.payload.event.durationMs, replayed.payload.event.durationMs);
});

test("completed terminal without final response remains visibly unsealed", () => {
  const built = buildWorkbenchProjectionEventFacts({
    projectedSeq: 1,
    event: {
      traceId: "trc_missing_final",
      sessionId: "ses_missing_final",
      sourceEventId: "evt_missing_final",
      sourceSeq: 1,
      type: "result",
      eventType: "terminal",
      status: "completed",
      terminal: true,
      createdAt: "2026-07-10T10:00:00.000Z"
    }
  });

  const session = built.facts.sessions[0];
  const message = built.facts.messages[0];
  const turn = built.facts.turns[0];
  const traceEvent = built.facts.traceEvents[0];
  const checkpoint = built.facts.checkpoints[0];
  for (const fact of [session, message, turn, traceEvent, checkpoint]) {
    assert.equal(fact.status ?? "running", "running");
    assert.equal(fact.terminal, false);
    assert.equal(fact.sealed, false);
  }
  assert.equal(built.terminal, false);
  assert.equal(built.sourceTerminal, true);
  assert.equal(turn.finalResponse, null);
  assert.equal(turn.diagnostic.terminalSealBlocked, true);
  assert.equal(traceEvent.diagnostic.sourceStatus, "completed");
  assert.equal(traceEvent.finishedAt, null);
  assert.equal(traceEvent.durationMs, null);
  assert.equal(checkpoint.projectionStatus, "projecting");
  assert.equal(checkpoint.projectionHealth, "degraded");

  const [sse] = projectionOutboxRealtimeEvents({
    events: [{
      outboxSeq: 1,
      entityFamily: "traceEvents",
      entityId: traceEvent.id,
      projectedSeq: 1,
      projectionRevision: 1,
      traceId: traceEvent.traceId,
      sessionId: traceEvent.sessionId,
      commitType: "event",
      terminal: traceEvent.terminal,
      sealed: traceEvent.sealed,
      payload: { family: "traceEvents", fact: traceEvent }
    }]
  });
  assert.equal(sse.name, "workbench.trace.event");
  assert.equal(sse.payload.terminal, false);
  assert.equal(sse.payload.sealed, false);
  assert.equal(sse.payload.snapshot.status, "running");
  assert.equal(sse.payload.event.terminal, false);
});

test("late running event cannot overwrite a sealed terminal", () => {
  const built = buildWorkbenchProjectionEventFacts({
    projectedSeq: 10,
    previousCheckpoint: {
      traceId: "trc_sealed",
      sessionId: "ses_sealed",
      projectedSeq: 9,
      sourceSeq: 9,
      terminal: true,
      sealed: true,
      finalResponse: { text: "sealed answer", status: "completed" }
    },
    event: {
      traceId: "trc_sealed",
      sessionId: "ses_sealed",
      sourceEventId: "evt_late",
      sourceSeq: 8,
      type: "backend",
      status: "running"
    }
  });

  assert.equal(built.written, false);
  assert.equal(built.suppressedAfterSeal, true);
  assert.equal(built.projectedSeq, 9);
  assert.equal(built.sourceEventId, "evt_late");
  assert.equal("facts" in built, false);
});

test("late terminal event cannot overwrite a different sealed terminal", () => {
  const built = buildWorkbenchProjectionEventFacts({
    projectedSeq: 11,
    previousCheckpoint: {
      traceId: "trc_sealed_terminal",
      sessionId: "ses_sealed_terminal",
      projectedSeq: 10,
      sourceSeq: 10,
      status: "completed",
      terminal: true,
      sealed: true,
      finalResponse: { text: "immutable completed answer", status: "completed" }
    },
    event: {
      traceId: "trc_sealed_terminal",
      sessionId: "ses_sealed_terminal",
      sourceEventId: "evt_late_failed_terminal",
      sourceSeq: 11,
      type: "terminal_status",
      status: "failed",
      terminal: true,
      errorCode: "late-failure"
    }
  });

  assert.equal(built.written, false);
  assert.equal(built.suppressedAfterSeal, true);
  assert.equal(built.projectedSeq, 10);
  assert.equal(built.sourceEventId, "evt_late_failed_terminal");
  assert.equal("facts" in built, false);
});

test("same canonical source event derives deterministic fact identities", () => {
  const input = {
    projectedSeq: 3,
    projectedAt: "2026-07-10T10:00:03.000Z",
    event: {
      traceId: "trc_deterministic",
      sessionId: "ses_deterministic",
      sourceEventId: "evt_deterministic",
      sourceSeq: 3,
      type: "assistant",
      eventType: "assistant",
      status: "running",
      text: "same",
      createdAt: "2026-07-10T10:00:02.000Z"
    }
  };
  const first = buildWorkbenchProjectionEventFacts(input);
  const second = buildWorkbenchProjectionEventFacts(input);

  assert.equal(first.facts.traceEvents[0].id, second.facts.traceEvents[0].id);
  assert.equal(first.facts.messages[0].messageId, second.facts.messages[0].messageId);
  assert.equal(first.facts.parts[0].partId, second.facts.parts[0].partId);
});
