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

import type { ChatMessage, WorkbenchSessionRecord } from "../types";
import { resolveCancelableAgentMessage, resolveComposerState, sessionDetailLoadingProgress, sessionToSessionTab, shouldReadTerminalTraceDetail, shouldShowSessionDetailLoading } from "./workbench-session";
import type { TurnStatusAuthority } from "./workbench-session";

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

function turnStatus(input: TurnStatusAuthority): TurnStatusAuthority {
  return input;
}

test("composer treats completed message without final response as unsealed running turn", () => {
  const traceId = "trc_session_completed_without_final";
  const sessionId = "ses_session_completed_without_final";
  const message = agentMessage({ id: "msg_session_completed_without_final", status: "completed", traceId, sessionId });
  const composer = resolveComposerState({
    messages: [message],
    activeSessionId: sessionId,
    chatPending: true,
    currentRequest: { traceId, sessionId, status: "running" },
    turnStatusAuthority: { [traceId]: turnStatus({ traceId, sessionId, status: "completed", terminal: true, running: false }) }
  });
  assert.equal(composer.submitMode, "steer");
  assert.equal(composer.targetTraceId, traceId);
});

test("composer treats failed message without terminal body as unsealed running turn", () => {
  const traceId = "trc_session_failed_without_body";
  const sessionId = "ses_session_failed_without_body";
  const message = agentMessage({ id: "msg_session_failed_without_body", status: "failed", traceId, sessionId });
  const composer = resolveComposerState({
    messages: [message],
    activeSessionId: sessionId,
    chatPending: true,
    currentRequest: { traceId, sessionId, status: "running" },
    turnStatusAuthority: { [traceId]: turnStatus({ traceId, sessionId, status: "failed", terminal: true, running: false }) }
  });
  assert.equal(composer.submitMode, "steer");
  assert.equal(composer.targetTraceId, traceId);
});

test("composer treats failed message with terminal body as sealed terminal turn", () => {
  const traceId = "trc_session_failed_with_body";
  const sessionId = "ses_session_failed_with_body";
  const message = agentMessage({ id: "msg_session_failed_with_body", status: "failed", traceId, sessionId, text: "Workbench terminal failed: provider-stream-disconnected" });
  const composer = resolveComposerState({
    messages: [message],
    activeSessionId: sessionId,
    chatPending: true,
    currentRequest: { traceId, sessionId, status: "running" },
    turnStatusAuthority: { [traceId]: turnStatus({ traceId, sessionId, status: "failed", terminal: true, running: false }) }
  });
  assert.equal(composer.submitMode, "turn");
  assert.equal(composer.targetTraceId, null);
});

test("composer keeps turn submission available while Kafka replay connects", () => {
  const sessionId = "ses_realtime_connecting";
  const connecting = resolveComposerState({ messages: [], activeSessionId: sessionId, chatPending: false, realtimeReady: false });
  const connected = resolveComposerState({ messages: [], activeSessionId: sessionId, chatPending: false, realtimeReady: true });

  assert.equal(connecting.disabled, false);
  assert.equal(connecting.disabledReason, null);
  assert.equal(connecting.sessionId, sessionId);
  assert.equal(connected.disabled, false);
});

test("session detail keeps a visible loading state until Kafka replay connects", () => {
  assert.equal(shouldShowSessionDetailLoading({
    loading: false,
    messageCount: 0,
    replayLoadingSessionId: "ses_refresh",
    activeSessionId: "ses_refresh"
  }), true);
  assert.equal(shouldShowSessionDetailLoading({
    loading: false,
    messageCount: 2,
    sessionDetailLoadingId: "ses_refresh",
    replayLoadingSessionId: "ses_refresh",
    activeSessionId: "ses_refresh"
  }), false);
  assert.equal(shouldShowSessionDetailLoading({
    loading: false,
    messageCount: 2,
    replayLoadingSessionId: null,
    activeSessionId: "ses_refresh"
  }), false);
  assert.equal(shouldShowSessionDetailLoading({
    loading: false,
    messageCount: 0,
    replayLoadingSessionId: "ses_previous",
    activeSessionId: "ses_refresh"
  }), false);
});

test("session detail exposes transport progress and renders replayed messages before full load", () => {
  assert.deepEqual(sessionDetailLoadingProgress({
    loading: true,
    messageCount: 0,
    sessionDetailLoadingId: "ses_progress",
    activeSessionId: "ses_progress",
    transportPhase: "connecting"
  }), {
    active: true,
    blocking: true,
    phase: "selecting",
    label: "正在选择会话并建立实时连接..."
  });

  assert.deepEqual(sessionDetailLoadingProgress({
    loading: true,
    messageCount: 3,
    sessionDetailLoadingId: "ses_progress",
    replayLoadingSessionId: "ses_progress",
    activeSessionId: "ses_progress",
    transportPhase: "event"
  }), {
    active: true,
    blocking: false,
    phase: "replaying",
    label: "实时流已连接，已显示 3 条，继续回放..."
  });
});

test("cancel target remains available for completed message until final response exists", () => {
  const traceId = "trc_cancel_completed_without_final";
  const sessionId = "ses_cancel_completed_without_final";
  const unsealed = agentMessage({ id: "msg_cancel_completed_without_final", status: "completed", traceId, sessionId });
  const sealed = agentMessage({ ...unsealed, id: "msg_cancel_completed_with_final", text: "final answer" });

  assert.equal(resolveCancelableAgentMessage({ messages: [unsealed], targetTraceId: traceId, targetSessionId: sessionId, turnStatusAuthority: { [traceId]: turnStatus({ traceId, sessionId, status: "running", running: true, terminal: false }) } })?.id, unsealed.id);
  assert.equal(resolveCancelableAgentMessage({ messages: [sealed], targetTraceId: traceId, targetSessionId: sessionId, turnStatusAuthority: { [traceId]: turnStatus({ traceId, sessionId, status: "running", running: true, terminal: false }) } }), null);
});

test("cancel target remains available for failed message until terminal body exists", () => {
  const traceId = "trc_cancel_failed_without_body";
  const sessionId = "ses_cancel_failed_without_body";
  const unsealed = agentMessage({ id: "msg_cancel_failed_without_body", status: "failed", traceId, sessionId });
  const sealed = agentMessage({ ...unsealed, id: "msg_cancel_failed_with_body", text: "Workbench terminal failed: provider-stream-disconnected" });

  assert.equal(resolveCancelableAgentMessage({ messages: [unsealed], targetTraceId: traceId, targetSessionId: sessionId, turnStatusAuthority: { [traceId]: turnStatus({ traceId, sessionId, status: "running", running: true, terminal: false }) } })?.id, unsealed.id);
  assert.equal(resolveCancelableAgentMessage({ messages: [sealed], targetTraceId: traceId, targetSessionId: sessionId, turnStatusAuthority: { [traceId]: turnStatus({ traceId, sessionId, status: "running", running: true, terminal: false }) } }), null);
});

test("terminal detail read skips sealed terminal trace authority", () => {
  const sessionId = "ses_terminal_detail_sealed";
  const traceId = "trc_terminal_detail_sealed";
  const sealed = agentMessage({ id: "msg_terminal_detail_sealed", traceId, sessionId, status: "completed", text: "final response" });
  const unsealed = agentMessage({ id: "msg_terminal_detail_unsealed", traceId, sessionId, status: "completed" });
  const turnStatusAuthority = { [traceId]: turnStatus({ traceId, sessionId, status: "completed", running: false, terminal: true }) };

  assert.equal(shouldReadTerminalTraceDetail({ traceId, messages: [sealed], turnStatusAuthority }), false);
  assert.equal(shouldReadTerminalTraceDetail({ traceId, messages: [unsealed], turnStatusAuthority }), true);
  assert.equal(shouldReadTerminalTraceDetail({ traceId, messages: [], turnStatusAuthority }), true);
});

test("session rail status prefers newer running trace over stale completed authority", () => {
  const sessionId = "ses_session_rail_stale";
  const older = agentMessage({ id: "msg_session_rail_old", traceId: "trc_session_rail_old", sessionId, status: "completed", text: "previous final" });
  const current = agentMessage({ id: "msg_session_rail_current", traceId: "trc_session_rail_current", sessionId, status: "running" });
  const session: WorkbenchSessionRecord = { sessionId, status: "completed", lastTraceId: older.traceId, messageCount: 2, messages: [older, current] };
  const tab = sessionToSessionTab(session, sessionId, { [sessionId]: { sessionId, status: "completed", lastTraceId: older.traceId } });

  assert.equal(tab.status, "running");
  assert.equal(tab.running, true);
  assert.equal(tab.lastTraceId, current.traceId);
});

test("session rail update time uses the last user send time instead of replay receipt time", () => {
  const sessionId = "ses_session_rail_user_time";
  const user = {
    id: "msg_session_rail_user_time",
    messageId: "msg_session_rail_user_time",
    role: "user",
    title: "用户",
    text: "hi",
    status: "sent",
    traceId: "trc_session_rail_user_time",
    sessionId,
    createdAt: "2026-07-20T00:00:00.000Z",
    updatedAt: "2026-07-20T10:30:00.000Z"
  } as ChatMessage;
  const tab = sessionToSessionTab({
    sessionId,
    status: "completed",
    lastUserMessageAt: "2026-07-20T10:30:00.000Z",
    updatedAt: "2026-07-20T10:30:00.000Z",
    messages: [user]
  }, sessionId);

  assert.equal(tab.updatedAt, "2026-07-20T00:00:00.000Z");
});

test("session rail uses a neutral label when Kafka has no user title event", () => {
  const sessionId = "ses_session_rail_without_kafka_title";
  const tab = sessionToSessionTab({ sessionId, messages: [] }, null);

  assert.equal(tab.label, "新建会话");
  assert.equal(tab.tooltip, `新建会话 · ${sessionId}`);
});

test("session rail keeps the Kafka user preview as its visible title", () => {
  const sessionId = "ses_session_rail_with_kafka_title";
  const tab = sessionToSessionTab({ sessionId, firstUserMessagePreview: "hi", messages: [] }, null);

  assert.equal(tab.label, "hi");
  assert.equal(tab.tooltip, `hi · ${sessionId}`);
});
