/* global React, window, I, S, M */
// RunSecurity - floating button (bottom-right, sits ABOVE the green
// Push to Production button). Visible only on beta, only to admins.
// On click, runs the server-side admin_security_scan() RPC and shows
// findings grouped by severity. Performs a few client-side static
// checks too (CSP headers, config exposure).

function RunSecurity() {
  const cfg = window.PAMP_CONFIG;
  const toast = window.useToast();
  const [open, setOpen] = React.useState(false);
  const [scanning, setScanning] = React.useState(false);
  const [findings, setFindings] = React.useState(null);
  const [meta, setMeta] = React.useState(null);

  if (!cfg?.IS_BETA) return null;

  // Run the full scan: combine DB-side findings (via SECURITY DEFINER RPC)
  // with a few client-side static checks.
  const runScan = async () => {
    setOpen(true);
    setScanning(true);
    setFindings(null);
    setMeta(null);

    const clientFindings = staticClientChecks(cfg);

    try {
      const { data, error } = await window.PampSupabase.client.rpc(
        "admin_security_scan"
      );
      if (error) throw new Error(error.message);

      const dbFindings = (data && data.findings) || [];
      const all = sortBySeverity([...clientFindings, ...dbFindings]);
      setFindings(all);
      setMeta({
        scanned_at: data?.scanned_at,
        postgres_version: data?.postgres_version,
        db_count: dbFindings.length,
        client_count: clientFindings.length,
      });
    } catch (err) {
      toast.error(err.message || "Security scan failed");
      // Still show client-side findings even if DB scan failed
      setFindings(sortBySeverity(clientFindings));
      setMeta({ db_error: err.message, client_count: clientFindings.length });
    } finally {
      setScanning(false);
    }
  };

  return (
    <>
      <button
        onClick={runScan}
        title="Run a deep security scan of the database, config, and client surface"
        style={{
          position: "fixed",
          bottom: 76, // sits above the Push to Production button
          right: 20,
          zIndex: 120,
          display: "inline-flex",
          alignItems: "center",
          gap: 8,
          padding: "11px 18px",
          borderRadius: 999,
          background: "oklch(64% 0.16 32)", // distinctive orange/red
          color: "white",
          border: "1px solid oklch(54% 0.18 30)",
          fontWeight: 500,
          fontSize: 13,
          cursor: "pointer",
          boxShadow: "0 8px 24px oklch(64% 0.16 32 / 0.25), 0 2px 4px oklch(0% 0 0 / 0.12)",
        }}
      >
        <I.lock size={14} />
        Run Security
      </button>

      <window.M.Modal
        open={open}
        onClose={() => !scanning && setOpen(false)}
        size="lg"
        icon={<I.lock />}
        title={scanning ? "Running security scan…" : "Security scan results"}
        subtitle={
          scanning
            ? "Auditing database, policies, config, and client surface."
            : findings && findings.length === 0
            ? "Nothing critical found. Good signal — keep auditing manually too."
            : findings
            ? "Sorted by severity. Higher severity = address first."
            : ""
        }
        footer={
          <>
            <S.Btn variant="ghost" onClick={() => setOpen(false)} disabled={scanning}>
              Close
            </S.Btn>
            {!scanning && (
              <S.Btn variant="primary" icon={<I.refresh size={14} />} onClick={runScan}>
                Re-scan
              </S.Btn>
            )}
          </>
        }
      >
        {scanning ? (
          <div className="loading-state">
            <div className="spin" />
            <div>Scanning database, RLS policies, and client config…</div>
          </div>
        ) : findings === null ? null : findings.length === 0 ? (
          <div style={{ padding: "32px 0", textAlign: "center" }}>
            <div style={{
              width: 48, height: 48, margin: "0 auto 12px", borderRadius: "50%",
              background: "color-mix(in oklch, var(--pos) 18%, transparent)",
              color: "var(--pos)", display: "flex", alignItems: "center", justifyContent: "center",
            }}>
              <I.check size={22} />
            </div>
            <div className="h3" style={{ marginBottom: 6 }}>No findings</div>
            <div className="muted tiny">
              The automated audit didn't surface anything. Continue with manual
              reviews of payment flows and auth boundaries.
            </div>
          </div>
        ) : (
          <>
            <div className="row" style={{ gap: 8, marginBottom: 14, flexWrap: "wrap" }}>
              {["critical", "high", "medium", "low"].map((sev) => {
                const count = findings.filter((f) => f.severity === sev).length;
                if (count === 0) return null;
                return (
                  <SeverityChip key={sev} severity={sev}>
                    {count} {sev}
                  </SeverityChip>
                );
              })}
            </div>

            <div className="col" style={{ gap: 10 }}>
              {findings.map((f, idx) => (
                <FindingCard key={idx} finding={f} />
              ))}
            </div>

            {meta && (
              <div className="tiny muted" style={{ marginTop: 14, paddingTop: 12, borderTop: "1px solid var(--border)" }}>
                {meta.scanned_at && <>Scanned {new Date(meta.scanned_at).toLocaleString()}. </>}
                {meta.postgres_version && <>Postgres {meta.postgres_version.split(" ")[0]}. </>}
                {typeof meta.db_count === "number" && <>{meta.db_count} DB checks, </>}
                {typeof meta.client_count === "number" && <>{meta.client_count} client checks.</>}
                {meta.db_error && <span style={{ color: "var(--neg)" }}> Database scan failed: {meta.db_error}</span>}
              </div>
            )}
          </>
        )}
      </window.M.Modal>
    </>
  );
}

// ============================================================
// Client-side static checks (in addition to the DB-side scan)
// ============================================================
function staticClientChecks(cfg) {
  const out = [];

  // CRITICAL: service-role key would NEVER appear here, but check anyway
  // in case someone accidentally puts it in PAMP_SUPABASE_ANON_KEY
  if (cfg && cfg.SUPABASE_ANON_KEY) {
    try {
      const payload = JSON.parse(atob(cfg.SUPABASE_ANON_KEY.split(".")[1] || ""));
      if (payload.role && payload.role !== "anon") {
        out.push({
          severity: "critical",
          category: "Secrets in client",
          title: "SUPABASE_ANON_KEY is not actually the anon role",
          detail:
            "The JWT in window.PAMP_CONFIG.SUPABASE_ANON_KEY claims role = '" +
            payload.role +
            "'. If this is a service_role token, anyone visiting the site can " +
            "read/modify every row in the database. Rotate immediately.",
          fix:
            "Replace PAMP_SUPABASE_ANON_KEY env var on Vercel with the anon " +
            "public key from Supabase > Settings > API. Never put service_role " +
            "in a client-facing config.",
        });
      }
    } catch (e) {
      // unparseable JWT, skip
    }
  }

  // MEDIUM: production fallback hardcoded in supabase.js
  // (We deliberately leave a fallback so the app works if /api/config fails,
  // but on beta we should be sure config.IS_BETA is true.)
  if (!cfg) {
    out.push({
      severity: "high",
      category: "Configuration",
      title: "window.PAMP_CONFIG is not set",
      detail:
        "The runtime config endpoint /api/config didn't load or didn't return " +
        "a valid config. The app is using hardcoded production fallbacks - " +
        "meaning beta and production may be talking to the same Supabase.",
      fix:
        "Verify /api/config returns valid JS that sets window.PAMP_CONFIG. " +
        "Check that index.html has <script src=\"/api/config\"></script> " +
        "before src/supabase.js.",
    });
  }

  // LOW: CSP header presence check (best-effort via fetch)
  // We can't read the response headers of the current page from JS in most
  // browsers, but we can fetch a sibling URL and check.
  // (Skipped because async; would complicate the modal. Server-side CSP
  // is enforced via vercel.json so it's set if vercel.json is correct.)

  return out;
}

function sortBySeverity(findings) {
  const order = { critical: 0, high: 1, medium: 2, low: 3 };
  return [...findings].sort(
    (a, b) => (order[a.severity] ?? 9) - (order[b.severity] ?? 9)
  );
}

// ============================================================
// Small presentational components
// ============================================================
function SeverityChip({ severity, children }) {
  const colors = {
    critical: { bg: "var(--neg)", fg: "white" },
    high:     { bg: "color-mix(in oklch, var(--neg) 75%, transparent)", fg: "white" },
    medium:   { bg: "color-mix(in oklch, var(--warn) 22%, transparent)", fg: "var(--warn)" },
    low:      { bg: "color-mix(in oklch, var(--ink-3) 18%, transparent)", fg: "var(--ink-2)" },
  };
  const c = colors[severity] || colors.low;
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      padding: "4px 10px", borderRadius: 999, fontSize: 11,
      fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.06em",
      background: c.bg, color: c.fg, whiteSpace: "nowrap",
    }}>
      {children}
    </span>
  );
}

function FindingCard({ finding }) {
  const [showFix, setShowFix] = React.useState(false);
  return (
    <div className="card" style={{ padding: 14 }}>
      <div className="row between" style={{ marginBottom: 8, gap: 10, alignItems: "flex-start" }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="row" style={{ gap: 8, marginBottom: 4, flexWrap: "wrap" }}>
            <SeverityChip severity={finding.severity}>{finding.severity}</SeverityChip>
            {finding.category && (
              <span className="tiny muted" style={{ alignSelf: "center" }}>
                {finding.category}
              </span>
            )}
          </div>
          <div style={{ fontWeight: 500, fontSize: 14, marginBottom: 4 }}>
            {finding.title}
          </div>
          <div className="muted" style={{ fontSize: 13, lineHeight: 1.5 }}>
            {finding.detail}
          </div>
        </div>
      </div>
      {finding.fix && (
        <>
          <button
            onClick={() => setShowFix((s) => !s)}
            style={{
              fontSize: 12, color: "var(--accent-ink)", padding: "4px 0",
              background: "none", border: "none", cursor: "pointer",
              textAlign: "left", fontWeight: 500,
            }}
          >
            {showFix ? "Hide fix ↑" : "Show fix ↓"}
          </button>
          {showFix && (
            <pre style={{
              marginTop: 6, padding: 10, borderRadius: 6,
              background: "var(--surface-2)", fontSize: 12,
              fontFamily: "var(--font-mono)", color: "var(--ink-2)",
              whiteSpace: "pre-wrap", wordBreak: "break-word",
              maxWidth: "100%", overflowX: "auto",
            }}>
              {finding.fix}
            </pre>
          )}
        </>
      )}
    </div>
  );
}

window.RunSecurity = RunSecurity;
