// API Keys management screen
const MODAL_OVERLAY = {
  position: "fixed", inset: 0,
  background: "oklch(0.08 0.012 240 / 0.7)",
  backdropFilter: "blur(6px)",
  zIndex: 50,
  display: "grid", placeItems: "center",
};

// The list endpoint returns key_masked already formatted by the server (the
// database has no plaintext to reconstruct). Falls back to a placeholder if
// the field is missing, never to the raw key — there is no raw key to show.
function maskKey(k) {
  if (k && typeof k.key_masked === "string" && k.key_masked !== "") return k.key_masked;
  return "sk_live_••••";
}

function ModalError({ message }) {
  if (!message) return null;
  return (
    <div style={{
      padding: "9px 12px", borderRadius: 7, fontSize: 12.5,
      background: "rgba(229,72,77,0.12)", border: "1px solid var(--red)", color: "var(--red)",
    }}>{message}</div>
  );
}

// Same auto-hide window the Dashboard uses for its key card. Keeps the
// behaviour consistent across the two places a user sees their plaintext.
const KEYS_REVEAL_AUTO_HIDE_MS = 15000;

function KeysPage({ onNavigate }) {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [keys, setKeys] = useState([]);
  const [tab, setTab] = useState("all");
  const [busyId, setBusyId] = useState(null);
  const [showNewKey, setShowNewKey] = useState(false);
  const [revealKey, setRevealKey] = useState(null);
  // Which row is currently revealed, the copy-just-happened row, and the
  // last inline help/error keyed by row id. All scoped per row so revealing
  // one key never touches the others.
  const [revealedId, setRevealedId] = useState(null);
  const [copiedId, setCopiedId] = useState(null);
  const [rowNotice, setRowNotice] = useState({});  // { [id]: "message" }
  const { guard, reauthModal } = useReauthGuard();

  // Collapse the revealed row back to its masked form after a short window.
  useEffect(() => {
    if (!revealedId) return undefined;
    const t = setTimeout(() => setRevealedId(null), KEYS_REVEAL_AUTO_HIDE_MS);
    return () => clearTimeout(t);
  }, [revealedId]);

  // The "copied" green-check is brief; clear it after a short timeout.
  useEffect(() => {
    if (!copiedId) return undefined;
    const t = setTimeout(() => setCopiedId(null), 1500);
    return () => clearTimeout(t);
  }, [copiedId]);

  // Guard against setState after unmount — see dashboard.jsx for the same
  // pattern. Stops a slow first fetch from logging a React warning when the
  // user navigates away before it resolves.
  const mountedRef = useRef(true);
  useEffect(() => () => { mountedRef.current = false; }, []);
  const load = () => {
    if (keys.length === 0) setLoading(true);
    setError("");
    API.cached("/keys", 30 * 1000)
      .then((res) => {
        if (!mountedRef.current) return;
        setKeys(res.keys || []);
        setLoading(false);
      })
      .catch((err) => {
        if (!mountedRef.current) return;
        setError(err.message || "Could not load your keys.");
        setLoading(false);
      });
  };

  useEffect(load, []);

  if (loading) return <LoadingScreen crumbs={["Console", "API Keys"]} />;
  if (error) return <ErrorScreen crumbs={["Console", "API Keys"]} message={error} onRetry={load} />;

  const activeKeys = keys.filter((k) => !k.revoked);
  const revokedKeys = keys.filter((k) => k.revoked);
  const now = Date.now();
  const staleKeys = activeKeys.filter(
    (k) => !k.last_used_at || now - new Date(k.last_used_at).getTime() > 21 * 864e5
  );
  const totalRequests = keys.reduce((s, k) => s + (k.request_count || 0), 0);

  const shown = tab === "active" ? activeKeys : tab === "revoked" ? revokedKeys : keys;

  const onCreated = (key) => {
    setShowNewKey(false);
    setKeys((prev) => [key, ...prev]);
    API.invalidate("/keys");
    setRevealKey(key);
  };

  const rotate = (id) => {
    if (busyId) return;
    setBusyId(id);
    API.post("/keys/" + id)
      .then((res) => {
        setKeys((prev) => prev.map((k) => (k.id === id ? res.key : k)));
        API.invalidate("/keys");
        setBusyId(null);
        setRevealKey(res.key);
      })
      .catch((err) => { setError(err.message || "Could not rotate the key."); setBusyId(null); });
  };

  const revoke = (id) => {
    if (busyId) return;
    if (!window.confirm("Revoke this key? Applications using it will immediately stop working.")) return;
    setBusyId(id);
    API.del("/keys/" + id)
      .then(() => {
        setKeys((prev) => prev.map((k) => (k.id === id ? { ...k, revoked: true } : k)));
        API.invalidate("/keys");
        setBusyId(null);
      })
      .catch((err) => { setError(err.message || "Could not revoke the key."); setBusyId(null); });
  };

  // Reveal toggle. Legacy rows (api_key === null) cannot be revealed — the
  // server has no plaintext for them; we post an inline help line on their
  // row pointing the user at Rotate instead of leaving the button silent.
  const setRowMessage = (id, msg) => {
    setRowNotice((prev) => Object.assign({}, prev, { [id]: msg }));
  };
  const toggleReveal = (k) => {
    if (!k.api_key) {
      setRowMessage(k.id, "This key was issued before display was enabled. Click Rotate to issue a fresh key you can copy.");
      return;
    }
    setRowMessage(k.id, "");
    setRevealedId((cur) => (cur === k.id ? null : k.id));
  };
  const copyRow = (k) => {
    if (!k.api_key) {
      setRowMessage(k.id, "This key was issued before display was enabled. Click Rotate to issue a fresh key you can copy.");
      return;
    }
    copyToClipboard(k.api_key)
      .then(() => { setCopiedId(k.id); setRowMessage(k.id, ""); })
      .catch(() => setRowMessage(k.id, "Could not copy automatically — reveal the key and copy manually."));
  };

  const tabs = [
    ["all", "All keys (" + keys.length + ")"],
    ["active", "Active (" + activeKeys.length + ")"],
    ["revoked", "Revoked (" + revokedKeys.length + ")"],
  ];

  return (
    <>
      <TopBar crumbs={["Console", "Production", "API Keys"]} />
      <div className="page">
        <div className="page-head row" style={{ alignItems: "flex-end", justifyContent: "space-between" }}>
          <div>
            <span className="eyebrow">Access control</span>
            <h1 className="page-title">API keys</h1>
            <p className="page-sub">
              {activeKeys.length} active {activeKeys.length === 1 ? "key" : "keys"}. Rotate or revoke
              any key without breaking traffic on the others.
            </p>
          </div>
          <div className="row gap-10">
            <button className="btn" onClick={() => onNavigate && onNavigate("docs")}>
              <Icon.Docs style={{ width: 14, height: 14 }} /> Auth docs
            </button>
            <button className="btn btn-primary"
              onClick={() => guard(() => setShowNewKey(true),
                { message: "Creating an API key is a sensitive action. Confirm your password to continue." })}>
              <Icon.Plus style={{ width: 14, height: 14 }} /> Create new key
            </button>
          </div>
        </div>

        {/* Security posture mini-cards */}
        <div className="grid g-4" style={{ marginBottom: 20 }}>
          <SecurityTile ok title="Active keys" detail={activeKeys.length + " in use"}
            hint="Keys authenticating gateway traffic" />
          <SecurityTile ok={staleKeys.length === 0} warn={staleKeys.length > 0}
            title="Stale keys" detail={staleKeys.length + " unused 21d+"}
            hint={staleKeys.length > 0 ? "Consider revoking" : "All keys are active"} />
          <SecurityTile ok title="Revoked keys" detail={revokedKeys.length + " revoked"}
            hint="Permanently disabled" />
          <SecurityTile ok title="Calls (30d)" detail={Fmt.num(totalRequests)}
            hint="Across all of your keys" />
        </div>

        {/* Tabs */}
        <div className="row" style={{ borderBottom: "1px solid var(--border-soft)", marginBottom: 0 }}>
          {tabs.map(([id, label]) => (
            <button key={id} className="btn-ghost" onClick={() => setTab(id)} style={{
              padding: "10px 14px", fontSize: 13,
              color: tab === id ? "var(--text)" : "var(--muted)",
              borderBottom: tab === id ? "2px solid var(--green)" : "2px solid transparent",
              borderRadius: 0,
              fontFamily: "var(--ff-sans)",
            }}>{label}</button>
          ))}
        </div>

        {/* Keys table */}
        <div className="card" style={{ borderTopLeftRadius: 0, borderTopRightRadius: 0, borderTop: 0 }}>
          <div className="scroll-x">
            <table className="tbl">
              <thead>
                <tr>
                  <th style={{ width: 32 }}></th>
                  <th>Name &amp; key</th>
                  <th>Env</th>
                  <th>Access</th>
                  <th>Last used</th>
                  <th>Calls (30d)</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {shown.length === 0 && (
                  <tr><td colSpan="7" className="text-sm muted" style={{ padding: "18px 12px" }}>
                    No keys in this view.
                  </td></tr>
                )}
                {shown.map((k) => {
                  const revealedHere = revealedId === k.id && !!k.api_key;
                  const copiedHere = copiedId === k.id;
                  const note = rowNotice[k.id];
                  return (
                    <tr key={k.id} style={{ opacity: k.revoked ? 0.5 : 1 }}>
                      <td>
                        <span className="dot" style={{
                          background: k.revoked ? "var(--faint)" : "var(--green)",
                          boxShadow: k.revoked ? "none" : "0 0 8px var(--green)",
                        }} />
                      </td>
                      <td>
                        <div style={{ fontWeight: 500, marginBottom: 3 }}>
                          {k.name}
                          {k.revoked && <span className="mono text-xs" style={{ marginLeft: 8, color: "var(--red)" }}>revoked</span>}
                        </div>
                        <div className="mono text-xs muted" style={{ wordBreak: "break-all" }}>
                          {revealedHere ? k.api_key : maskKey(k)}
                        </div>
                        {(revealedHere || copiedHere || note) && (
                          <div className="mono text-xs" style={{
                            marginTop: 4,
                            color: note ? "var(--amber)" : copiedHere ? "var(--green)" : "var(--muted)",
                          }}>
                            {copiedHere && "Copied."}
                            {!copiedHere && revealedHere && !note && `Hidden in ${Math.round(KEYS_REVEAL_AUTO_HIDE_MS / 1000)}s.`}
                            {note}
                          </div>
                        )}
                      </td>
                      <td><Tag kind="get">live</Tag></td>
                      <td>
                        <span className="mono text-xs" style={{ padding: "2px 6px", borderRadius: 4, background: "var(--panel-2)", color: "var(--text-dim)" }}>
                          all endpoints
                        </span>
                      </td>
                      <td>
                        <div className="mono text-xs">{Fmt.ago(k.last_used_at)}</div>
                        <div className="mono text-xs muted" style={{ marginTop: 2 }}>created {Fmt.date(k.created_at)}</div>
                      </td>
                      <td className="mono">{Fmt.num(k.request_count)}</td>
                      <td>
                        <div className="row gap-6">
                          <button className="icon-btn"
                            title={k.api_key ? (revealedHere ? "Hide" : "Reveal") : "Rotate first to get a visible key"}
                            disabled={k.revoked}
                            onClick={() => toggleReveal(k)}>
                            {revealedHere
                              ? <Icon.EyeOff style={{ width: 13, height: 13 }} />
                              : <Icon.Eye style={{ width: 13, height: 13 }} />}
                          </button>
                          <button className="icon-btn"
                            title={k.api_key ? "Copy" : "Rotate first to get a visible key"}
                            disabled={k.revoked}
                            onClick={() => copyRow(k)}>
                            {copiedHere
                              ? <Icon.Check style={{ width: 13, height: 13, color: "var(--green)" }} />
                              : <Icon.Copy style={{ width: 13, height: 13 }} />}
                          </button>
                          <button className="icon-btn" title="Rotate secret" disabled={k.revoked || busyId === k.id}
                            onClick={() => rotate(k.id)}>
                            <Icon.Refresh style={{ width: 13, height: 13 }} />
                          </button>
                          <button className="icon-btn" title="Revoke key" style={{ color: "var(--red)" }}
                            disabled={k.revoked || busyId === k.id}
                            onClick={() => guard(() => revoke(k.id),
                              { message: "Revoking a key is a sensitive action. Confirm your password to continue." })}>
                            <Icon.Trash style={{ width: 13, height: 13 }} />
                          </button>
                        </div>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>

        <div style={{ marginTop: 18, padding: 16, border: "1px dashed var(--border)", borderRadius: 10, fontSize: 13, color: "var(--text-dim)", display: "flex", gap: 12, alignItems: "center" }}>
          <Icon.Shield style={{ width: 16, height: 16, color: "var(--cyan)" }} />
          <span>
            Treat API keys like passwords. Anyone holding a key can spend your quota —
            <strong style={{ color: "var(--text)" }}> rotate immediately</strong> if one is exposed.
          </span>
        </div>
      </div>

      {showNewKey && <NewKeyModal onClose={() => setShowNewKey(false)} onCreated={onCreated} />}
      {revealKey && <KeyRevealModal apiKey={revealKey.api_key} name={revealKey.name} onClose={() => setRevealKey(null)} />}
      {reauthModal}
    </>
  );
}

function SecurityTile({ ok, warn, title, detail, hint }) {
  const color = warn ? "var(--amber)" : "var(--green)";
  return (
    <div className="stat">
      <div className="row" style={{ justifyContent: "space-between" }}>
        <span className="label">{title}</span>
        <span style={{
          width: 18, height: 18, borderRadius: "50%",
          background: warn ? "var(--amber-soft)" : "var(--green-soft)",
          color, display: "grid", placeItems: "center",
        }}>
          {warn ? (
            <svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2"><path d="M8 5v4M8 11.5v.5" strokeLinecap="round" /></svg>
          ) : (
            <Icon.Check style={{ width: 10, height: 10 }} />
          )}
        </span>
      </div>
      <div className="value" style={{ fontSize: 18, marginTop: 8 }}>{detail}</div>
      <div className="mono text-xs muted" style={{ marginTop: 6 }}>{hint}</div>
    </div>
  );
}

function NewKeyModal({ onClose, onCreated }) {
  const [label, setLabel] = useState("Production worker");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  const generate = () => {
    if (loading) return;
    setError("");
    if (!label.trim()) { setError("Give the key a label."); return; }
    setLoading(true);
    API.post("/keys", { name: label.trim() })
      .then((res) => { onCreated(res.key); })
      .catch((err) => { setError(err.message || "Could not create the key."); setLoading(false); });
  };

  return (
    <div style={MODAL_OVERLAY} onClick={onClose}>
      <div className="card" style={{ width: 480, boxShadow: "var(--shadow-lg)" }} onClick={(e) => e.stopPropagation()}>
        <div className="card-head">
          <span>Create new API key</span>
          <button className="icon-btn" style={{ marginLeft: "auto" }} onClick={onClose}>
            <Icon.X style={{ width: 13, height: 13 }} />
          </button>
        </div>

        <div className="card-body" style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <div className="field">
            <label>Label</label>
            <input
              className="input"
              value={label}
              onChange={(e) => setLabel(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter") generate(); }}
              autoFocus
            />
            <span className="text-xs muted">Only you see this — describe where the key is used.</span>
          </div>

          <div className="text-xs muted" style={{ display: "flex", gap: 8, alignItems: "flex-start", lineHeight: 1.55 }}>
            <Icon.Shield style={{ width: 14, height: 14, color: "var(--cyan)", flex: "0 0 14px", marginTop: 1 }} />
            <span>The key grants access to all five options endpoints. The full key is shown only once — copy it immediately.</span>
          </div>

          <ModalError message={error} />

          <div className="row gap-10" style={{ justifyContent: "flex-end", marginTop: 4 }}>
            <button className="btn" onClick={onClose}>Cancel</button>
            <button className="btn btn-primary" onClick={generate} disabled={loading}>
              <Icon.Bolt style={{ width: 13, height: 13 }} /> {loading ? "Generating…" : "Generate key"}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function KeyRevealModal({ apiKey, name, onClose }) {
  const [copied, setCopied] = useState(false);
  const [copyError, setCopyError] = useState("");

  // This modal is the ONLY place the user ever sees the plaintext key, so a
  // silent copy failure is a real footgun. Use the shared copyToClipboard
  // helper (Clipboard API + textarea fallback) and surface a user-visible
  // message if both paths fail — at minimum they know to select manually.
  const copy = () => {
    setCopyError("");
    copyToClipboard(apiKey)
      .then(() => {
        setCopied(true);
        setTimeout(() => setCopied(false), 1500);
      })
      .catch(() => {
        setCopyError("Could not copy automatically — select the key below and copy manually before closing this dialog.");
      });
  };

  const downloadEnv = () => {
    const blob = new Blob(["MARKET_OPTIONS_API_KEY=" + apiKey + "\n"], { type: "text/plain" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "market-options.env";
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  };

  return (
    <div style={MODAL_OVERLAY} onClick={onClose}>
      <div className="card" style={{ width: 540, boxShadow: "var(--shadow-lg)" }} onClick={(e) => e.stopPropagation()}>
        <div className="card-head">
          <span>Save this key now</span>
          <button className="icon-btn" style={{ marginLeft: "auto" }} onClick={onClose}>
            <Icon.X style={{ width: 13, height: 13 }} />
          </button>
        </div>
        <div className="card-body" style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <div style={{ padding: 14, border: "1px solid oklch(0.85 0.14 85 / 0.4)", background: "var(--amber-soft)", borderRadius: 8, fontSize: 13, color: "var(--text-dim)", lineHeight: 1.55 }}>
            <strong style={{ color: "var(--amber)" }}>Copy this key now.</strong><br />
            For your security it is shown in full only once — store it in your secrets manager.
          </div>
          <div style={{ background: "oklch(0.13 0.011 240)", border: "1px solid var(--border-soft)", padding: 14, borderRadius: 8 }}>
            <div className="mono text-xs muted" style={{ marginBottom: 6 }}>{name}</div>
            <code style={{ fontFamily: "var(--ff-mono)", fontSize: 13, color: "var(--green)", wordBreak: "break-all" }}>{apiKey}</code>
          </div>
          <div className="row gap-10">
            <button className="btn" style={{ flex: 1, justifyContent: "center" }} onClick={copy}>
              {copied
                ? <><Icon.Check style={{ width: 13, height: 13, color: "var(--green)" }} /> Copied</>
                : <><Icon.Copy style={{ width: 13, height: 13 }} /> Copy key</>}
            </button>
            <button className="btn" style={{ flex: 1, justifyContent: "center" }} onClick={downloadEnv}>
              <Icon.Docs style={{ width: 13, height: 13 }} /> Download .env
            </button>
          </div>
          {copyError && (
            <div style={{
              padding: "9px 12px", borderRadius: 7, fontSize: 12.5, lineHeight: 1.45,
              background: "rgba(229,72,77,0.12)", border: "1px solid var(--red)",
              color: "var(--red)",
            }}>{copyError}</div>
          )}
          <button className="btn btn-primary" style={{ justifyContent: "center" }} onClick={onClose}>
            <Icon.Check style={{ width: 13, height: 13 }} /> I've saved it
          </button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { KeysPage });
