import type { AgentChatReply, AgentRunProvenance, CodeAgentAvailability, LiveSurface, RunnerTrace, TraceEvent } from "../types/domain";
import { firstNonEmptyString, jsonPreview, nonEmptyString, shortToken } from "../utils";

export interface CodeAgentStatusSummary {
  kind: "long-lived-session" | "blocked" | "skill-cli-api-blocked" | "read-only-session-tools" | "stateless-one-shot" | "degraded" | "unverified";
  tone: "ok" | "warn" | "blocked" | "source" | "dev-live";
  label: string;
  icon: string;
  provider: string;
  mode: string;
  backend: string;
  providerModeBackend: string;
  codeAgentStatus: string;
  capabilityLevel: string;
  sessionMode: string;
  sessionId: string;
  threadId: string;
  sessionStatus: string;
  sessionLifecycleStatus: string;
  sessionLifecycleHint: string;
  sessionLifecycleLabel: string;
  sessionSummary: string;
  sessionIdStatus: string;
  workspace: string;
  sandbox: string;
  runnerKind: string;
  protocol: string;
  implementationType: string;
  providerTraceCommand: string;
  providerTraceTerminalStatus: string;
  providerTraceFailureKind: string;
  runtimePathLabel: string;
  runtimePathSummary: string;
  runtimePathTone: "ok" | "warn" | "blocked" | "source" | "dev-live";
  toolCalls: string;
  skills: string;
  runnerTrace: string;
  readinessBlockers: string[];
  blockersLabel: string;
  lastTraceId: string;
  deploymentRevision: string;
  fields: Record<string, string>;
}

export interface CodeAgentRuntimePath {
  protocol: string;
  implementationType: string;
  command: string;
  terminalStatus: string;
  failureKind: string;
  label: string;
  summary: string;
  tone: "ok" | "warn" | "blocked" | "source" | "dev-live";
  rows: { label: string; value: string }[];
  missingFields: string[];
}

const CODEX_APP_SERVER_RUNNER = "codex-app-server-stdio-runner";
const CODEX_APP_SERVER_SESSION_MODE = "codex-app-server-stdio-long-lived";
const CODEX_APP_SERVER_IMPLEMENTATION = "repo-owned-codex-app-server-stdio-session";
const CODEX_APP_SERVER_PROTOCOL = "codex-app-server-jsonrpc-stdio";

function asRecord(value: unknown): Record<string, unknown> | null {
  return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null;
}

function asString(value: unknown): string | null {
  return typeof value === "string" && value.trim() ? value.trim() : null;
}

function pickString(...values: unknown[]): string | null {
  for (const value of values) {
    const text = asString(value);
    if (text) return text;
  }
  return null;
}

function readField(payload: Record<string, unknown> | null, path: readonly string[]): string | null {
  let cursor: unknown = payload;
  for (const key of path) {
    if (!cursor || typeof cursor !== "object") return null;
    cursor = (cursor as Record<string, unknown>)[key];
  }
  return asString(cursor);
}

function safeField(value: string | null): string {
  return value ?? "未观测";
}

function compactJoin(values: ReadonlyArray<string | null>, separator: string): string {
  const parts = values.filter((value): value is string => Boolean(value && value !== "未观测" && value !== "字段缺失：证据不足"));
  return parts.length > 0 ? parts.join(separator) : "未观测";
}

function unsafeGreenForNonReady(tone: CodeAgentStatusSummary["tone"], blockers: string[]): boolean {
  return tone === "ok" && blockers.length > 0;
}

export function classifyCodeAgentStatusSummary({ availability = null, latestMessage = null, live = null }: { availability?: CodeAgentAvailability | null; latestMessage?: Record<string, unknown> | null; live?: LiveSurface | null } = {}): CodeAgentStatusSummary {
  const observedAvailability = availability ?? (live?.healthLive.data?.codeAgent as CodeAgentAvailability | undefined) ?? null;
  const messageRecord = latestMessage ? asRecord(latestMessage) : null;
  const availabilityRecord = observedAvailability ? asRecord(observedAvailability) : null;
  const messageStatus = pickString(messageRecord?.status as string | undefined, availabilityRecord?.status as string | undefined);
  const blockers = readinessBlockers(availabilityRecord, messageRecord);
  const classification = classifyPayload(availabilityRecord, messageRecord, blockers, messageStatus);
  const safeTone = unsafeGreenForNonReady(classification.tone, blockers) ? "warn" : classification.tone;

  const provider = pickString(availabilityRecord?.provider as string | undefined, observedAvailability?.provider, messageRecord?.provider as string | undefined);
  const mode = pickString(availabilityRecord?.mode as string | undefined, messageRecord?.providerTrace && (messageRecord.providerTrace as Record<string, unknown>).mode as string | undefined, observedAvailability?.mode);
  const backend = pickString(availabilityRecord?.backend as string | undefined, observedAvailability?.backend, messageRecord?.backend as string | undefined);
  const codeAgentStatus = pickString(messageStatus, availabilityRecord?.readinessStatus as string | undefined, observedAvailability?.status);
  const capabilityLevel = pickString(availabilityRecord?.capabilityLevel as string | undefined, messageRecord?.capabilityLevel as string | undefined, observedAvailability?.capabilityLevel);
  const sessionMode = pickString(availabilityRecord?.sessionMode as string | undefined, messageRecord?.sessionMode as string | undefined, observedAvailability?.sessionMode);
  const sessionId = pickString(messageRecord?.sessionId as string | undefined, observedAvailability?.sessionId);
  const threadId = pickString(messageRecord?.threadId as string | undefined, observedAvailability?.threadId);
  const sessionStatus = pickString(messageRecord?.status as string | undefined, messageRecord?.sessionStatus as string | undefined);
  const lifecycle = sessionLifecycleSummary(messageStatus, blockers);
  const workspace = pickString(messageRecord?.workspace as string | undefined, messageRecord?.workspaceId as string | undefined);
  const sandbox = pickString(messageRecord?.sandbox as string | undefined, messageRecord?.runner && (messageRecord.runner as Record<string, unknown>).sandbox as string | undefined);
  const runnerKind = pickString(messageRecord?.runnerKind as string | undefined, messageRecord?.runner && (messageRecord.runner as Record<string, unknown>).kind as string | undefined);
  const runtimePath = codeAgentRuntimePathFromMessage({ role: "agent", ...(messageRecord ?? {}) });
  const lastTraceId = pickString(
    messageRecord?.traceId as string | undefined,
    messageRecord?.lastTraceId as string | undefined
  ) ?? "未观测";
  const deploymentRevision = currentDeploymentRevision(live);

  const toolCalls = compactToolCallsSummary(messageRecord?.toolCalls);
  const skills = compactSkillsSummary(messageRecord?.skills);
  const runnerTrace = compactRunnerTraceSummary(messageRecord?.runnerTrace);

  return {
    ...classification,
    tone: safeTone,
    provider: safeField(provider),
    mode: safeField(mode),
    backend: safeField(backend),
    providerModeBackend: compactJoin([provider, mode, backend], " / "),
    codeAgentStatus: safeField(codeAgentStatus),
    capabilityLevel: safeField(capabilityLevel),
    sessionMode: safeField(sessionMode),
    sessionId: safeField(sessionId),
    threadId: safeField(threadId),
    sessionStatus: safeField(sessionStatus),
    sessionLifecycleStatus: lifecycle.status,
    sessionLifecycleHint: lifecycle.hint,
    sessionLifecycleLabel: lifecycle.label,
    sessionSummary: compactJoin([sessionMode, sessionStatus, lifecycle.status], " / "),
    sessionIdStatus: compactJoin([sessionId, threadId, sessionStatus, lifecycle.status], " / "),
    workspace: safeField(workspace),
    sandbox: safeField(sandbox),
    runnerKind: safeField(runnerKind),
    protocol: runtimePath.protocol,
    implementationType: runtimePath.implementationType,
    providerTraceCommand: runtimePath.command,
    providerTraceTerminalStatus: runtimePath.terminalStatus,
    providerTraceFailureKind: runtimePath.failureKind,
    runtimePathLabel: runtimePath.label,
    runtimePathSummary: runtimePath.summary,
    runtimePathTone: runtimePath.tone,
    toolCalls,
    skills,
    runnerTrace,
    readinessBlockers: blockers,
    blockersLabel: blockers.length > 0 ? blockers.join(", ") : "无",
    lastTraceId: lastTraceId || "未观测",
    deploymentRevision,
    fields: {
      provider: safeField(provider),
      mode: safeField(mode),
      backend: safeField(backend),
      codeAgentStatus: safeField(codeAgentStatus),
      capabilityLevel: safeField(capabilityLevel),
      sessionMode: safeField(sessionMode),
      sessionId: safeField(sessionId),
      threadId: safeField(threadId),
      sessionStatus: safeField(sessionStatus),
      runnerKind: safeField(runnerKind),
      protocol: runtimePath.protocol,
      implementationType: runtimePath.implementationType,
      providerTraceCommand: runtimePath.command,
      providerTraceTerminalStatus: runtimePath.terminalStatus,
      providerTraceFailureKind: runtimePath.failureKind,
      toolCalls,
      skills,
      runnerTrace
    }
  };
}

function classifyPayload(availability: Record<string, unknown> | null, message: Record<string, unknown> | null, blockers: string[], messageStatus: string | null): Pick<CodeAgentStatusSummary, "kind" | "tone" | "label" | "icon"> {
  const providerBackend = pickString(availability?.provider as string | undefined, message?.provider as string | undefined);
  const sessionRunnerField = availability ? (availability as Record<string, unknown>).sessionRunner : null;
  const sessionRunnerKind = pickString(
    availability?.sessionRunnerKind as string | undefined,
    typeof sessionRunnerField === "string" ? sessionRunnerField : asString(sessionRunnerField)
  );
  const sessionMode = pickString(availability?.sessionMode as string | undefined, message?.sessionMode as string | undefined);
  if (blockers.length > 0) {
    return { kind: "blocked", tone: "blocked", label: "Code Agent 阻塞", icon: "×" };
  }
  if (providerBackend && (sessionRunnerKind === CODEX_APP_SERVER_RUNNER || sessionMode === CODEX_APP_SERVER_SESSION_MODE)) {
    return { kind: "long-lived-session", tone: "ok", label: "长会话可用", icon: "●" };
  }
  if (providerBackend && /codex-stdio|codex-app-server/iu.test(providerBackend)) {
    return { kind: "stateless-one-shot", tone: "warn", label: "单次 Code Agent", icon: "○" };
  }
  if (providerBackend) {
    return { kind: "read-only-session-tools", tone: "warn", label: "Provider 受限", icon: "!" };
  }
  if (messageStatus === "running" || messageStatus === "pending") {
    return { kind: "degraded", tone: "source", label: "等待后端", icon: "…" };
  }
  return { kind: "unverified", tone: "source", label: "未观测", icon: "○" };
}

function readinessBlockers(availability: Record<string, unknown> | null, message: Record<string, unknown> | null): string[] {
  const blockers: string[] = [];
  const seen = new Set<string>();
  function add(value: unknown, prefix = ""): void {
    if (typeof value === "string" && value.trim() && value !== "ready" && value !== "ok") {
      const key = `${prefix}${value}`;
      if (!seen.has(key)) {
        seen.add(key);
        blockers.push(key);
      }
    }
  }
  add((availability?.currentBlocker as string | undefined) ?? (availability?.blocker as string | undefined));
  for (const blocker of asArray(availability?.currentBlockers)) add(blocker);
  for (const blocker of asArray(availability?.blockerCodes)) add(blocker);
  for (const blocker of asArray(availability?.blockers)) {
    if (blocker && typeof blocker === "object") {
      const code = (blocker as Record<string, unknown>).code;
      const message = (blocker as Record<string, unknown>).message;
      if (typeof code === "string") add(code);
      if (typeof message === "string") add(message);
    }
  }
  if (message) {
    const error = asRecord(message.error);
    add(error?.code as string | undefined);
    add(error?.message as string | undefined);
  }
  return blockers;
}

function sessionLifecycleSummary(messageStatus: string | null, blockers: string[]): { status: string; hint: string; label: string } {
  if (blockers.length > 0) return { status: "阻塞", hint: "等待补齐 provider/session 阻断条件", label: "会话受阻" };
  if (messageStatus === "running" || messageStatus === "pending") return { status: "运行", hint: "正在等待后端响应", label: "会话运行中" };
  if (messageStatus === "completed") return { status: "完成", hint: "可继续 steer 或新建 session", label: "会话已就绪" };
  if (messageStatus === "failed" || messageStatus === "blocked" || messageStatus === "timeout") return { status: "失败", hint: "可新建 session 后重试", label: "会话已失败" };
  if (messageStatus === "canceled") return { status: "已取消", hint: "可重新发起", label: "会话已取消" };
  return { status: "未观测", hint: "尚未发起 Code Agent 请求", label: "会话未观测" };
}

function asArray<T>(value: unknown): T[] {
  return Array.isArray(value) ? value as T[] : [];
}

function currentDeploymentRevision(live: LiveSurface | null): string {
  const liveRevision = live?.healthLive.data?.revision ?? live?.health.data?.revision ?? null;
  return liveRevision ? String(liveRevision).slice(0, 12) : "未观测";
}

export function codeAgentRuntimePathFromMessage(message: Record<string, unknown> | { role?: string; [key: string]: unknown }): CodeAgentRuntimePath {
  const record = asRecord(message) ?? {};
  const provider = pickString(record.provider as string | undefined, record && asString((record as Record<string, unknown>).provider));
  const runnerKind = pickString(record.runnerKind as string | undefined, record.runner && (record.runner as Record<string, unknown>).kind as string | undefined, record.agentRun && (record.agentRun as AgentRunProvenance).adapter as string | undefined);
  const protocol = pickString(record.protocol as string | undefined, record.protocol as string | undefined);
  const implementationType = pickString(record.implementationType as string | undefined, record.implementationType as string | undefined);
  const providerTrace = asRecord(record.providerTrace) ?? {};
  const command = pickString(providerTrace.command as string | undefined, providerTrace.commandId as string | undefined, record.agentRun && (record.agentRun as AgentRunProvenance).commandId as string | undefined);
  const terminalStatus = pickString(providerTrace.terminalStatus as string | undefined, providerTrace.terminal as string | undefined, record.agentRun && (record.agentRun as AgentRunProvenance).terminalStatus as string | undefined);
  const failureKind = pickString(providerTrace.failureKind as string | undefined, providerTrace.failure as string | undefined);

  const isCodexAppServer = runnerKind === CODEX_APP_SERVER_RUNNER || provider === "codex-stdio" || /codex-app-server/iu.test(protocol ?? "") || /codex-app-server/iu.test(implementationType ?? "");
  const label = isCodexAppServer ? "repo-owned codex app-server stdio" : (provider ?? "运行路径证据不足");
  const tone: CodeAgentRuntimePath["tone"] = isCodexAppServer ? "ok" : provider ? "warn" : "blocked";
  const summary = isCodexAppServer
    ? "provider / runner / protocol / implementationType 与 codex app-server stdio 长会话一致；trace 链路可见。"
    : provider
      ? `provider=${provider} 但 runner / protocol 字段未对齐；runtime 路径证据不足。`
      : "providerTrace 未观测；不会推断完整 Code Agent。";
  const rows: { label: string; value: string }[] = [
    { label: "provider", value: safeField(provider) },
    { label: "runnerKind", value: safeField(runnerKind) },
    { label: "protocol", value: safeField(protocol ?? (isCodexAppServer ? CODEX_APP_SERVER_PROTOCOL : null)) },
    { label: "implementationType", value: safeField(implementationType ?? (isCodexAppServer ? CODEX_APP_SERVER_IMPLEMENTATION : null)) },
    { label: "providerTrace.command", value: safeField(command) },
    { label: "providerTrace.terminalStatus", value: safeField(terminalStatus) },
    { label: "providerTrace.failureKind", value: safeField(failureKind) }
  ];
  const missingFields = rows.filter((row) => row.value === "未观测" || row.value === "字段缺失：证据不足").map((row) => row.label);
  return {
    protocol: rows[2]?.value ?? "未观测",
    implementationType: rows[3]?.value ?? "未观测",
    command: rows[4]?.value ?? "未观测",
    terminalStatus: rows[5]?.value ?? "未观测",
    failureKind: rows[6]?.value ?? "未观测",
    label,
    summary,
    tone,
    rows,
    missingFields
  };
}

function compactToolCallsSummary(toolCalls: unknown): string {
  if (!toolCalls) return "none";
  if (typeof toolCalls === "string") return toolCalls;
  if (Array.isArray(toolCalls)) {
    if (toolCalls.length === 0) return "none";
    return toolCalls.map((tool) => {
      if (typeof tool === "string") return tool;
      const record = asRecord(tool);
      return record?.name ? String(record.name) : jsonPreview(tool, 60);
    }).join(", ");
  }
  const record = asRecord(toolCalls);
  if (!record) return "none";
  return record.name ? String(record.name) : jsonPreview(record, 120);
}

function compactSkillsSummary(skills: unknown): string {
  if (!skills) return "none";
  if (typeof skills === "string") return skills;
  if (Array.isArray(skills)) {
    if (skills.length === 0) return "none";
    return skills.map((skill) => asString(skill) ?? jsonPreview(skill, 60)).join(", ");
  }
  const record = asRecord(skills);
  if (!record) return "none";
  return record.id ? String(record.id) : jsonPreview(record, 120);
}

function compactRunnerTraceSummary(runnerTrace: unknown): string {
  if (!runnerTrace) return "未观测";
  const record = asRecord(runnerTrace);
  if (!record) return "未观测";
  const eventCount = Array.isArray(record.events) ? (record.events as unknown[]).length : Number(record.eventCount ?? 0);
  const status = asString(record.status);
  const sessionId = shortToken(asString(record.sessionId), 8);
  const lastLabel = asString(record.lastEventLabel) ?? asString(record.lastEvent) ?? asString(record.waitingFor) ?? "无";
  const parts: string[] = [];
  if (status) parts.push(`status=${status}`);
  parts.push(`events=${eventCount || 0}`);
  if (sessionId !== "unknown") parts.push(`session=${sessionId}`);
  parts.push(`last=${lastLabel}`);
  return parts.join(" / ");
}

export function codeAgentSummaryRows(summary: CodeAgentStatusSummary): { key: string; value: string; tone: CodeAgentStatusSummary["tone"] | "source" }[] {
  // 顶层 summary 仅保留有意义条目；providerTrace.* / facts / runnerTrace echo
  // 等未观测噪音移出，调试用户走"调试信息" dialog 读取 summary.fields。
  const allRows: Array<{ key: string; value: string; tone: CodeAgentStatusSummary["tone"] | "source" }> = [
    { key: "codeAgent.status", value: summary.codeAgentStatus, tone: summary.tone },
    { key: "provider/mode/backend", value: summary.providerModeBackend, tone: "source" },
    { key: "capabilityLevel", value: summary.capabilityLevel, tone: summary.tone },
    { key: "session", value: summary.sessionSummary, tone: sessionSummaryTone(summary) },
    { key: "sessionId/status", value: summary.sessionIdStatus, tone: sessionSummaryTone(summary) },
    { key: "会话提示", value: summary.sessionLifecycleHint, tone: sessionSummaryTone(summary) },
    { key: "workspace", value: summary.workspace, tone: "source" },
    { key: "sandbox", value: summary.sandbox, tone: "source" },
    { key: "runnerKind", value: summary.runnerKind, tone: summary.kind === "long-lived-session" ? "ok" : "source" },
    { key: "运行路径语义", value: summary.runtimePathLabel, tone: summary.runtimePathTone },
    { key: "toolCalls", value: summary.toolCalls, tone: summary.toolCalls === "none" ? "source" : summary.tone },
    { key: "skills", value: summary.skills, tone: summary.skills === "none" ? "source" : summary.tone },
    { key: "readiness blockers", value: summary.blockersLabel, tone: summary.readinessBlockers.length > 0 ? "blocked" : "source" },
    { key: "当前部署 revision", value: summary.deploymentRevision, tone: "source" }
  ];
  return allRows.filter((row) => {
    if (!row.value) return false;
    if (row.value === "未观测") return false;
    if (row.value === "字段缺失：证据不足") return false;
    if (row.value === "DEGRADED：运行路径字段不完整") return false;
    if (row.value === "无" && row.key === "readiness blockers") return false;
    return true;
  });
}

function sessionSummaryTone(summary: CodeAgentStatusSummary): CodeAgentStatusSummary["tone"] {
  if (summary.kind === "long-lived-session") return "ok";
  if (summary.kind === "blocked") return "blocked";
  if (["read-only-session-tools", "stateless-one-shot", "degraded"].includes(summary.kind)) return "warn";
  return "source";
}

export function summarizeLastEvent(trace: RunnerTrace | null | undefined): { label: string; ts: string | null; tone: CodeAgentStatusSummary["tone"] } {
  if (!trace) return { label: "等待后端事件", ts: null, tone: "source" };
  const events = Array.isArray(trace.events) ? trace.events as TraceEvent[] : [];
  const last = events[events.length - 1] ?? null;
  const lastLabel = String(last?.label ?? last?.type ?? trace.lastEventLabel ?? trace.lastEvent ?? trace.waitingFor ?? "等待后端事件");
  const ts: string | null = typeof last?.ts === "string" ? last.ts : (typeof trace.updatedAt === "string" ? trace.updatedAt : null);
  const status = String(last?.status ?? "");
  const tone: CodeAgentStatusSummary["tone"] = status === "completed" ? "ok" : status === "running" || status === "pending" ? "warn" : status === "failed" ? "blocked" : "source";
  return { label: lastLabel, ts, tone };
}
