import { useMemo, useState } from "react";
import { useDashboardStore } from "../store";

type AnswerMode = "search" | "llm";

interface MatchReason {
  type: string;
  field?: string;
  message: string;
  score: number;
}

interface RagHit {
  chunkId: string;
  nodeId: string;
  docTitle: string;
  docPath: string;
  sectionTitle: string;
  chunkType: string;
  layer: string;
  score: number;
  scores: {
    exact: number;
    fuzzy: number;
    semantic: number;
    titleBoost: number;
    sectionBoost: number;
    directoryBoost: number;
    chunkTypeBoost: number;
  };
  snippet: string;
  evidenceContent?: string;
  reasons: MatchReason[];
}

interface LlmSettings {
  enabled: boolean;
  provider: "auto" | "openai" | "routin-plan";
  endpoint: string;
  model: string;
  apiKey: string;
}

interface EvidenceDecision {
  allowed: boolean;
  confidence: "high" | "medium" | "low" | "none";
  reason: string;
}

interface RequestStatus {
  stage?: string;
  startedAt?: string;
  endpoint?: string;
  protocol?: string;
  elapsedMs?: number;
  rawShape?: string[];
}

interface RagResponse {
  ok: boolean;
  mode?: string;
  query?: unknown;
  decision?: EvidenceDecision;
  answer?: string;
  hits?: RagHit[];
  requestStatus?: RequestStatus;
  error?: {
    message?: string;
    name?: string;
    status?: number;
  };
}

const SETTINGS_KEY = "ua-rag-llm-settings-v2";

function loadSettings(): LlmSettings {
  if (typeof window === "undefined") return { enabled: false, provider: "auto", endpoint: "http://localhost:11434/v1", model: "qwen2.5:7b", apiKey: "" };
  try {
    const raw = window.localStorage.getItem(SETTINGS_KEY);
    if (raw) return { enabled: false, provider: "auto", endpoint: "http://localhost:11434/v1", model: "qwen2.5:7b", apiKey: "", ...JSON.parse(raw) };
  } catch {
  }
  return { enabled: false, provider: "auto", endpoint: "http://localhost:11434/v1", model: "qwen2.5:7b", apiKey: "" };
}

function saveSettings(settings: LlmSettings): void {
  if (typeof window === "undefined") return;
  window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
}

function stageLabel(stage?: string): string {
  switch (stage) {
    case "search": return "本地检索中";
    case "evidence_rejected": return "证据不足，未调用模型";
    case "local_answer": return "本地回答";
    case "llm_done": return "模型回答完成";
    case "llm_failed": return "模型调用失败";
    default: return stage || "待请求";
  }
}

async function postJson<T>(url: string, payload: unknown): Promise<{ status: number; data: T }> {
  const response = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  const text = await response.text();
  let data: T;
  try {
    data = text ? JSON.parse(text) as T : {} as T;
  } catch {
    data = { ok: false, error: { message: text || "响应不是 JSON" } } as T;
  }
  return { status: response.status, data };
}

export default function RagAssistant({ onClose }: { onClose: () => void }) {
  const graph = useDashboardStore((s) => s.graph);
  const navigateToNodeInLayer = useDashboardStore((s) => s.navigateToNodeInLayer);
  const [question, setQuestion] = useState("");
  const [submittedQuestion, setSubmittedQuestion] = useState("");
  const [mode, setMode] = useState<AnswerMode>("search");
  const [settings, setSettings] = useState<LlmSettings>(loadSettings);
  const [answer, setAnswer] = useState("");
  const [hits, setHits] = useState<RagHit[]>([]);
  const [decision, setDecision] = useState<EvidenceDecision | null>(null);
  const [requestStatus, setRequestStatus] = useState<RequestStatus>({});
  const [loading, setLoading] = useState(false);
  const [testing, setTesting] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [showSettings, setShowSettings] = useState(false);
  const [selectedHit, setSelectedHit] = useState<RagHit | null>(null);

  const nodeCount = graph?.nodes.length ?? 0;
  const docCount = useMemo(() => graph?.nodes.filter((node) => node.id.startsWith("doc:")).length ?? 0, [graph]);

  const updateSettings = (patch: Partial<LlmSettings>) => {
    const next = { ...settings, ...patch };
    setSettings(next);
    saveSettings(next);
  };

  const submit = async () => {
    const trimmed = question.trim();
    setSubmittedQuestion(trimmed);
    setError(null);
    setAnswer("");
    setHits([]);
    setSelectedHit(null);
    setDecision(null);
    setRequestStatus({ stage: mode === "llm" ? "search" : "search", startedAt: new Date().toISOString() });
    if (!trimmed) {
      setAnswer("请输入要检索或提问的内容。");
      return;
    }
    setLoading(true);
    try {
      const endpoint = mode === "llm" ? "/api/rag/answer" : "/api/rag/search";
      const { status, data } = await postJson<RagResponse>(endpoint, {
        query: trimmed,
        topK: 16,
        llm: settings,
      });
      setRequestStatus(data.requestStatus || { stage: mode === "llm" ? "llm_done" : "local_answer" });
      setDecision(data.decision || null);
      setHits(data.hits || []);
      setAnswer(data.answer || "暂无该需求描述。");
      if (!data.ok) {
        const message = data.error?.message || `请求失败：HTTP ${status}`;
        setError(message);
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : String(err));
      setRequestStatus({ stage: "llm_failed" });
      setAnswer("请求失败，未能获取检索结果。");
    } finally {
      setLoading(false);
    }
  };

  const testModel = async () => {
    setTesting(true);
    setError(null);
    setRequestStatus({ stage: "模型连接测试中", startedAt: new Date().toISOString() });
    try {
      const { status, data } = await postJson<RagResponse & { endpoint?: string; elapsedMs?: number }>("/api/llm/test", settings);
      if (!data.ok) {
        setRequestStatus({ stage: "llm_failed", endpoint: data.endpoint, elapsedMs: data.elapsedMs });
        setError(data.error?.message || `模型连接测试失败：HTTP ${status}`);
      } else {
        setRequestStatus({ stage: "llm_done", endpoint: data.endpoint, elapsedMs: data.elapsedMs });
        setAnswer(`模型连接成功。\nEndpoint：${data.endpoint || settings.endpoint}\n耗时：${data.elapsedMs ?? "?"} ms\n返回：${data.answer || ""}`);
      }
    } catch (err) {
      setRequestStatus({ stage: "llm_failed" });
      setError(err instanceof Error ? err.message : String(err));
    } finally {
      setTesting(false);
    }
  };

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/65 backdrop-blur-sm p-4 sm:p-6" onMouseDown={onClose}>
      <div className="w-[calc(100vw-32px)] max-w-[1180px] h-[calc(100vh-64px)] max-h-[860px] rounded-lg border border-border-medium bg-surface shadow-2xl overflow-hidden flex flex-col" onMouseDown={(event) => event.stopPropagation()}>
        <div className="flex items-center justify-between px-5 py-4 border-b border-border-subtle bg-elevated/40">
          <div>
            <h2 className="font-heading text-lg text-text-primary">知识库混合 RAG 检索 / 问答</h2>
            <p className="text-xs text-text-muted mt-1">前端只发请求到本地后端；后端先检索证据，再代理调用模型并返回明确错误。</p>
          </div>
          <button type="button" onClick={onClose} className="text-text-muted hover:text-accent transition-colors text-xl leading-none">×</button>
        </div>

        <div className="p-5 border-b border-border-subtle space-y-3">
          <div className="flex flex-wrap items-center gap-2">
            <button type="button" onClick={() => { setMode("search"); setShowSettings(false); }} className={`px-3 py-1.5 rounded-md border text-xs font-semibold ${mode === "search" && !showSettings ? "border-accent/50 bg-accent/20 text-accent" : "border-border-subtle text-text-muted hover:text-text-primary"}`}>本地检索</button>
            <button type="button" onClick={() => { setMode("llm"); setShowSettings(false); }} className={`px-3 py-1.5 rounded-md border text-xs font-semibold ${mode === "llm" && !showSettings ? "border-accent/50 bg-accent/20 text-accent" : "border-border-subtle text-text-muted hover:text-text-primary"}`}>大模型回答</button>
            <button type="button" onClick={() => setShowSettings((v) => !v)} className={`px-3 py-1.5 rounded-md border text-xs font-semibold ${showSettings ? "border-accent/50 bg-accent/20 text-accent" : "border-border-subtle text-text-muted hover:text-text-primary"}`}>模型设置</button>
            <div className="text-[11px] text-text-muted">图谱节点：{nodeCount}，文档节点：{docCount}</div>
          </div>

          {showSettings && (
            <div className="grid grid-cols-1 md:grid-cols-[120px_160px_1fr_160px_100px] gap-2 border border-border-subtle rounded-lg p-3 bg-elevated/30">
              <label className="flex items-center gap-2 text-xs text-text-secondary">
                <input type="checkbox" checked={settings.enabled} onChange={(e) => updateSettings({ enabled: e.target.checked })} />启用模型
              </label>
              <select value={settings.provider} onChange={(e) => updateSettings({ provider: e.target.value as LlmSettings["provider"] })} className="bg-surface text-text-primary text-xs rounded px-2 py-1.5 border border-border-subtle focus:outline-none focus:border-accent/50">
                <option value="auto">自动识别</option>
                <option value="openai">OpenAI兼容</option>
                <option value="routin-plan">RoutIn Plan</option>
              </select>
              <input value={settings.endpoint} onChange={(e) => updateSettings({ endpoint: e.target.value })} placeholder="Base URL；RoutIn Plan 填 https://api.routin.ai/plan/v1" className="bg-surface text-text-primary text-xs rounded px-2 py-1.5 border border-border-subtle focus:outline-none focus:border-accent/50" />
              <input value={settings.model} onChange={(e) => updateSettings({ model: e.target.value })} placeholder="model" className="bg-surface text-text-primary text-xs rounded px-2 py-1.5 border border-border-subtle focus:outline-none focus:border-accent/50" />
              <button type="button" onClick={() => void testModel()} disabled={testing} className="px-2 py-1.5 rounded border border-accent/30 text-xs text-accent disabled:opacity-60">{testing ? "测试中" : "测试连接"}</button>
              <div className="hidden md:block" />
              <div className="hidden md:block text-[10px] text-text-muted pt-1">plan- key 会走 /messages</div>
              <input type="password" value={settings.apiKey} onChange={(e) => updateSettings({ apiKey: e.target.value })} placeholder="API Key；通过本地后端代理发送，不从浏览器直连模型" className="md:col-span-3 bg-surface text-text-primary text-xs rounded px-2 py-1.5 border border-border-subtle focus:outline-none focus:border-accent/50" />
            </div>
          )}

          <div className="flex gap-2">
            <textarea
              value={question}
              onChange={(event) => setQuestion(event.target.value)}
              onKeyDown={(event) => {
                if ((event.ctrlKey || event.metaKey) && event.key === "Enter") void submit();
              }}
              placeholder="例如：黑名单是什么？回评是什么？对外 API 契约（草案）；风险与反欺诈有哪些接口？"
              className="flex-1 min-h-[76px] resize-none bg-elevated text-text-primary text-sm rounded-lg px-3 py-2 border border-border-subtle focus:outline-none focus:border-accent/50 placeholder-text-muted"
            />
            <button type="button" onClick={() => void submit()} disabled={loading} className="px-4 py-2 rounded-lg bg-accent/20 text-accent hover:text-accent-bright border border-accent/30 text-sm font-semibold transition-colors disabled:opacity-60">
              {loading ? "处理中" : mode === "llm" ? "问答" : "检索"}
            </button>
          </div>
          <div className="flex flex-wrap items-center gap-3 text-[11px] text-text-muted">
            <span>快捷键：Ctrl + Enter 提交。</span>
            <span>状态：<span className="text-accent">{stageLabel(requestStatus.stage)}</span></span>
            {requestStatus.endpoint && <span>Endpoint：{requestStatus.endpoint}</span>}
            {requestStatus.protocol && <span>协议：{requestStatus.protocol}</span>}
            {typeof requestStatus.elapsedMs === "number" && <span>耗时：{requestStatus.elapsedMs} ms</span>}
          </div>
        </div>

        <div className="flex-1 min-h-0 overflow-auto p-5 grid grid-cols-1 lg:grid-cols-[1fr_440px] gap-5">
          <section className="min-w-0 space-y-3">
            <div className="flex items-center justify-between mb-2">
              <h3 className="text-[11px] font-semibold text-accent uppercase tracking-wider">回答 / 请求状态</h3>
              {submittedQuestion && decision && <span className={`text-[11px] ${decision.allowed ? "text-accent" : "text-text-muted"}`}>证据判断：{decision.reason}</span>}
            </div>
            {error && <div className="text-xs text-red-300 border border-red-400/30 bg-red-500/10 rounded p-2">错误：{error}</div>}
            <pre className="whitespace-pre-wrap text-sm leading-relaxed text-text-secondary bg-elevated/50 border border-border-subtle rounded-lg p-4 font-sans min-h-[260px]">
              {submittedQuestion || answer ? answer || "等待后端返回。" : "输入问题后点击“检索”或“问答”。"}
            </pre>
            {selectedHit && (
              <div className="border border-border-subtle rounded-lg bg-elevated/40 p-3">
                <div className="text-xs text-accent mb-1">当前证据内容</div>
                <div className="text-sm text-text-primary">{selectedHit.docTitle} / {selectedHit.sectionTitle}</div>
                <div className="text-[11px] text-text-muted mt-1">{selectedHit.docPath}</div>
                <pre className="mt-2 max-h-[260px] overflow-auto whitespace-pre-wrap text-xs leading-relaxed text-text-secondary font-sans">{selectedHit.evidenceContent || selectedHit.snippet}</pre>
              </div>
            )}
          </section>

          <section className="min-w-0">
            <h3 className="text-[11px] font-semibold text-accent uppercase tracking-wider mb-2">本地命中证据与排序依据</h3>
            <div className="space-y-2">
              {hits.length === 0 && submittedQuestion && (
                <div className="text-sm text-text-muted border border-border-subtle rounded-lg p-3">暂无命中。</div>
              )}
              {hits.slice(0, 12).map((hit) => (
                <div key={hit.chunkId} className="border border-border-subtle bg-elevated/40 hover:border-accent/40 rounded-lg p-3 transition-colors">
                  <button
                    type="button"
                    onClick={() => navigateToNodeInLayer(hit.nodeId)}
                    className="w-full text-left"
                  >
                    <div className="flex items-start justify-between gap-2">
                      <div className="min-w-0">
                        <div className="text-sm text-text-primary truncate">{hit.docTitle}</div>
                        <div className="text-[11px] text-accent mt-1 truncate">{hit.sectionTitle}</div>
                      </div>
                      <div className="text-[11px] text-accent shrink-0">{hit.score.toFixed(1)}</div>
                    </div>
                    <div className="text-[11px] text-text-muted mt-1 truncate">{hit.docPath}</div>
                  </button>
                  <button type="button" onClick={() => setSelectedHit(hit)} className="mt-2 text-left text-xs text-text-secondary line-clamp-4 hover:text-text-primary">{hit.snippet}</button>
                  <div className="mt-2 grid grid-cols-4 gap-1 text-[10px] text-text-muted">
                    <span>exact {hit.scores.exact.toFixed(0)}</span>
                    <span>fuzzy {hit.scores.fuzzy.toFixed(0)}</span>
                    <span>semantic {hit.scores.semantic.toFixed(0)}</span>
                    <span>boost {(hit.scores.titleBoost + hit.scores.sectionBoost + hit.scores.directoryBoost + hit.scores.chunkTypeBoost).toFixed(0)}</span>
                  </div>
                  <div className="mt-2 flex flex-wrap gap-1">
                    {hit.reasons.slice(0, 5).map((reason, index) => (
                      <span key={`${hit.chunkId}-${index}`} className="text-[10px] text-text-muted border border-border-subtle rounded px-1.5 py-0.5">
                        {reason.message} +{reason.score.toFixed(0)}
                      </span>
                    ))}
                  </div>
                </div>
              ))}
            </div>
          </section>
        </div>
      </div>
    </div>
  );
}
