// Gateway HTTP/SSE 客户端：提交 DSL 任务、订阅 Token 流、登记偏好记忆。
import type { TaskDsl } from "./dsl";

// 开发期直连 Gateway；Wails 打包后可改为本地后端地址或经 Go 绑定。
export const GATEWAY: string =
  (import.meta.env.VITE_GATEWAY as string | undefined) ?? "http://localhost:8080";

export interface Identity {
  userId: string;
  sessionId: string;
}

export interface AuthUser {
  id: string;
  email: string;
  name?: string;
}

// ---- JWT 令牌存储（localStorage）----
const TOKEN_KEY = "sdx_token";
let authToken: string = typeof localStorage !== "undefined" ? localStorage.getItem(TOKEN_KEY) ?? "" : "";

export function setToken(t: string): void {
  authToken = t;
  try {
    localStorage.setItem(TOKEN_KEY, t);
  } catch {
    /* 隐私模式忽略 */
  }
}
export function clearToken(): void {
  authToken = "";
  try {
    localStorage.removeItem(TOKEN_KEY);
  } catch {
    /* ignore */
  }
}
export function getToken(): string {
  return authToken;
}

// bearer 把 JWT 放进请求头（无令牌则不带）。
function bearer(): Record<string, string> {
  return authToken ? { Authorization: `Bearer ${authToken}` } : {};
}

// guard401 在收到 401 时清理令牌并广播登出事件（App 监听后回到登录页）。
function guard401(res: Response): Response {
  if (res.status === 401) {
    clearToken();
    if (typeof window !== "undefined") window.dispatchEvent(new Event("sdx:logout"));
  }
  return res;
}

// ---- 鉴权 API ----
export async function authRegister(email: string, password: string, name: string): Promise<AuthUser> {
  const res = await fetch(`${GATEWAY}/api/v1/auth/register`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email, password, name }),
  });
  const data = (await res.json()) as { token?: string; user?: AuthUser; error?: string };
  if (!res.ok || !data.token || !data.user) throw new Error(data.error ?? `注册失败: ${res.status}`);
  setToken(data.token);
  return data.user;
}

export async function authLogin(email: string, password: string): Promise<AuthUser> {
  const res = await fetch(`${GATEWAY}/api/v1/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ email, password }),
  });
  const data = (await res.json()) as { token?: string; user?: AuthUser; error?: string };
  if (!res.ok || !data.token || !data.user) throw new Error(data.error ?? `登录失败: ${res.status}`);
  setToken(data.token);
  return data.user;
}

// authMe 用当前令牌取登录用户；无效/过期返回 null（用于应用启动校验）。
export async function authMe(): Promise<AuthUser | null> {
  if (!authToken) return null;
  const res = await fetch(`${GATEWAY}/api/v1/auth/me`, { headers: bearer() });
  if (!res.ok) {
    clearToken();
    return null;
  }
  const data = (await res.json()) as { user?: AuthUser };
  return data.user ?? null;
}

export function logout(): void {
  clearToken();
}

// submitTask: POST /api/v1/tasks，返回 task_id。
export async function submitTask(dsl: TaskDsl, id: Identity): Promise<string> {
  const res = guard401(
    await fetch(`${GATEWAY}/api/v1/tasks`, {
      method: "POST",
      headers: { "Content-Type": "application/json", ...idHeaders(id) },
      body: JSON.stringify(dsl),
    }),
  );
  if (!res.ok) throw new Error(`submit failed: ${res.status} ${await res.text()}`);
  const data = (await res.json()) as { task_id: string };
  return data.task_id;
}

// taskStatus: GET /api/v1/tasks/:id —— 任务生命周期状态（submitted/running/waiting/done/failed/timeout/rejected）。
// 轮询它来可靠捕获 waiting（审批中断）—— 不依赖易抢跑的实时 exec 事件。
export async function taskStatus(taskId: string): Promise<{ status: string; detail: string }> {
  const res = guard401(await fetch(`${GATEWAY}/api/v1/tasks/${taskId}`, { headers: bearer() }));
  if (!res.ok) throw new Error(`status failed: ${res.status}`);
  const d = (await res.json()) as { status?: string; detail?: string };
  return { status: d.status ?? "", detail: d.detail ?? "" };
}

// approveTask: POST /api/v1/tasks/:id/approve —— HITL 人工审批决定（批准放行 / 拒绝中止）。
export async function approveTask(taskId: string, approved: boolean, opts?: { node?: string; note?: string }): Promise<void> {
  const res = guard401(
    await fetch(`${GATEWAY}/api/v1/tasks/${taskId}/approve`, {
      method: "POST",
      headers: { "Content-Type": "application/json", ...bearer() },
      body: JSON.stringify({ approved, node: opts?.node ?? "", note: opts?.note ?? "" }),
    }),
  );
  if (!res.ok) throw new Error(`approve failed: ${res.status} ${await res.text()}`);
}

// streamTokens: 订阅 SSE /api/v1/tasks/:id/stream，逐 token 回调，done 收尾。
// 返回关闭函数。注意 EventSource 无法带请求头，但流按 task_id 寻址，无需身份头。
export function streamTokens(
  taskId: string,
  onToken: (t: string) => void,
  onDone: () => void,
  onError?: (e: unknown) => void,
): () => void {
  const es = new EventSource(`${GATEWAY}/api/v1/tasks/${taskId}/stream`);
  es.addEventListener("token", (e) => onToken((e as MessageEvent).data));
  es.addEventListener("done", () => {
    es.close();
    onDone();
  });
  es.onerror = (e) => {
    es.close();
    onError?.(e);
  };
  return () => es.close();
}

// 执行轨迹事件（与后端 contract.ExecEvent 对应）：运行·观测的节点级实时事件。
export interface ExecEvent {
  seq: number;
  ts: number;
  node: string; // init / tool:wiki_search / prompt / model / plan / section:0 / render / task / compile
  kind: string; // memory|tool|prompt|model|plan|section|render|system
  phase: string; // start|end|error|info
  label: string;
  detail?: string;
  ms?: number;
}

// streamExec: 订阅 SSE /api/v1/tasks/:id/exec —— 与 token 流并行，逐节点点亮执行轨迹。
export function streamExec(
  taskId: string,
  onEvent: (ev: ExecEvent) => void,
  onDone: () => void,
  onError?: (e: unknown) => void,
): () => void {
  const es = new EventSource(`${GATEWAY}/api/v1/tasks/${taskId}/exec`);
  es.addEventListener("exec", (e) => onEvent(JSON.parse((e as MessageEvent).data) as ExecEvent));
  es.addEventListener("done", () => {
    es.close();
    onDone();
  });
  es.onerror = (e) => {
    es.close();
    onError?.(e);
  };
  return () => es.close();
}

// 入库进度事件（与后端 contract.IngestEvent 对应）。
export interface IngestEvent {
  stage: string;
  msg?: string;
  done?: number;
  total?: number;
  chunks?: string[];
  preview?: string; // 解析阶段：解析出的文本片段
  triples?: Triple[]; // 抽实体阶段：LLM 抽出的知识三元组
  error?: string;
}

// idHeaders 把身份带进请求头：JWT(Bearer) 作鉴权与 owner 作用域；X-Session-ID 作多轮会话标识。
// 不再发 X-User-ID —— owner 由网关从已验证 JWT 取（伪造头无效）。
function idHeaders(id: Identity): Record<string, string> {
  return { ...bearer(), "X-Session-ID": id.sessionId };
}

export interface KbInfo {
  name: string;
  kind: string; // folder / project / case / general
}

// listKb: GET /api/v1/kb/list —— 当前用户的知识库列表（owner 隔离）。
export async function listKb(id: Identity): Promise<KbInfo[]> {
  const res = guard401(await fetch(`${GATEWAY}/api/v1/kb/list`, { headers: idHeaders(id) }));
  const data = (await res.json()) as { kbs?: KbInfo[]; error?: string };
  if (!res.ok) throw new Error(data.error ?? `list failed: ${res.status}`);
  return data.kbs ?? [];
}

// createKb: POST /api/v1/kb/create —— 新建知识库（项目/案件/文件夹/通用）。
export async function createKb(id: Identity, name: string, kind: string): Promise<KbInfo> {
  const res = await fetch(`${GATEWAY}/api/v1/kb/create`, {
    method: "POST",
    headers: { "Content-Type": "application/json", ...idHeaders(id) },
    body: JSON.stringify({ name, kind }),
  });
  const data = (await res.json()) as { name?: string; kind?: string; error?: string };
  if (!res.ok || !data.name) throw new Error(data.error ?? `create failed: ${res.status}`);
  return { name: data.name, kind: data.kind ?? kind };
}

export interface VaultDoc {
  id: string; // 文件主表雪花 ID —— 选中/取正文/关联一律用它
  name: string;
  ext?: string; // 文件后缀（.md/.pdf…；笔记为空）
  size?: number;
  preview?: string;
}
export interface DocLink {
  from: string; // 源文件 ID
  to: string; // 目标文件 ID
}

// ---- 我的 Agent 编排（服务端保存，owner 隔离）----
export interface AgentInfo {
  name: string;
  graph: string; // {nodes,edges} JSON
  updated_at?: string;
}

export async function listAgents(id: Identity): Promise<AgentInfo[]> {
  const res = guard401(await fetch(`${GATEWAY}/api/v1/agents`, { headers: idHeaders(id) }));
  const data = (await res.json()) as { agents?: AgentInfo[]; error?: string };
  if (!res.ok) throw new Error(data.error ?? `list agents failed: ${res.status}`);
  return data.agents ?? [];
}

export async function saveAgent(id: Identity, name: string, graph: string): Promise<void> {
  const res = await fetch(`${GATEWAY}/api/v1/agents`, {
    method: "POST",
    headers: { "Content-Type": "application/json", ...idHeaders(id) },
    body: JSON.stringify({ name, graph }),
  });
  const data = (await res.json()) as { name?: string; error?: string };
  if (!res.ok || !data.name) throw new Error(data.error ?? `save agent failed: ${res.status}`);
}

export async function deleteAgent(id: Identity, name: string): Promise<void> {
  const res = await fetch(`${GATEWAY}/api/v1/agents?name=${encodeURIComponent(name)}`, { method: "DELETE", headers: idHeaders(id) });
  if (!res.ok) {
    const data = (await res.json().catch(() => ({}))) as { error?: string };
    throw new Error(data.error ?? `delete agent failed: ${res.status}`);
  }
}

// listChatModels: GET /api/v1/admin/models?kind=chat —— 已登记的对话模型名（供编排 Agent 节点选择）。
export async function listChatModels(): Promise<string[]> {
  try {
    const res = await fetch(`${GATEWAY}/api/v1/admin/models?kind=chat`);
    const data = (await res.json()) as { models?: Array<{ model: string }> };
    return (data.models ?? []).map((m) => m.model);
  } catch {
    return [];
  }
}

// listVault: GET /api/v1/kb/vault —— 文库列表（仅元数据 + 预览，不拉全文）。
export async function listVault(id: Identity, kb: string): Promise<VaultDoc[]> {
  const res = await fetch(`${GATEWAY}/api/v1/kb/vault?kb=${encodeURIComponent(kb)}`, { headers: idHeaders(id) });
  const data = (await res.json()) as { docs?: VaultDoc[]; error?: string };
  if (!res.ok) throw new Error(data.error ?? `vault failed: ${res.status}`);
  return data.docs ?? [];
}

// getDoc: GET /api/v1/kb/doc?id= —— 按文件 ID 取单篇全文（按需加载，避免列表拉全量）。
export async function getDoc(id: Identity, docId: string): Promise<string> {
  const res = await fetch(`${GATEWAY}/api/v1/kb/doc?id=${encodeURIComponent(docId)}`, { headers: idHeaders(id) });
  const data = (await res.json()) as { content?: string; error?: string };
  if (!res.ok) throw new Error(data.error ?? `doc failed: ${res.status}`);
  return data.content ?? "";
}

// listLinks: GET /api/v1/kb/links —— 某库全部 [[双链]]（反链/笔记关系图，数据小）。
export async function listLinks(id: Identity, kb: string): Promise<DocLink[]> {
  const res = await fetch(`${GATEWAY}/api/v1/kb/links?kb=${encodeURIComponent(kb)}`, { headers: idHeaders(id) });
  const data = (await res.json()) as { links?: DocLink[]; error?: string };
  if (!res.ok) throw new Error(data.error ?? `links failed: ${res.status}`);
  return data.links ?? [];
}

// saveNote: POST /api/v1/kb/note —— 新建/编辑笔记（落库 + 按 doc 重入库替换旧块）。
export async function saveNote(id: Identity, kb: string, name: string, content: string): Promise<void> {
  const res = await fetch(`${GATEWAY}/api/v1/kb/note`, {
    method: "POST",
    headers: { "Content-Type": "application/json", ...idHeaders(id) },
    body: JSON.stringify({ kb, name, content }),
  });
  const data = (await res.json()) as { name?: string; error?: string };
  if (!res.ok || !data.name) throw new Error(data.error ?? `save failed: ${res.status}`);
}

// ingestKb: POST /api/v1/kb/ingest —— 文本入库（异步，返回 job_id）。
export async function ingestKb(id: Identity, kb: string, text: string): Promise<string> {
  const res = await fetch(`${GATEWAY}/api/v1/kb/ingest`, {
    method: "POST",
    headers: { "Content-Type": "application/json", ...idHeaders(id) },
    body: JSON.stringify({ kb, text }),
  });
  const data = (await res.json()) as { job_id?: string; error?: string };
  if (!res.ok || !data.job_id) throw new Error(data.error ?? `ingest failed: ${res.status}`);
  return data.job_id;
}

// ingestFile: POST /api/v1/kb/ingest_file（multipart）—— 文件入库（异步，返回 job_id）。
export async function ingestFile(id: Identity, kb: string, file: File): Promise<string> {
  const fd = new FormData();
  fd.append("kb", kb);
  fd.append("file", file);
  const res = await fetch(`${GATEWAY}/api/v1/kb/ingest_file`, { method: "POST", headers: idHeaders(id), body: fd });
  const data = (await res.json()) as { job_id?: string; error?: string };
  if (!res.ok || !data.job_id) throw new Error(data.error ?? `ingest file failed: ${res.status}`);
  return data.job_id;
}

// streamIngest: SSE 订阅入库进度（/kb/ingest/:id/stream）。返回关闭函数。
export function streamIngest(
  jobId: string,
  onEvent: (ev: IngestEvent) => void,
  onDone: () => void,
  onError?: () => void,
): () => void {
  const es = new EventSource(`${GATEWAY}/api/v1/kb/ingest/${jobId}/stream`);
  es.addEventListener("progress", (e) => onEvent(JSON.parse((e as MessageEvent).data) as IngestEvent));
  es.addEventListener("done", () => {
    es.close();
    onDone();
  });
  es.onerror = () => {
    es.close();
    onError?.();
  };
  return () => es.close();
}

export interface KbHit {
  text: string;
  score: number;
}

export interface Triple {
  s: string;
  p: string;
  o: string;
}

// graphKb: GET /api/v1/kb/graph —— 取某知识库的图谱三元组（→ mcp-go kb_graph，Neo4j）。
export async function graphKb(id: Identity, kb: string): Promise<Triple[]> {
  const res = await fetch(`${GATEWAY}/api/v1/kb/graph?kb=${encodeURIComponent(kb)}`, { headers: idHeaders(id) });
  const data = (await res.json()) as { triples?: Triple[]; error?: string };
  if (!res.ok) throw new Error(data.error ?? `graph failed: ${res.status}`);
  return data.triples ?? [];
}

// searchKb: POST /api/v1/kb/search，检索台查询（→ mcp-go kb_search，带分数）。
export async function searchKb(id: Identity, kb: string, q: string, topK = 5): Promise<KbHit[]> {
  const res = await fetch(`${GATEWAY}/api/v1/kb/search`, {
    method: "POST",
    headers: { "Content-Type": "application/json", ...idHeaders(id) },
    body: JSON.stringify({ kb, q, topK }),
  });
  const data = (await res.json()) as { hits?: KbHit[]; error?: string };
  if (!res.ok) throw new Error(data.error ?? `search failed: ${res.status}`);
  return data.hits ?? [];
}

// generateReport: POST /api/v1/reports —— 触发报告生成，返回 task_id。
// 用 streamTokens(task_id) 看实时进度，完成后用 reportDownloadUrl(task_id) 下载 Word。
export async function generateReport(id: Identity, topic: string, kb?: string): Promise<string> {
  const res = guard401(
    await fetch(`${GATEWAY}/api/v1/reports`, {
      method: "POST",
      headers: { "Content-Type": "application/json", ...idHeaders(id) },
      body: JSON.stringify({ topic, kb: kb ?? "" }),
    }),
  );
  const data = (await res.json()) as { task_id?: string; error?: string };
  if (!res.ok || !data.task_id) throw new Error(data.error ?? `report failed: ${res.status}`);
  return data.task_id;
}

// reportDownloadUrl: 渲染好的 Word(.docx) 下载地址（兼容旧入口）。
export function reportDownloadUrl(taskId: string): string {
  return `${GATEWAY}/api/v1/reports/${taskId}/download`;
}

// reportExportUrl: 按需导出报告地址（format=docx|md；后端现渲染）。PDF 由前端打印预览生成。
export function reportExportUrl(taskId: string, format: "docx" | "md"): string {
  return `${GATEWAY}/api/v1/reports/${taskId}/export?format=${format}`;
}

// setMemory: PUT /api/v1/memory，登记一条用户偏好（→ mcp-go memory_upsert）。
export async function setMemory(
  id: Identity,
  key: string,
  value: string,
): Promise<string> {
  const res = guard401(
    await fetch(`${GATEWAY}/api/v1/memory`, {
      method: "PUT",
      headers: { "Content-Type": "application/json", ...idHeaders(id) },
      body: JSON.stringify({ key, value }),
    }),
  );
  const data = (await res.json()) as { message?: string; error?: string };
  if (!res.ok) throw new Error(data.error ?? `memory failed: ${res.status}`);
  return data.message ?? "ok";
}

// 一条长期偏好（含读路径打分维度）。
export interface MemoryItem {
  key: string;
  value: string;
  importance: number;
  last_seen: string;
}

// listMemory: GET /api/v1/memory —— 列出当前用户偏好（已按 Score 降序）。
export async function listMemory(id: Identity): Promise<MemoryItem[]> {
  const res = guard401(await fetch(`${GATEWAY}/api/v1/memory`, { headers: idHeaders(id) }));
  const data = (await res.json()) as { memories?: MemoryItem[]; error?: string };
  if (!res.ok) throw new Error(data.error ?? `list memory failed: ${res.status}`);
  return data.memories ?? [];
}

// deleteMemory: DELETE /api/v1/memory?key= —— 软删一条偏好。
export async function deleteMemory(id: Identity, key: string): Promise<void> {
  const res = guard401(await fetch(`${GATEWAY}/api/v1/memory?key=${encodeURIComponent(key)}`, { method: "DELETE", headers: idHeaders(id) }));
  if (!res.ok) {
    const d = (await res.json().catch(() => ({}))) as { error?: string };
    throw new Error(d.error ?? `delete memory failed: ${res.status}`);
  }
}

// ── 工作台仪表盘聚合（GET /stats/overview）──
export interface KeyCount { key: string; count: number }
export interface RecentRun { task_id: string; status: string; detail: string; at: string }
export interface Overview {
  tasks_today: number;
  tasks_total: number;
  status_count: KeyCount[];
  task_trend: KeyCount[];
  token_trend: KeyCount[];
  eval_avg: number;
  faithful_avg: number;
  eval_count: number;
  kb_docs: number;
  kb_count: number;
  tokens_today: number;
  daily_budget: number;
  recent_runs: RecentRun[];
  services: Record<string, boolean>;
}

export async function statsOverview(): Promise<Overview> {
  const res = guard401(await fetch(`${GATEWAY}/api/v1/stats/overview`, { headers: bearer() }));
  if (!res.ok) throw new Error(`stats failed: ${res.status}`);
  return res.json() as Promise<Overview>;
}

// ── 运行历史 / 复盘 ──
export interface RunSummary {
  task_id: string;
  status: string;
  detail: string;
  at: string;
  eval_level: string;
  eval_overall: number;
}

export async function listRuns(limit = 30): Promise<RunSummary[]> {
  const res = guard401(await fetch(`${GATEWAY}/api/v1/runs?limit=${limit}`, { headers: bearer() }));
  if (!res.ok) throw new Error(`list runs failed: ${res.status}`);
  const d = (await res.json()) as { runs?: RunSummary[] };
  return d.runs ?? [];
}

export interface EvalResult {
  overall: number;
  rule: number;
  llm: number;
  faithful: number;
  level: string;
  flags: string[];
  reason: string;
  sources: number;
  corrected: boolean;
}

// taskEval: 取一次任务的评测结果（无则返回 null）。
export async function taskEval(taskId: string): Promise<EvalResult | null> {
  const res = await fetch(`${GATEWAY}/api/v1/tasks/${taskId}/eval`, { headers: bearer() });
  if (!res.ok) return null;
  return res.json() as Promise<EvalResult>;
}

// runReplay: 历史运行复盘 —— 读库取持久化的输出 + 轨迹（不依赖 Redis TTL，秒回）。
export async function runReplay(taskId: string): Promise<{ output: string; exec: ExecEvent[] }> {
  const res = guard401(await fetch(`${GATEWAY}/api/v1/tasks/${taskId}/replay`, { headers: bearer() }));
  if (!res.ok) throw new Error(`replay failed: ${res.status}`);
  const d = (await res.json()) as { output?: string; exec?: ExecEvent[] };
  return { output: d.output ?? "", exec: d.exec ?? [] };
}
