// Shared two-factor-authentication UI components, used by:
//   - reseller-login-page.jsx  (mandatory challenge / forced enrollment at login)
//   - admin-portal-page.jsx    (opt-in challenge at login)
//   - reseller-settings-page.jsx (self-service reset, no disable)
//   - admin-settings-page.jsx    (self-service enable/disable)
//
// Loaded once, early (see index.html) since every one of those four
// files renders one of these components. Deliberately theme-agnostic:
// every component takes a `palette` prop (bg/fg/border/accent/error
// colors) instead of hardcoding either the reseller portal's --pt-*
// CSS variables or the admin area's fixed black-on-white inline
// styles, since those two areas don't share a theme system (see
// portal-theme.jsx's header comment on why AdminShell is out of scope
// for that system). LIGHT_PALETTE below matches the admin area's own
// look; the reseller pages pass a palette built from --pt-* var
// references instead (still real CSS values in that DOM subtree).
//
// otpauth:// QR rendering uses QRCode.js (davidshimjs/qrcodejs, loaded
// in index.html) directly against a ref'd DOM node -- same "talk
// straight to the global browser API, no bundler" pattern this
// codebase already uses for Leaflet (see unit-management-page.jsx's
// UM_LocationMap).

const LIGHT_PALETTE = {
  bg: "#fff", fg: "#000",
  fgDim: "rgba(0,0,0,0.6)", fgFaint: "rgba(0,0,0,0.5)",
  border: "rgba(0,0,0,0.16)", borderStrong: "rgba(0,0,0,0.25)",
  surface: "rgba(0,0,0,0.03)",
  accentBg: "#000", accentFg: "#fff",
  accentBgDisabled: "rgba(0,0,0,0.15)", accentFgDisabled: "rgba(0,0,0,0.5)",
  errorBg: "rgba(190,40,40,0.08)", errorBorder: "rgba(190,40,40,0.35)", errorText: "#8a1f1f",
  successText: "rgba(20,140,60,0.95)",
};

// Renders an otpauth:// URI as a scannable QR code via QRCode.js
// (global `window.QRCode`, loaded in index.html). Re-renders cleanly if
// the URI ever changes (e.g. "start over" on enrollment) by clearing
// the container first -- QRCode.js has no update-in-place API.
function QRCodeBox({ otpauthUri, size = 176 }) {
  const ref = useRef(null);

  useEffect(() => {
    if (!ref.current || !otpauthUri) return;
    ref.current.innerHTML = "";
    if (!window.QRCode) return; // CDN failed to load -- secret text fallback below still works
    // eslint-disable-next-line no-new
    new window.QRCode(ref.current, {
      text: otpauthUri,
      width: size,
      height: size,
      correctLevel: window.QRCode.CorrectLevel.M,
    });
  }, [otpauthUri, size]);

  return (
    <div
      ref={ref}
      style={{
        width: size, height: size,
        display: "flex", alignItems: "center", justifyContent: "center",
        background: "#fff", padding: 8, margin: "0 auto",
      }}
    />
  );
}

// Auto-uppercases + strips invalid characters as the user types, so a
// 6-digit authenticator code or an XXXX-XXXX recovery code both just
// work in the same field without the caller needing two code paths.
function TwoFactorCodeField({ value, onChange, palette, autoFocus }) {
  return (
    <input
      type="text"
      inputMode="text"
      autoComplete="one-time-code"
      autoFocus={autoFocus}
      autoCapitalize="characters"
      spellCheck={false}
      placeholder="123456 or XXXX-XXXX"
      value={value}
      onChange={(e) => onChange(e.target.value.toUpperCase().replace(/[^0-9A-Z-]/g, "").slice(0, 12))}
      style={{
        width: "100%", boxSizing: "border-box",
        background: palette.surface,
        border: `1px solid ${palette.borderStrong}`,
        color: palette.fg,
        fontFamily: "var(--font-body)",
        fontSize: 20, letterSpacing: "0.12em", textAlign: "center",
        padding: "16px 16px", outline: "none",
      }}
    />
  );
}

function TwoFactorError({ message, palette }) {
  if (!message) return null;
  return (
    <div style={{
      background: palette.errorBg, border: `1px solid ${palette.errorBorder}`,
      color: palette.errorText, padding: "14px 16px", marginBottom: 22,
      fontFamily: "var(--font-body)", fontSize: 13.5, lineHeight: 1.5,
    }}>
      {message}
    </div>
  );
}

function TwoFactorSubmitButton({ children, disabled, palette }) {
  return (
    <button
      type="submit" disabled={disabled}
      style={{
        width: "100%", marginTop: 4,
        background: disabled ? palette.accentBgDisabled : palette.accentBg,
        color: disabled ? palette.accentFgDisabled : palette.accentFg,
        border: `1px solid ${disabled ? palette.accentBgDisabled : palette.accentBg}`,
        padding: "16px 22px",
        cursor: disabled ? "not-allowed" : "pointer",
        fontFamily: "var(--font-body)", fontSize: 13, fontWeight: 500,
        letterSpacing: "0.22em", textTransform: "uppercase",
        transition: "background 140ms, color 140ms",
      }}>
      {children}
    </button>
  );
}

/**
 * Login-time (or already-signed-in) 2FA CHALLENGE step: submits
 * pendingToken + a code to `verifyUrl`. Accepts either a 6-digit
 * authenticator code or an XXXX-XXXX recovery code in the same field
 * (see routes/*.ts's resolveTwoFactorChallenge, which distinguishes by
 * the hyphen). `onSuccess(data)` is called with the full parsed
 * response body on success -- callers that need the session payload
 * (user/admin + company) read it from there.
 */
function TwoFactorChallengeCard({ verifyUrl, pendingToken, onSuccess, onBack, palette = LIGHT_PALETTE, title = "Enter your verification code." }) {
  const [code, setCode] = useState("");
  const [status, setStatus] = useState("idle"); // idle → submitting
  const [error, setError] = useState("");

  const canSubmit = code.trim().length >= 6 && status !== "submitting";

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!canSubmit) return;
    setStatus("submitting");
    setError("");
    try {
      const res = await fetch(verifyUrl, {
        method: "POST",
        credentials: "same-origin",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ pendingToken, code: code.trim() }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        setError(data.error || "Something went wrong. Please try again.");
        setStatus("idle");
        return;
      }
      onSuccess(data);
    } catch {
      setError("Couldn't reach the server. Check your connection and try again.");
      setStatus("idle");
    }
  };

  return (
    <form onSubmit={handleSubmit} style={{
      background: palette.surface, border: `1px solid ${palette.border}`,
      backdropFilter: "blur(6px)", padding: 36,
    }}>
      <h2 style={{
        fontFamily: "var(--font-display)", fontWeight: 700,
        fontSize: 24, textTransform: "uppercase", letterSpacing: "-0.005em",
        margin: "0 0 10px", color: palette.fg,
      }}>{title}</h2>
      <p style={{
        fontFamily: "var(--font-body)", fontSize: 14, lineHeight: 1.5,
        color: palette.fgDim, margin: "0 0 24px",
      }}>
        Open your authenticator app and enter the 6-digit code, or use one of your backup recovery codes.
      </p>

      <TwoFactorError message={error} palette={palette} />

      <div style={{ marginBottom: 22 }}>
        <TwoFactorCodeField value={code} onChange={setCode} palette={palette} autoFocus />
      </div>

      <TwoFactorSubmitButton disabled={!canSubmit} palette={palette}>
        {status === "submitting" ? "Verifying…" : "Verify →"}
      </TwoFactorSubmitButton>

      {onBack && (
        <div style={{ textAlign: "center", marginTop: 20 }}>
          <button
            type="button" onClick={onBack}
            style={{
              background: "none", border: "none", padding: 0, cursor: "pointer",
              fontFamily: "var(--font-body)", fontSize: 12,
              letterSpacing: "0.14em", textTransform: "uppercase",
              color: palette.fgFaint, textDecoration: "underline",
            }}>← Back to sign in</button>
        </div>
      )}
    </form>
  );
}

/**
 * Enrollment step: shows the QR code for `otpauthUri` (plus the raw
 * secret as a fallback for manual entry) and asks for the first
 * 6-digit code to confirm it was scanned correctly. Submits
 * pendingToken + code to `confirmUrl`. On success calls
 * `onSuccess(recoveryCodes)` -- every caller MUST show those codes next
 * (this is the only time they're ever visible in plaintext), never
 * skip straight past them.
 */
function TwoFactorEnrollCard({ confirmUrl, pendingToken, otpauthUri, accountLabel, intro, onSuccess, palette = LIGHT_PALETTE }) {
  const [code, setCode] = useState("");
  const [status, setStatus] = useState("idle");
  const [error, setError] = useState("");

  // The otpauth:// URI is otpauth://totp/Issuer:label?secret=XXXX&... --
  // pull just the secret back out for the "can't scan? enter manually"
  // fallback, rather than asking the backend for it a second time.
  const manualSecret = (() => {
    try {
      const match = /[?&]secret=([^&]+)/.exec(otpauthUri || "");
      return match ? decodeURIComponent(match[1]) : "";
    } catch { return ""; }
  })();

  const canSubmit = code.trim().length === 6 && status !== "submitting";

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!canSubmit) return;
    setStatus("submitting");
    setError("");
    try {
      const res = await fetch(confirmUrl, {
        method: "POST",
        credentials: "same-origin",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ pendingToken, code: code.trim() }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        setError(data.error || "Something went wrong. Please try again.");
        setStatus("idle");
        return;
      }
      onSuccess(data.recoveryCodes || []);
    } catch {
      setError("Couldn't reach the server. Check your connection and try again.");
      setStatus("idle");
    }
  };

  return (
    <form onSubmit={handleSubmit} style={{
      background: palette.surface, border: `1px solid ${palette.border}`,
      backdropFilter: "blur(6px)", padding: 36,
    }}>
      <h2 style={{
        fontFamily: "var(--font-display)", fontWeight: 700,
        fontSize: 24, textTransform: "uppercase", letterSpacing: "-0.005em",
        margin: "0 0 10px", color: palette.fg,
      }}>Set up two-factor authentication.</h2>
      <p style={{
        fontFamily: "var(--font-body)", fontSize: 14, lineHeight: 1.5,
        color: palette.fgDim, margin: "0 0 24px",
      }}>{intro}</p>

      <TwoFactorError message={error} palette={palette} />

      <QRCodeBox otpauthUri={otpauthUri} />

      {manualSecret && (
        <div style={{
          margin: "18px 0 24px", textAlign: "center",
          fontFamily: "var(--font-body)", fontSize: 12, color: palette.fgFaint,
        }}>
          Can't scan? Enter this code manually{accountLabel ? ` for ${accountLabel}` : ""}:
          <div style={{
            marginTop: 8, fontFamily: "monospace", fontSize: 13,
            letterSpacing: "0.08em", color: palette.fgDim, wordBreak: "break-all",
          }}>{manualSecret}</div>
        </div>
      )}

      <div style={{ marginBottom: 22 }}>
        <label style={{
          display: "block", marginBottom: 10,
          fontFamily: "var(--font-body)", fontSize: 10,
          letterSpacing: "0.28em", textTransform: "uppercase", color: palette.fgDim,
        }}>6-digit code</label>
        <input
          type="text" inputMode="numeric" autoComplete="one-time-code"
          maxLength={6} placeholder="123456"
          value={code}
          onChange={(e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
          style={{
            width: "100%", boxSizing: "border-box",
            background: palette.surface, border: `1px solid ${palette.borderStrong}`,
            color: palette.fg, fontFamily: "var(--font-body)",
            fontSize: 20, letterSpacing: "0.3em", textAlign: "center",
            padding: "16px 16px", outline: "none",
          }}
        />
      </div>

      <TwoFactorSubmitButton disabled={!canSubmit} palette={palette}>
        {status === "submitting" ? "Confirming…" : "Confirm & continue →"}
      </TwoFactorSubmitButton>
    </form>
  );
}

/**
 * Shows the 8 one-time recovery codes exactly once, right after a
 * successful enrollment. `onContinue` is only enabled once the user has
 * explicitly acknowledged they've saved them -- there's no way to see
 * these again afterwards (see lib/twoFactor.ts's generateRecoveryCodes
 * comment), so this is deliberately a hard stop, not a toast that
 * scrolls past.
 */
function RecoveryCodesCard({ codes, onContinue, palette = LIGHT_PALETTE }) {
  const [acknowledged, setAcknowledged] = useState(false);

  return (
    <div style={{
      background: palette.surface, border: `1px solid ${palette.border}`,
      backdropFilter: "blur(6px)", padding: 36,
    }}>
      <h2 style={{
        fontFamily: "var(--font-display)", fontWeight: 700,
        fontSize: 24, textTransform: "uppercase", letterSpacing: "-0.005em",
        margin: "0 0 10px", color: palette.fg,
      }}>Save your recovery codes.</h2>
      <p style={{
        fontFamily: "var(--font-body)", fontSize: 14, lineHeight: 1.5,
        color: palette.fgDim, margin: "0 0 20px",
      }}>
        If you lose access to your authenticator app, you can use one of these one-time codes to sign in instead.
        <strong style={{ color: palette.fg }}> They will not be shown again.</strong> Store them somewhere safe (a password manager is ideal).
      </p>

      <div style={{
        display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px 20px",
        background: "#fff", border: `1px solid ${palette.border}`,
        padding: 20, marginBottom: 24,
      }}>
        {(codes || []).map((c) => (
          <div key={c} style={{
            fontFamily: "monospace", fontSize: 15, letterSpacing: "0.06em",
            color: "#000", textAlign: "center",
          }}>{c}</div>
        ))}
      </div>

      <label style={{
        display: "flex", alignItems: "flex-start", gap: 10, marginBottom: 22,
        fontFamily: "var(--font-body)", fontSize: 13, color: palette.fgDim, cursor: "pointer",
      }}>
        <input
          type="checkbox" checked={acknowledged}
          onChange={(e) => setAcknowledged(e.target.checked)}
          style={{ marginTop: 3 }}
        />
        <span>I've saved these recovery codes somewhere safe.</span>
      </label>

      <TwoFactorSubmitButton disabled={!acknowledged} palette={palette}>
        Continue →
      </TwoFactorSubmitButton>
    </div>
  );
}

Object.assign(window, {
  TWO_FACTOR_LIGHT_PALETTE: LIGHT_PALETTE,
  QRCodeBox, TwoFactorCodeField, TwoFactorChallengeCard, TwoFactorEnrollCard, RecoveryCodesCard,
});
