import { describe, it, expect } from "vitest";
import type { ExecEvent } from "./api";
import { deriveNodes, pendingApproval, isMultiAgent, deriveTeam, teamNow, isReportRun } from "./run";

let seq = 0;
function ev(node: string, phase: string, extra: Partial<ExecEvent> = {}): ExecEvent {
  return { seq: seq++, ts: 0, node, kind: extra.kind ?? "system", phase, label: extra.label ?? node, ...extra };
}

describe("deriveNodes（ExecEvent 流 → 节点轨迹）", () => {
  it("start → running", () => {
    const [n] = deriveNodes([ev("a", "start")]);
    expect(n.status).toBe("running");
  });

  it("start→end → done 且带耗时", () => {
    const [n] = deriveNodes([ev("a", "start"), ev("a", "end", { ms: 123, detail: "ok" })]);
    expect(n.status).toBe("done");
    expect(n.ms).toBe(123);
    expect(n.detail).toBe("ok");
  });

  it("error → error 状态", () => {
    const [n] = deriveNodes([ev("a", "start"), ev("a", "error", { ms: 5, detail: "boom" })]);
    expect(n.status).toBe("error");
    expect(n.detail).toBe("boom");
  });

  it("已 error 的节点不会被后续 start 覆盖回 running", () => {
    const [n] = deriveNodes([ev("a", "error"), ev("a", "start")]);
    expect(n.status).toBe("error");
  });

  it("info 事件累计到 notes", () => {
    const [n] = deriveNodes([
      ev("a", "info", { detail: "检索到 3 条" }),
      ev("a", "info", { detail: "命中向量库" }),
    ]);
    expect(n.notes).toEqual(["检索到 3 条", "命中向量库"]);
  });

  it("按首次出现顺序聚合多个节点", () => {
    const nodes = deriveNodes([ev("first", "start"), ev("second", "start"), ev("first", "end")]);
    expect(nodes.map((n) => n.node)).toEqual(["first", "second"]);
  });

  it("后续事件可更新 label", () => {
    const [n] = deriveNodes([ev("a", "start", { label: "旧" }), ev("a", "end", { label: "新" })]);
    expect(n.label).toBe("新");
  });

  it("await 事件 → 节点状态 waiting", () => {
    const [n] = deriveNodes([ev("ap", "await", { kind: "approval", label: "审批" })]);
    expect(n.status).toBe("waiting");
  });
});

describe("pendingApproval（HITL 待审批中断）", () => {
  it("await 未收口 → 返回待审批", () => {
    const p = pendingApproval([ev("ap", "await", { kind: "approval", label: "高危审批", detail: "摘要" })]);
    expect(p).toEqual({ node: "ap", title: "高危审批", summary: "摘要" });
  });

  it("await 后 end（同节点）→ 已决，返回 null", () => {
    const p = pendingApproval([
      ev("ap", "await", { kind: "approval" }),
      ev("ap", "end", { kind: "approval" }),
    ]);
    expect(p).toBeNull();
  });

  it("await 后 error（超时/拒绝）→ 已决，返回 null", () => {
    const p = pendingApproval([
      ev("ap", "await", { kind: "approval" }),
      ev("ap", "error", { kind: "approval" }),
    ]);
    expect(p).toBeNull();
  });

  it("无审批事件 → null", () => {
    expect(pendingApproval([ev("a", "start"), ev("a", "end")])).toBeNull();
  });
});

// ---- 团队视图派生（多智能体）----
// 后端约定：协调者 coordinator:<id>/kind=model；专家 agent:<名>/kind=agent，
// 收尾 detail="简报 X → Y"；工具 tool:<名>/kind=tool。

describe("isMultiAgent（值不值得看团队）", () => {
  it("有 coordinator: 节点 → true", () => {
    expect(isMultiAgent([ev("coordinator:c", "start", { kind: "model" })])).toBe(true);
  });
  it("普通任务(model/tool) → false，不显示团队 tab", () => {
    expect(isMultiAgent([ev("model", "start", { kind: "model" }), ev("tool:wiki", "end", { kind: "tool" })])).toBe(false);
  });
  // 编排里并排多个 agent 的图没有协调者，且 agent 节点发的是 kind=model（不是 kind=agent）。
  // 按 kind 判会把这种图整个漏掉——用户从编排点执行后就看不到团队 tab。
  it("图里 ≥2 个 agent 节点（kind=model，无协调者）→ true", () => {
    expect(
      isMultiAgent([
        ev("agent:a", "start", { kind: "model" }),
        ev("agent:b", "start", { kind: "model" }),
      ]),
    ).toBe(true);
  });
  it("只有 1 个 agent → false（一个人不叫团队）", () => {
    expect(isMultiAgent([ev("agent:a", "start", { kind: "model" }), ev("agent:a", "end", { kind: "model" })])).toBe(false);
  });
});

describe("deriveTeam（协调者 + 工位）", () => {
  it("专家 start→end：done + 耗时 + 解析简报/产出", () => {
    const t = deriveTeam([
      ev("agent:条款专家", "start", { kind: "agent", ts: 100 }),
      ev("agent:条款专家", "end", { kind: "agent", ts: 400, ms: 300, detail: "简报 核对违约金 → 发现3处问题" }),
    ]);
    const s = t.seats[0];
    expect(s.name).toBe("条款专家");
    expect(s.kind).toBe("agent");
    expect(s.status).toBe("done");
    expect(s.ms).toBe(300);
    expect(s.brief).toBe("核对违约金");
    expect(s.output).toBe("发现3处问题");
  });

  it("未收口的专家 → running，endTS 为空", () => {
    const t = deriveTeam([ev("agent:风险专家", "start", { kind: "agent", ts: 10 })]);
    expect(t.seats[0].status).toBe("running");
    expect(t.seats[0].endTS).toBeUndefined();
  });

  it("专家失败 → error，detail 落到产出", () => {
    const t = deriveTeam([
      ev("agent:x", "start", { kind: "agent", ts: 0 }),
      ev("agent:x", "error", { kind: "agent", ts: 50, ms: 50, detail: "专家超时" }),
    ]);
    expect(t.seats[0].status).toBe("error");
    expect(t.seats[0].output).toBe("专家超时");
  });

  it("detail 无「简报 → 」格式 → 整段当产出，不误判", () => {
    const t = deriveTeam([
      ev("tool:wiki", "start", { kind: "tool", ts: 0 }),
      ev("tool:wiki", "end", { kind: "tool", ts: 20, ms: 20, detail: "命中 4 段" }),
    ]);
    expect(t.seats[0].brief).toBeUndefined();
    expect(t.seats[0].output).toBe("命中 4 段");
  });

  it("图里的 agent 节点（kind=model）也算工位", () => {
    const t = deriveTeam([
      ev("agent:writer", "start", { kind: "model", ts: 0 }),
      ev("agent:writer", "end", { kind: "model", ts: 300, ms: 300, detail: "成稿 800 字" }),
    ]);
    expect(t.seats.map((s) => s.name)).toEqual(["writer"]);
    expect(t.seats[0].status).toBe("done");
    expect(t.lead).toBeNull(); // 没有协调者 → 不能凭空造一个
  });

  it("retriever/map/render 不当工位（同为 kind=tool，但不是 tool: 前缀）", () => {
    const t = deriveTeam([
      ev("retriever:r", "start", { kind: "tool", ts: 0 }),
      ev("retriever:r", "end", { kind: "tool", ts: 50, ms: 50 }),
      ev("map:m", "start", { kind: "plan", ts: 10 }),
      ev("render:o", "end", { kind: "render", ts: 60 }),
    ]);
    expect(t.seats).toEqual([]);
  });

  it("协调者被识别为 lead，且不混进工位", () => {
    const t = deriveTeam([
      ev("coordinator:c", "start", { kind: "model", label: "多智能体协调", detail: "2 个专家可派发" }),
      ev("agent:a", "start", { kind: "agent", ts: 5 }),
    ]);
    expect(t.lead?.label).toBe("多智能体协调");
    expect(t.lead?.detail).toBe("2 个专家可派发");
    expect(t.seats.map((s) => s.node)).toEqual(["agent:a"]);
  });

  it("并发时间轴窗口：t0=最早开始，t1=最晚结束", () => {
    const t = deriveTeam([
      ev("agent:a", "start", { kind: "agent", ts: 100 }),
      ev("agent:b", "start", { kind: "agent", ts: 150 }),
      ev("agent:a", "end", { kind: "agent", ts: 900, ms: 800 }),
      ev("agent:b", "end", { kind: "agent", ts: 400, ms: 250 }),
    ]);
    expect(t.t0).toBe(100);
    expect(t.t1).toBe(900);
  });

  it("未收口时 t1 用 now（进行中的条也能画满）", () => {
    const t = deriveTeam([ev("agent:a", "start", { kind: "agent", ts: 100 })], 5000);
    expect(t.t1).toBe(5000);
  });
});

describe("teamNow（并发时间轴的右端）", () => {
  it("直播时用此刻", () => {
    const before = Date.now();
    expect(teamNow([ev("agent:a", "start", { kind: "agent", ts: 100 })], true)).toBeGreaterThanOrEqual(before);
  });

  it("复盘时用最后一个事件的时间戳", () => {
    const n = teamNow(
      [ev("agent:a", "start", { kind: "agent", ts: 100 }), ev("agent:b", "end", { kind: "agent", ts: 900, ms: 800 })],
      false,
    );
    expect(n).toBe(900);
  });

  it("复盘一条卡在 running 的旧任务，跨度不会拉到今天", () => {
    const old = Date.now() - 365 * 24 * 3600 * 1000;
    const events = [
      ev("agent:a", "start", { kind: "agent", ts: old }),
      ev("agent:b", "start", { kind: "agent", ts: old + 2000 }), // 永远没收口
    ];
    const t = deriveTeam(events, teamNow(events, false));
    expect(t.t1 - t.t0).toBe(2000);
  });
});

describe("isReportRun（运行是不是报告生成）", () => {
  it("report_ 前缀 → true", () => {
    expect(isReportRun("report_5dae9155af5cb500")).toBe(true);
  });
  it("普通任务 → false", () => {
    expect(isReportRun("task_e984b1f3e38a35c7")).toBe(false);
  });
  it("空/未定义 → false（当前无选中运行时不该冒出导出按钮）", () => {
    expect(isReportRun(undefined)).toBe(false);
    expect(isReportRun("")).toBe(false);
  });
  it("不能只看是否包含 report（避免误判）", () => {
    expect(isReportRun("task_report_like")).toBe(false);
  });
});
