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

import type { ChatMessage, WorkbenchSessionRecord } from "../types";
import { createWorkbenchServerState, reduceWorkbenchServerState, selectActiveMessages, selectSessionStatusAuthority } from "./workbench-server-state";

function sessionRecord(input: WorkbenchSessionRecord): WorkbenchSessionRecord {
  return input;
}

function agentMessage(input: Partial<ChatMessage> & Pick<ChatMessage, "id">): ChatMessage {
  return {
    role: "agent",
    title: "Code Agent",
    text: "",
    status: "running",
    createdAt: "2026-07-01T00:00:00.000Z",
    ...input
  };
}

function userMessage(input: Partial<ChatMessage> & Pick<ChatMessage, "id">): ChatMessage {
  return {
    role: "user",
    title: "用户",
    text: "",
    status: "sent",
    createdAt: "2026-07-01T00:00:00.000Z",
    ...input
  };
}

test("server state reducer keeps completed message without final response unsealed", () => {
  const sessionId = "ses_state_completed_without_final";
  const traceId = "trc_state_completed_without_final";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [agentMessage({ id: "msg_state_completed_without_final", status: "running", traceId, sessionId, startedAt: "2026-07-01T00:00:00.000Z" })] });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [agentMessage({ id: "msg_state_completed_without_final", status: "completed", traceId, sessionId, startedAt: "2026-07-01T00:00:00.000Z", finishedAt: "2026-07-01T00:00:02.000Z", durationMs: 2000 })] });

  const [message] = selectActiveMessages(state, sessionId);
  assert.ok(message);
  assert.equal(message.status, "running");
  assert.equal(message.finishedAt, null);
  assert.equal(message.durationMs, null);
  assert.notEqual(selectSessionStatusAuthority(state)[sessionId]?.status, "completed");
});

test("late terminal replay derives duration only from canonical started and finished timestamps", () => {
  const sessionId = "ses_state_late_terminal_duration";
  const traceId = "trc_state_late_terminal_duration";
  const messageId = "msg_state_late_terminal_duration";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "message.upsert", sessionId, message: agentMessage({ id: messageId, status: "running", traceId, sessionId, startedAt: "2026-07-01T00:00:00.000Z", updatedAt: "2026-07-01T00:00:01.000Z" }) });
  state = reduceWorkbenchServerState(state, { type: "message.upsert", sessionId, message: agentMessage({ id: messageId, status: "completed", text: "final answer", finalResponse: { text: "final answer" }, traceId, sessionId, startedAt: "2026-07-01T00:00:00.000Z", finishedAt: "2026-07-01T00:00:05.000Z", updatedAt: "2026-07-01T00:17:25.000Z" }) });

  const [message] = selectActiveMessages(state, sessionId);
  assert.equal(message?.finishedAt, "2026-07-01T00:00:05.000Z");
  assert.equal(message?.durationMs, 5000);
});

test("late terminal receipt time cannot synthesize duration without canonical finish time", () => {
  const sessionId = "ses_state_late_terminal_unknown_duration";
  const traceId = "trc_state_late_terminal_unknown_duration";
  const messageId = "msg_state_late_terminal_unknown_duration";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "message.upsert", sessionId, message: agentMessage({ id: messageId, status: "running", traceId, sessionId, startedAt: "2026-07-01T00:00:00.000Z" }) });
  state = reduceWorkbenchServerState(state, { type: "message.upsert", sessionId, message: agentMessage({ id: messageId, status: "completed", text: "final answer", finalResponse: { text: "final answer" }, traceId, sessionId, startedAt: "2026-07-01T00:00:00.000Z", lastEventAt: "2026-07-01T00:17:25.000Z", updatedAt: "2026-07-01T00:17:25.000Z" }) });

  const [message] = selectActiveMessages(state, sessionId);
  assert.equal(message?.finishedAt ?? null, null);
  assert.equal(message?.durationMs ?? null, null);
});

test("server state reducer keeps session detail reference stable when detail has no semantic change", () => {
  const sessionId = "ses_state_stable_detail";
  const traceId = "trc_state_stable_detail";
  let state = createWorkbenchServerState();
  const messages = [agentMessage({ id: "msg_state_stable_detail", status: "running", traceId, sessionId })];
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", lastTraceId: traceId, updatedAt: "2026-07-01T00:00:00.000Z", messageCount: 1, messages }) });
  const first = state;
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", lastTraceId: traceId, updatedAt: "2026-07-01T00:00:00.000Z", messageCount: 1, messages: [...messages] }) });

  assert.equal(state, first);
  assert.equal(selectActiveMessages(state, sessionId), selectActiveMessages(first, sessionId));
});

test("server state reducer skips equivalent message upsert and loadedAt-only turn status", () => {
  const sessionId = "ses_state_realtime_noop";
  const traceId = "trc_state_realtime_noop";
  let state = createWorkbenchServerState();
  const message = agentMessage({ id: "msg_state_realtime_noop", status: "running", traceId, sessionId, updatedAt: "2026-07-01T00:00:01.000Z" });
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", lastTraceId: traceId, messageCount: 1, messages: [message] }) });
  state = reduceWorkbenchServerState(state, { type: "turn.status", turn: { traceId, status: "running", running: true, terminal: false, sessionId, updatedAt: "2026-07-01T00:00:01.000Z", loadedAt: "2026-07-01T00:00:01.000Z" } });
  state = reduceWorkbenchServerState(state, { type: "message.upsert", sessionId, message: { ...message } });
  const first = state;
  state = reduceWorkbenchServerState(state, { type: "message.upsert", sessionId, message: { ...message } });
  state = reduceWorkbenchServerState(state, { type: "turn.status", turn: { traceId, status: "running", running: true, terminal: false, sessionId, updatedAt: "2026-07-01T00:00:01.000Z", loadedAt: "2026-07-01T00:00:02.000Z" } });

  assert.equal(state, first);
  assert.equal(selectActiveMessages(state, sessionId), selectActiveMessages(first, sessionId));
});

test("server state reducer keeps failed message without terminal body unsealed", () => {
  const sessionId = "ses_state_failed_without_body";
  const traceId = "trc_state_failed_without_body";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [agentMessage({ id: "msg_state_failed_without_body", status: "running", traceId, sessionId, startedAt: "2026-07-01T00:00:00.000Z" })] });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [agentMessage({ id: "msg_state_failed_without_body", status: "failed", traceId, sessionId, startedAt: "2026-07-01T00:00:00.000Z", finishedAt: "2026-07-01T00:00:02.000Z", durationMs: 2000 })] });

  const [message] = selectActiveMessages(state, sessionId);
  assert.ok(message);
  assert.equal(message.status, "running");
  assert.equal(message.finishedAt, null);
  assert.equal(message.durationMs, null);
  assert.notEqual(selectSessionStatusAuthority(state)[sessionId]?.status, "failed");
});

test("server state reducer seals completed message when final response exists", () => {
  const sessionId = "ses_state_completed_with_final";
  const traceId = "trc_state_completed_with_final";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [agentMessage({ id: "msg_state_completed_with_final", status: "completed", traceId, sessionId, text: "final answer", finishedAt: "2026-07-01T00:00:02.000Z", durationMs: 2000 })] });
  assert.equal(selectSessionStatusAuthority(state)[sessionId]?.status, "running");
  state = reduceWorkbenchServerState(state, { type: "turn.status", turn: { traceId, status: "completed", running: false, terminal: true, sessionId, updatedAt: "2026-07-01T00:00:02.000Z" } });

  const [message] = selectActiveMessages(state, sessionId);
  assert.ok(message);
  assert.equal(message.status, "completed");
  assert.equal(message.text, "final answer");
  assert.equal(selectSessionStatusAuthority(state)[sessionId]?.status, "completed");
});

test("server state reducer upserts terminal authority when the session page has no agent row", () => {
  const sessionId = "ses_state_terminal_upsert_missing_agent";
  const traceId = "trc_state_terminal_upsert_missing_agent";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [userMessage({ id: "msg_state_terminal_upsert_user", text: "run it", sessionId, traceId })] });
  state = reduceWorkbenchServerState(state, { type: "message.upsert", sessionId, message: agentMessage({ id: "msg_state_terminal_upsert_agent", status: "completed", text: "final answer from turn authority", finalResponse: { text: "final answer from turn authority" }, sessionId, traceId }) });

  const messages = selectActiveMessages(state, sessionId);
  assert.equal(messages.length, 2);
  const terminal = messages.find((message) => message.role === "agent" && message.traceId === traceId);
  assert.ok(terminal);
  assert.equal(terminal.status, "completed");
  assert.equal(terminal.text, "final answer from turn authority");
});

test("server state reducer appends authoritative agent snapshot after durable user snapshot", () => {
  const sessionId = "ses_state_cross_page_snapshot";
  const traceId = "trc_state_cross_page_snapshot";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "message.snapshot", sessionId, message: userMessage({ id: "msg_state_cross_page_user", text: "run it", sessionId, traceId }) });
  state = reduceWorkbenchServerState(state, { type: "message.snapshot", sessionId, message: agentMessage({ id: "msg_state_cross_page_agent", status: "running", sessionId, traceId }) });

  const messages = selectActiveMessages(state, sessionId);
  assert.equal(messages.length, 2);
  assert.equal(messages[0]?.role, "user");
  assert.equal(messages[1]?.role, "agent");
});

test("server state reducer keeps sealed terminal messages when a stale partial snapshot arrives", () => {
  const sessionId = "ses_state_partial_snapshot";
  const traceId = "trc_state_partial_snapshot";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [
    userMessage({ id: "msg_state_partial_user_1", text: "one", sessionId, traceId: "trc_state_partial_1" }),
    agentMessage({ id: "msg_state_partial_agent_1", status: "completed", text: "one done", sessionId, traceId: "trc_state_partial_1" }),
    userMessage({ id: "msg_state_partial_user_2", text: "two", sessionId, traceId }),
    agentMessage({ id: "msg_state_partial_agent_2", status: "completed", text: "final answer", finalResponse: { text: "final answer" }, sessionId, traceId, finishedAt: "2026-07-01T00:00:04.000Z", durationMs: 4000 })
  ] });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [
    userMessage({ id: "msg_state_partial_user_1", text: "one", sessionId, traceId: "trc_state_partial_1" }),
    agentMessage({ id: "msg_state_partial_agent_1", status: "completed", text: "one done", sessionId, traceId: "trc_state_partial_1" })
  ] });

  const messages = selectActiveMessages(state, sessionId);
  assert.equal(messages.length, 4);
  const terminal = messages.find((message) => message.id === "msg_state_partial_agent_2");
  assert.ok(terminal);
  assert.equal(terminal.status, "completed");
  assert.equal(terminal.text, "final answer");
  assert.equal((terminal.finalResponse as { text?: string } | undefined)?.text, "final answer");
  assert.equal(selectSessionStatusAuthority(state)[sessionId]?.status, "running");
  state = reduceWorkbenchServerState(state, { type: "turn.status", turn: { traceId, status: "completed", running: false, terminal: true, sessionId, updatedAt: "2026-07-01T00:00:04.000Z" } });
  assert.equal(selectSessionStatusAuthority(state)[sessionId]?.status, "completed");
});

test("server state reducer promotes failed terminal message authority and ignores running list rollback", () => {
  const sessionId = "ses_state_failed_terminal_authority";
  const traceId = "trc_state_failed_terminal_authority";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "running", lastTraceId: traceId, messages: [] }) });
  state = reduceWorkbenchServerState(state, { type: "session.messages", sessionId, messages: [
    userMessage({ id: "msg_state_failed_user", text: "one", sessionId, traceId }),
    agentMessage({ id: "msg_state_failed_agent", status: "failed", text: "Workbench terminal failed: provider-stream-disconnected", finalResponse: { text: "Workbench terminal failed: provider-stream-disconnected" }, sessionId, traceId, finishedAt: "2026-07-01T00:00:05.000Z", durationMs: 5000 })
  ] });

  assert.equal(selectSessionStatusAuthority(state)[sessionId]?.status, "running");
  state = reduceWorkbenchServerState(state, { type: "turn.status", turn: { traceId, status: "failed", running: false, terminal: true, sessionId, updatedAt: "2026-07-01T00:00:05.000Z" } });
  assert.equal(selectSessionStatusAuthority(state)[sessionId]?.status, "failed");
  state = reduceWorkbenchServerState(state, { type: "session.list", sessions: [sessionRecord({ sessionId, status: "running", lastTraceId: traceId, messages: [] })] });

  assert.equal(selectSessionStatusAuthority(state)[sessionId]?.status, "failed");
  assert.equal(selectActiveMessages(state, sessionId).find((message) => message.id === "msg_state_failed_agent")?.status, "failed");
});

test("server state reducer keeps newer running turn authority over older terminal message", () => {
  const sessionId = "ses_state_new_turn_running";
  const oldTraceId = "trc_state_old_completed";
  const newTraceId = "trc_state_new_running";
  let state = createWorkbenchServerState();
  state = reduceWorkbenchServerState(state, {
    type: "session.detail",
    session: {
      sessionId,
      status: "completed",
      lastTraceId: oldTraceId,
      messages: [
        userMessage({ id: "msg_state_old_user", text: "one", sessionId, traceId: oldTraceId }),
        agentMessage({ id: "msg_state_old_agent", status: "completed", text: "one done", finalResponse: { text: "one done" }, sessionId, traceId: oldTraceId }),
        userMessage({ id: "msg_state_new_user", text: "two", sessionId, traceId: newTraceId }),
        agentMessage({ id: "msg_state_new_agent", status: "running", text: "", sessionId, traceId: newTraceId })
      ]
    }
  });
  state = reduceWorkbenchServerState(state, { type: "turn.status", turn: { traceId: newTraceId, status: "running", running: true, terminal: false, sessionId, updatedAt: "2026-07-02T00:00:04.000Z" } });
  state = reduceWorkbenchServerState(state, { type: "session.status", session: { sessionId, status: "running", lastTraceId: newTraceId, updatedAt: "2026-07-02T00:00:04.000Z" } });
  state = reduceWorkbenchServerState(state, { type: "session.list", sessions: [sessionRecord({ sessionId, status: "completed", lastTraceId: oldTraceId, messages: [] })] });
  state = reduceWorkbenchServerState(state, { type: "session.detail", session: sessionRecord({ sessionId, status: "completed", lastTraceId: oldTraceId, messages: [] }) });
  state = reduceWorkbenchServerState(state, {
    type: "session.messages",
    sessionId,
    messages: [
      userMessage({ id: "msg_state_old_user", text: "one", sessionId, traceId: oldTraceId }),
      agentMessage({ id: "msg_state_old_agent", status: "completed", text: "one done", finalResponse: { text: "one done" }, sessionId, traceId: oldTraceId }),
      userMessage({ id: "msg_state_new_user", text: "two", sessionId, traceId: newTraceId }),
      agentMessage({ id: "msg_state_new_agent", status: "running", text: "", sessionId, traceId: newTraceId })
    ]
  });

  const authority = selectSessionStatusAuthority(state)[sessionId];
  assert.equal(authority?.status, "running");
  assert.equal(authority?.lastTraceId, newTraceId);
});
