import { useEffect, useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { AppIcon } from "../components/PixelIcons";
import { Card, Loading, ErrorState, RefreshButton } from "../components/ui";
import {
  aiApi,
  AI_PROVIDER_META,
  type AIProviderId,
  type AIConfigBundle,
  type DataSourcesPayload,
  type EnvField,
  type McpCatalogItem,
  type SkillLibraryItem,
  type SkillLibraryPayload,
} from "../lib/ai";

const SCOPE_LABEL: Record<SkillLibraryItem["scope"], string> = {
  route: "路徑",
  card: "卡片",
  global: "全域",
};

function KeyField({
  f,
  secrets,
  setSecrets,
}: {
  f: EnvField;
  secrets: Record<string, string>;
  setSecrets: React.Dispatch<React.SetStateAction<Record<string, string>>>;
}) {
  const isSecret = f.type === "secret";
  const placeholder = f.hasValue
    ? isSecret
      ? `已設定 ${f.masked}，留空保留`
      : `已設定 ${f.value || "（有值）"}，留空保留`
    : f.hint || "";
  return (
    <div className="settings-field">
      <label>
        {f.label}
        {f.required && <span className="settings-required">必備</span>}
        {f.hasValue && <span className="settings-set">已設定</span>}
      </label>
      <input
        type={isSecret ? "password" : "text"}
        placeholder={placeholder}
        value={secrets[f.key] || ""}
        onChange={(e) => setSecrets((s) => ({ ...s, [f.key]: e.target.value }))}
      />
      {f.hint && <p className="small muted">{f.hint}</p>}
      {f.signupUrl && (
        <p className="small">
          <a href={f.signupUrl} target="_blank" rel="noreferrer">
            免費申請 →
          </a>
        </p>
      )}
      {f.usedBy?.length ? (
        <p className="small muted">用於：{f.usedBy.join("、")}</p>
      ) : null}
    </div>
  );
}

const PROVIDER_KEYS: Record<AIProviderId, { key: string; model: string }> = {
  grok: { key: "GROK_API_KEY", model: "GROK_MODEL" },
  "opencode-go": { key: "OPENCODE_GO_API_KEY", model: "OPENCODE_GO_MODEL" },
};

function ProviderModelSelect({
  providerId,
  modelEnvValue,
  apiKeySecret,
  hasSavedKey,
  value,
  onChange,
}: {
  providerId: AIProviderId;
  modelEnvValue?: string;
  apiKeySecret: string;
  hasSavedKey: boolean;
  value: string;
  onChange: (model: string) => void;
}) {
  const canFetch = hasSavedKey || !!apiKeySecret.trim();
  const modelsQ = useQuery({
    queryKey: ["settings-ai-models", providerId, apiKeySecret.trim() || (hasSavedKey ? "env" : "")],
    queryFn: () => aiApi.models(providerId, apiKeySecret.trim() || undefined),
    enabled: canFetch,
    staleTime: 120_000,
    retry: 1,
  });
  const options = modelsQ.data?.models || [];
  const current = value || modelEnvValue || "";

  return (
    <div className="settings-model-row">
      <select
        value={current}
        disabled={!canFetch || modelsQ.isLoading}
        onChange={(e) => onChange(e.target.value)}
      >
        {!canFetch ? <option value="">請先填入 API Key</option> : null}
        {canFetch && modelsQ.isLoading ? <option value="">載入模型中…</option> : null}
        {canFetch && !modelsQ.isLoading && modelsQ.isError ? (
          <option value="">抓取失敗：{(modelsQ.error as Error)?.message || "未知錯誤"}</option>
        ) : null}
        {canFetch && !modelsQ.isLoading && !modelsQ.isError && options.length === 0 ? (
          <option value="">無可用模型</option>
        ) : null}
        {options.map((m) => (
          <option key={m.id} value={m.id}>
            {m.id}
          </option>
        ))}
        {current && !options.some((m) => m.id === current) ? (
          <option value={current}>{current}（目前已儲存）</option>
        ) : null}
      </select>
      <button
        type="button"
        className="btn-ghost"
        disabled={!canFetch || modelsQ.isFetching}
        onClick={() => void modelsQ.refetch()}
      >
        {modelsQ.isFetching ? "抓取中…" : "重新抓取"}
      </button>
    </div>
  );
}

export default function Settings() {
  const qc = useQueryClient();
  const envQ = useQuery({ queryKey: ["settings-env"], queryFn: aiApi.getEnv });
  const dsQ = useQuery({ queryKey: ["settings-data-sources"], queryFn: aiApi.getDataSources });
  const aiCfgQ = useQuery({ queryKey: ["settings-ai-config"], queryFn: aiApi.getAiConfig });

  const [active, setActive] = useState<AIProviderId>("grok");
  const [secrets, setSecrets] = useState<Record<string, string>>({});
  const [models, setModels] = useState<Record<string, string>>({});
  const [msg, setMsg] = useState("");

  const [agentMd, setAgentMd] = useState("");
  const [skillLibrary, setSkillLibrary] = useState<SkillLibraryPayload | null>(null);
  const [skillPaste, setSkillPaste] = useState("");
  const [skillPreview, setSkillPreview] = useState<SkillLibraryItem[] | null>(null);
  const [skillBusy, setSkillBusy] = useState(false);
  const [mcpEnabled, setMcpEnabled] = useState<string[]>([]);
  const [mcpCatalog, setMcpCatalog] = useState<McpCatalogItem[]>([]);
  const [mcpStatus, setMcpStatus] = useState<McpCatalogItem[]>([]);
  const [mcpProbing, setMcpProbing] = useState(false);
  const [configDir, setConfigDir] = useState("");
  const [settingsRefreshing, setSettingsRefreshing] = useState(false);

  useEffect(() => {
    if (!envQ.data) return;
    const activeField = envQ.data.fields.find((f) => f.key === "AI_ACTIVE_PROVIDER");
    if (activeField?.value === "opencode-go" || activeField?.value === "grok") {
      setActive(activeField.value);
    }
    const m: Record<string, string> = {};
    for (const f of envQ.data.fields) {
      if (f.type === "text" && f.value) m[f.key] = f.value;
    }
    setModels(m);
  }, [envQ.data]);

  useEffect(() => {
    if (!aiCfgQ.data) return;
    const d = aiCfgQ.data as AIConfigBundle;
    setAgentMd(d.agentMd || "");
    if (d.skillLibrary) setSkillLibrary(d.skillLibrary);
    setMcpEnabled(d.mcp?.enabled || []);
    setMcpCatalog(d.mcpCatalog || []);
    setConfigDir(d.configDir || "");
  }, [aiCfgQ.data]);

  if (envQ.isLoading) return <Loading />;
  if (envQ.error || !envQ.data) return <ErrorState detail={(envQ.error as Error)?.message} />;

  const aiCfgError = aiCfgQ.error ? (aiCfgQ.error as Error).message : "";
  const aiCfgReady = !!aiCfgQ.data && !aiCfgQ.error;

  const fields = envQ.data.fields;
  const field = (key: string) => fields.find((f) => f.key === key) as EnvField | undefined;

  const saveEnv = async () => {
    const values: Record<string, string> = { AI_ACTIVE_PROVIDER: active, ...models };
    for (const [k, v] of Object.entries(secrets)) {
      if (v.trim()) values[k] = v.trim();
    }
    await aiApi.saveEnv(values);
    setSecrets({});
    await qc.invalidateQueries({ queryKey: ["settings-env"] });
    await qc.invalidateQueries({ queryKey: ["ai-status"] });
    setMsg("API 設定已寫入 .env。");
  };

  const saveAiConfig = async () => {
    await aiApi.saveAiConfig({
      agentMd,
      mcp: { enabled: mcpEnabled },
    });
    await qc.invalidateQueries({ queryKey: ["settings-ai-config"] });
    setMsg("導覽員設定已儲存。下次問 AI 會帶入 agent.md 與 MCP 清單。");
  };

  const refreshSkills = async () => {
    const lib = await aiApi.getSkillLibrary();
    setSkillLibrary(lib);
    await qc.invalidateQueries({ queryKey: ["settings-ai-config"] });
    return lib;
  };

  const previewSkillPaste = async () => {
    setSkillBusy(true);
    try {
      const d = await aiApi.previewSkillPaste(skillPaste);
      setSkillPreview(d.skills);
      setMsg(`已解析 ${d.skills.length} 個技能，確認後按「安裝技能」。`);
    } catch (e) {
      setSkillPreview(null);
      setMsg((e as Error).message || "解析失敗");
    } finally {
      setSkillBusy(false);
    }
  };

  const installSkillPaste = async () => {
    setSkillBusy(true);
    try {
      const d = await aiApi.installSkill({ paste: skillPaste });
      if (d.library) setSkillLibrary(d.library);
      else await refreshSkills();
      setSkillPreview(null);
      setSkillPaste("");
      setMsg(d.skill ? `已安裝技能「${d.skill.label}」` : "技能已安裝");
    } catch (e) {
      setMsg((e as Error).message || "安裝失敗");
    } finally {
      setSkillBusy(false);
    }
  };

  const toggleSkill = async (id: string, enabled: boolean) => {
    const d = await aiApi.toggleSkill(id, enabled);
    setSkillLibrary(d.library);
    setMsg(enabled ? "技能已啟用" : "技能已停用");
  };

  const deleteSkill = async (id: string, label: string) => {
    if (!confirm(`刪除自訂技能「${label}」？`)) return;
    const d = await aiApi.deleteSkill(id);
    setSkillLibrary(d.library);
    setMsg("已刪除自訂技能");
  };

  const resetAiConfig = async () => {
    await aiApi.resetAiConfig();
    await qc.invalidateQueries({ queryKey: ["settings-ai-config"] });
    setMsg("已還原預設 agent.md、skills、mcp。");
  };

  const toggleMcp = (id: string) => {
    setMcpEnabled((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
  };

  const probeMcp = async () => {
    setMcpProbing(true);
    setMsg("測試 MCP 連線中（首次 npx 可能較久）…");
    try {
      const d = await aiApi.mcpStatus(true);
      setMcpStatus(d.servers || []);
      const ok = (d.servers || []).filter((s) => s.enabled && s.status === "ok").length;
      const err = (d.servers || []).filter((s) => s.enabled && (s.status === "error" || s.status === "needs_key")).length;
      setMsg(`MCP 測試完成：${ok} 個可用，${err} 個需處理。`);
    } catch (e) {
      setMsg(`MCP 測試失敗：${(e as Error).message}`);
    } finally {
      setMcpProbing(false);
    }
  };

  const mcpBadge = (id: string) => {
    const s = mcpStatus.find((x) => x.id === id);
    if (!s || !mcpEnabled.includes(id)) return null;
    if (s.status === "ok") return <span className="mcp-badge ok">可用</span>;
    if (s.status === "needs_key") return <span className="mcp-badge warn">需 Key</span>;
    if (s.status === "error") return <span className="mcp-badge err">失敗</span>;
    if (s.status === "ready") return <span className="mcp-badge">待測</span>;
    return null;
  };

  const testProvider = async (provider: AIProviderId) => {
    await saveEnv().catch(() => {});
    await saveAiConfig().catch(() => {});
    setMsg("測試連線中…");
    try {
      const ctx = await aiApi.context("settings", {});
      const res = await aiApi.chat({
        provider,
        model: models[PROVIDER_KEYS[provider].model],
        question: "請用一句話確認連線測試成功，並用 JRPG 導覽員語氣。",
        context: ctx,
      });
      setMsg(`測試成功：${res.text?.slice(0, 120)}`);
    } catch (e) {
      setMsg(`測試失敗：${(e as Error).message}`);
    }
  };

  const groups = envQ.data.groups || { market: "市場與總經", mcp: "MCP 擴充", ai: "AI" };
  const marketFields = fields.filter((f) => f.group === "market");
  const mcpFields = fields.filter((f) => f.group === "mcp");
  const integrated = (dsQ.data as DataSourcesPayload | undefined)?.integrated || [];

  const refreshSettingsStatus = async () => {
    setSettingsRefreshing(true);
    try {
      await Promise.all([
        qc.invalidateQueries({ queryKey: ["settings-env"] }),
        qc.invalidateQueries({ queryKey: ["settings-data-sources"] }),
        qc.invalidateQueries({ queryKey: ["settings-ai-config"] }),
        qc.invalidateQueries({ queryKey: ["ai-status"] }),
      ]);
      setMsg("已重新檢查 API 金鑰與資料來源狀態。");
    } finally {
      setSettingsRefreshing(false);
    }
  };

  return (
    <>
      <div className="page-head">
        <div className="eyebrow">系統</div>
        <h1 className="page-title-row">
          <AppIcon name="gear" size={38} framed glow variant="hero" />
          設定
        </h1>
        <p>
          API 金鑰 → <code>{envQ.data.envPath}</code>
          {configDir && (
            <>
              {" "}
              · 導覽員 → <code>{configDir}</code>
            </>
          )}
        </p>
      </div>

      <Card
        title="已接入免費資料（不需 Key）"
        onRefresh={() => void refreshSettingsStatus()}
        refreshing={settingsRefreshing || dsQ.isFetching}
        refreshLabel="重新檢查"
      >
        <p className="small muted" style={{ marginBottom: 10 }}>
          以下已在專案或 MCP 中<b>真實接通</b>，可直接使用。
        </p>
        <div className="settings-source-grid">
          {integrated
            .filter((s) => !s.needsKey)
            .map((s) => (
              <div key={s.id} className="settings-source-item">
                <b>{s.name}</b>
                <span className="small muted">{s.hint}</span>
                <span className="settings-source-via small">{s.via}</span>
              </div>
            ))}
        </div>
      </Card>

      <Card
        title={groups.market || "市場與總經"}
        className="mt"
        onRefresh={() => void refreshSettingsStatus()}
        refreshing={settingsRefreshing || envQ.isFetching}
        refreshLabel="重新檢查"
      >
        <p className="small muted" style={{ marginBottom: 10 }}>
          <b>FRED</b> 為總經必備；其餘為免費選用 Key，欄位先留空，之後會逐步接入。
        </p>
        {marketFields.map((f) => (
          <KeyField key={f.key} f={f} secrets={secrets} setSecrets={setSecrets} />
        ))}
        <button type="button" className="btn-primary mt-s" onClick={() => void saveEnv()}>
          儲存{groups.market}
        </button>
      </Card>

      <Card title={groups.mcp || "MCP 擴充資料"} className="mt">
        <p className="small muted" style={{ marginBottom: 10 }}>
          啟用對應 MCP 後，問 AI 會真實呼叫。未填 Key 的來源仍可用免費部分（如 stock-scanner 恐懼貪婪）。
        </p>
        {mcpFields.map((f) => (
          <KeyField key={f.key} f={f} secrets={secrets} setSecrets={setSecrets} />
        ))}
        <button type="button" className="btn-ghost mt-s" onClick={() => void saveEnv()}>
          儲存 MCP 金鑰
        </button>
      </Card>

      <Card title="AI Provider" className="mt">
        <p className="small muted" style={{ marginBottom: 12 }}>
          Grok 或 OpenCode Go。問 AI 時會附上頁面資料 + 下方「導覽員」設定。
        </p>
        <div className="settings-providers">
          {(Object.keys(AI_PROVIDER_META) as AIProviderId[]).map((id) => {
            const meta = AI_PROVIDER_META[id];
            const keys = PROVIDER_KEYS[id];
            const keyF = field(keys.key);
            const modelF = field(keys.model);
            return (
              <div key={id} className="settings-provider-card">
                <div className="settings-provider-head">
                  <div>
                    <b>{meta.label}</b>
                    <span className="small muted">{meta.hint}</span>
                  </div>
                  <label className="settings-radio">
                    <input type="radio" name="active" checked={active === id} onChange={() => setActive(id)} />
                    預設
                  </label>
                </div>
                <div className="settings-field">
                  <label>API Key</label>
                  <input
                    type="password"
                    placeholder={keyF?.hasValue ? `已設定 ${keyF.masked}` : "貼上 API key"}
                    value={secrets[keys.key] || ""}
                    onChange={(e) => setSecrets((s) => ({ ...s, [keys.key]: e.target.value }))}
                  />
                </div>
                <div className="settings-field">
                  <label>Model</label>
                  <ProviderModelSelect
                    providerId={id}
                    modelEnvValue={modelF?.value}
                    apiKeySecret={secrets[keys.key] || ""}
                    hasSavedKey={!!keyF?.hasValue}
                    value={models[keys.model] ?? ""}
                    onChange={(model) => setModels((m) => ({ ...m, [keys.model]: model }))}
                  />
                  <p className="small muted">填入 API Key 後會自動抓取該 Provider 支援的模型；選好後記得儲存。</p>
                </div>
                <button type="button" className="btn-ghost" onClick={() => void testProvider(id)}>
                  測試連線
                </button>
              </div>
            );
          })}
        </div>
        <button type="button" className="btn-primary mt-s" onClick={() => void saveEnv()}>
          儲存 API 設定
        </button>
      </Card>

      {aiCfgQ.isLoading && (
        <Card title="導覽員設定" className="mt">
          <p className="small muted">載入 agent.md / 技能庫 / MCP…</p>
        </Card>
      )}

      {aiCfgError && (
        <Card title="導覽員設定" className="mt">
          <p className="small" style={{ color: "var(--coral)" }}>
            讀取失敗：{aiCfgError}
          </p>
          <p className="small muted" style={{ marginTop: 8 }}>
            多半是後端尚未重啟。請重啟 <code>app/server.js</code> 後按下方重試。
          </p>
          <button type="button" className="btn-ghost mt-s" onClick={() => void qc.invalidateQueries({ queryKey: ["settings-ai-config"] })}>
            重試載入
          </button>
        </Card>
      )}

      {aiCfgReady && (
        <>
          <Card title="導覽員 · agent.md" className="mt">
            <p className="small muted" style={{ marginBottom: 10 }}>
              寫入 <code>agent.md</code>，每次問 AI 會當成 <b>system prompt</b> 帶入（人設、語氣、規則）。
            </p>
            <textarea
              className="settings-code"
              rows={14}
              value={agentMd}
              onChange={(e) => setAgentMd(e.target.value)}
              spellCheck={false}
            />
          </Card>

          <Card title="技能庫 · 可安裝多個" className="mt">
            <p className="small muted" style={{ marginBottom: 10 }}>
              勾選啟用的技能會出現在聊天視窗快捷按鈕。內建技能可開關；自訂技能可貼上新增或刪除。
              {skillLibrary ? (
                <>
                  {" "}
                  共 {skillLibrary.stats.total} 個，已啟用 {skillLibrary.stats.enabled} 個（自訂{" "}
                  {skillLibrary.stats.custom}）。
                </>
              ) : null}
            </p>
            <div className="settings-skill-grid">
              {(skillLibrary?.skills || []).map((s) => (
                <label key={s.id} className="settings-skill-item">
                  <input
                    type="checkbox"
                    checked={s.enabled}
                    onChange={() => void toggleSkill(s.id, !s.enabled)}
                  />
                  <span>
                    <b>
                      {s.label}
                      <span className="settings-skill-tag">{s.source === "custom" ? "自訂" : "內建"}</span>
                    </b>
                    <span className="small muted">
                      {SCOPE_LABEL[s.scope]}：{s.match || "—"} · {s.pack}
                    </span>
                    {s.editable && (
                      <button
                        type="button"
                        className="settings-skill-del"
                        onClick={(e) => {
                          e.preventDefault();
                          void deleteSkill(s.id, s.label);
                        }}
                      >
                        刪除
                      </button>
                    )}
                  </span>
                </label>
              ))}
            </div>
          </Card>

          <Card title="建立技能 · 貼上即可新增" className="mt">
            <p className="small muted" style={{ marginBottom: 10 }}>
              貼上下方格式或 JSON，按「解析預覽」後再「安裝技能」。也可以直接把 SKILL.md 內容貼給 AI 請它幫你轉成此格式。
            </p>
            <div className="settings-skill-actions">
              <button
                type="button"
                className="btn-ghost"
                onClick={() => setSkillPaste(skillLibrary?.template || "")}
              >
                插入範本
              </button>
            </div>
            <textarea
              className="settings-code"
              rows={12}
              value={skillPaste}
              onChange={(e) => {
                setSkillPaste(e.target.value);
                setSkillPreview(null);
              }}
              placeholder="id: my-skill&#10;label: 技能名稱&#10;scope: route&#10;match: /market&#10;prompt: |&#10;  你的提示詞…"
              spellCheck={false}
            />
            {skillPreview && skillPreview.length > 0 && (
              <div className="settings-skill-preview">
                <div className="small" style={{ color: "var(--teal)", marginBottom: 6 }}>
                  預覽
                </div>
                {skillPreview.map((s) => (
                  <div key={s.id} className="small muted">
                    <b>{s.label}</b>（{s.id}）· {SCOPE_LABEL[s.scope]} {s.match}
                  </div>
                ))}
              </div>
            )}
            <div className="settings-actions mt-s">
              <button
                type="button"
                className="btn-ghost"
                disabled={skillBusy || !skillPaste.trim()}
                onClick={() => void previewSkillPaste()}
              >
                解析預覽
              </button>
              <button
                type="button"
                className="btn-primary"
                disabled={skillBusy || !skillPaste.trim()}
                onClick={() => void installSkillPaste()}
              >
                安裝技能
              </button>
            </div>
          </Card>

          <Card title="MCP 工具（真實呼叫）" className="mt">
            <p className="small muted" style={{ marginBottom: 10 }}>
              勾選後，問 AI 時<b>後端會真的 spawn MCP</b> 抓資料（報價、恐懼貪婪、內部人交易等），結果附在對話裡。
            </p>
            <div className="settings-mcp-grid">
              {mcpCatalog.map((m) => (
                <label key={m.id} className="settings-mcp-item">
                  <input type="checkbox" checked={mcpEnabled.includes(m.id)} onChange={() => toggleMcp(m.id)} />
                  <span>
                    <b>
                      {m.name} {mcpBadge(m.id)}
                    </b>
                    <span className="small muted">
                      {m.hint}
                      {m.free ? " · 免費" : ""}
                      {m.needsKey && !m.hasKey ? " · 需 API Key" : ""}
                      {m.toolCount ? ` · ${m.toolCount} tools` : ""}
                    </span>
                  </span>
                </label>
              ))}
            </div>
            <button type="button" className="btn-ghost mt-s" disabled={mcpProbing} onClick={() => void probeMcp()}>
              {mcpProbing ? "測試中…" : "測試已啟用 MCP"}
            </button>
          </Card>

          <div className="settings-actions mt">
            <button type="button" className="btn-primary" onClick={() => void saveAiConfig()}>
              儲存導覽員設定
            </button>
            <button type="button" className="btn-ghost" onClick={() => void resetAiConfig()}>
              還原預設
            </button>
          </div>
        </>
      )}

      {msg && <p className="settings-msg small">{msg}</p>}
    </>
  );
}
