// Client-side auth wrapper around /api/auth/* endpoints.
// Falls back to local-only accounts if the API is unreachable (older deployments).
//
// Globals expected: React, ACCOUNTS_KEY, CURRENT_USER_KEY.

const { useState: auUseState, useEffect: auUseEffect, useCallback: auUseCallback, useMemo: auUseMemo } = React;

const TOKEN_KEY = "vp.token.v1";        // single active token (one logged-in user at a time)
const STATS_KEY_BASE = "vp.stats.v1";

// --- username normalization (mirrors server-side, but tolerant) -------------
function normUsername(raw) {
  let u = (raw || "").trim().toLowerCase();
  if (!u) return "";
  if (!u.startsWith("@")) u = "@" + u;
  u = "@" + u.slice(1).replace(/[^a-z0-9_.]/g, "");
  return u;
}
function isValidUsername(u) {
  return /^@[a-z0-9_.]{2,24}$/.test(u);
}

// --- local persistence ------------------------------------------------------
function loadCurrentUser() {
  try { return localStorage.getItem(CURRENT_USER_KEY); } catch (e) { return null; }
}
function saveCurrentUser(u) {
  try {
    if (u) localStorage.setItem(CURRENT_USER_KEY, u);
    else localStorage.removeItem(CURRENT_USER_KEY);
  } catch (e) {}
}
function loadToken() {
  try { return localStorage.getItem(TOKEN_KEY) || null; } catch (e) { return null; }
}
function saveToken(t) {
  try {
    if (t) localStorage.setItem(TOKEN_KEY, t);
    else localStorage.removeItem(TOKEN_KEY);
  } catch (e) {}
}

// --- local-stats helpers (used for the local → cloud migration prompt) ------
function readStatsAt(key) {
  try {
    const raw = localStorage.getItem(key);
    if (!raw) return null;
    return JSON.parse(raw);
  } catch (e) { return null; }
}
function writeUserStats(username, stats) {
  try {
    localStorage.setItem(`${STATS_KEY_BASE}__${username}`, JSON.stringify(stats));
  } catch (e) {}
}
// Drop the pre-backend client-only account record (vp.accounts.v1[username])
// once its data has been migrated up.
function purgeLegacyLocalAccount(username) {
  try {
    const raw = localStorage.getItem(ACCOUNTS_KEY);
    if (!raw) return;
    const accs = JSON.parse(raw);
    if (accs[username]) {
      delete accs[username];
      if (Object.keys(accs).length === 0) localStorage.removeItem(ACCOUNTS_KEY);
      else localStorage.setItem(ACCOUNTS_KEY, JSON.stringify(accs));
    }
  } catch (e) {}
}

// --- HTTP wrappers ----------------------------------------------------------
async function api(path, opts = {}) {
  const headers = { "content-type": "application/json", ...(opts.headers || {}) };
  const token = loadToken();
  if (token && !headers.Authorization) headers.Authorization = `Bearer ${token}`;
  const res = await fetch(path, { ...opts, headers });
  // Try JSON first; if the body is HTML or empty, surface a snippet of text
  // in `bodyText` so the UI can show *something* useful instead of a generic
  // "Sign-up failed".
  let body = null, text = "";
  try {
    text = await res.text();
    if (text) body = JSON.parse(text);
  } catch (e) {}
  return { status: res.status, body: body || {}, bodyText: text || "" };
}

// --- hook --------------------------------------------------------------------
function useAuth() {
  // A "logged in" state requires BOTH a currentUser AND a token (unless they
  // chose guest). Without a token, treat it as not-logged-in so the auth
  // screen comes back — this fixes the case where a previous signup attempt
  // failed silently but left currentUser set in localStorage.
  const initialUser = (() => {
    const u = loadCurrentUser();
    if (!u || u === "guest") return u;
    return loadToken() ? u : null;
  })();
  const [currentUser, setCurrentUser] = auUseState(initialUser);
  const [decided, setDecided] = auUseState(initialUser !== null);
  // bumped whenever the server hands us a fresh stats payload — useStats
  // listens to this and forces a reload from localStorage.
  const [syncTick, setSyncTick] = auUseState(0);

  auUseEffect(() => { saveCurrentUser(currentUser); }, [currentUser]);

  const userKey = auUseMemo(() => {
    if (!currentUser || currentUser === "guest") return "guest";
    return currentUser;
  }, [currentUser]);

  // After successful signup, look for local stats worth migrating into the
  // new cloud account. Two sources, in priority order:
  //   1. Same-username stats from the pre-backend client-only auth
  //      (e.g. vp.stats.v1__@sergey) — always migrate if any answers exist
  //   2. Guest stats (vp.stats.v1__guest) — only if > 5 answers
  // The user is asked once per signup; declining keeps the local data in place.
  const maybeMigrateLocalData = (username) => {
    const sameKey = `${STATS_KEY_BASE}__${username}`;
    const sameUser = readStatsAt(sameKey);
    const guest = readStatsAt(`${STATS_KEY_BASE}__guest`);
    const sameAns = sameUser?.totalAnswered || 0;
    const guestAns = guest?.totalAnswered || 0;

    let pick = null, sourceKey = "", label = "";
    if (sameAns > 0 && sameAns >= guestAns) {
      pick = sameUser; sourceKey = sameKey; label = `existing ${username}`;
    } else if (guestAns > 5) {
      pick = guest; sourceKey = `${STATS_KEY_BASE}__guest`; label = "guest";
    }
    if (!pick) {
      // Still purge a stale legacy account record for this username if any
      purgeLegacyLocalAccount(username);
      return false;
    }

    const correct = pick.totalCorrect || 0;
    const ok = window.confirm(
      `Found ${pick.totalAnswered} ${label} answer${pick.totalAnswered === 1 ? "" : "s"} ` +
      `(${correct} correct) on this device. Move them into ${username}'s cloud account?`
    );
    if (!ok) {
      purgeLegacyLocalAccount(username);
      return false;
    }
    writeUserStats(username, pick);
    // Only clear the source if it differs from the destination (otherwise
    // we'd just delete what we wrote — happened in the v1 migration code).
    const destKey = `${STATS_KEY_BASE}__${username}`;
    if (sourceKey !== destKey) {
      try { localStorage.removeItem(sourceKey); } catch (e) {}
    }
    purgeLegacyLocalAccount(username);
    return true;
  };

  const signUp = auUseCallback(async (rawU, password, accountType) => {
    const u = normUsername(rawU);
    if (!isValidUsername(u)) return { ok: false, err: "Username must be 2–24 letters/digits/_/. after @." };
    if (!password || password.length < 4) return { ok: false, err: "Password must be at least 4 characters." };

    const r = await api("/api/auth/signup", {
      method: "POST",
      body: JSON.stringify({ username: u, password, accountType: accountType === "teacher" ? "teacher" : "student" }),
    }).catch(e => ({ status: 0, body: { err: `Network error: ${e?.message || "unknown"}` }, bodyText: "" }));

    if (r.status === 0 || r.status >= 500) {
      return { ok: false, err: r.body.err || `Server error ${r.status}${r.bodyText ? ` — ${r.bodyText.slice(0, 200)}` : ""}` };
    }
    if (!r.body.ok) {
      return { ok: false, err: r.body.err || `Sign-up failed (status ${r.status})${r.bodyText ? `: ${r.bodyText.slice(0, 200)}` : ""}` };
    }

    saveToken(r.body.token);

    // If this device has matching local stats (from the pre-backend client-only
    // auth, or from guest mode), offer to migrate them BEFORE we flip
    // currentUser — that way useStats loads the migrated data on first read.
    const migrated = maybeMigrateLocalData(u);

    setCurrentUser(u);
    setDecided(true);

    // If we migrated, push the new account's stats up so the server matches.
    if (migrated) setSyncTick(t => t + 1);

    return { ok: true, migrated };
  }, []);

  const signIn = auUseCallback(async (rawU, password) => {
    const u = normUsername(rawU);
    if (!isValidUsername(u)) return { ok: false, err: "Invalid username." };
    if (!password) return { ok: false, err: "Password is required." };

    const r = await api("/api/auth/signin", {
      method: "POST",
      body: JSON.stringify({ username: u, password }),
    }).catch(e => ({ status: 0, body: { err: `Network error: ${e?.message || "unknown"}` }, bodyText: "" }));

    if (r.status === 0 || r.status >= 500) {
      return { ok: false, err: r.body.err || `Server error ${r.status}${r.bodyText ? ` — ${r.bodyText.slice(0, 200)}` : ""}` };
    }
    if (!r.body.ok) {
      return { ok: false, err: r.body.err || `Sign-in failed (status ${r.status})${r.bodyText ? `: ${r.bodyText.slice(0, 200)}` : ""}` };
    }

    saveToken(r.body.token);

    // Overwrite local with server's authoritative copy for this account
    // (won't touch other users' or guest's stats).
    if (r.body.stats) writeUserStats(u, r.body.stats);

    setCurrentUser(u);
    setDecided(true);
    setSyncTick(t => t + 1);

    return { ok: true };
  }, []);

  const continueAsGuest = auUseCallback(() => {
    saveToken(null);
    setCurrentUser("guest");
    setDecided(true);
  }, []);

  const signOut = auUseCallback(() => {
    saveToken(null);
    setCurrentUser(null);
    setDecided(false);
  }, []);

  return {
    currentUser, userKey, decided, syncTick,
    signIn, signUp, continueAsGuest, signOut,
  };
}

// --- AuthScreen --------------------------------------------------------------
function AuthScreen({ onSignIn, onSignUp, onGuest, onExamType }) {
  const [mode, setMode] = auUseState("signin");
  const [username, setUsername] = auUseState("");
  const [password, setPassword] = auUseState("");
  const [err, setErr] = auUseState("");
  const [busy, setBusy] = auUseState(false);
  const [accountType, setAccountType] = auUseState("student");
  const [examType, setExamType] = auUseState("ege");

  const submit = async (e) => {
    e.preventDefault();
    setErr("");
    setBusy(true);
    const res = mode === "signin"
      ? await onSignIn(username, password)
      : await onSignUp(username, password, accountType);
    setBusy(false);
    if (!res.ok) setErr(res.err || "Something went wrong.");
    else if (mode === "signup" && onExamType) onExamType(examType);
  };

  return (
    <div className="auth-shell">
      <div className="auth-card">
        <div className="auth-brand">
          <div className="auth-wordmark">
            <span className="tn-brand-main">iri</span>
            <span className="tn-brand-accent">kos</span>
          </div>
        </div>

        <div className="auth-tabs" role="tablist">
          <button
            role="tab"
            className={`auth-tab ${mode === "signin" ? "is-on" : ""}`}
            aria-selected={mode === "signin"}
            onClick={() => { setMode("signin"); setErr(""); }}
          >Sign in</button>
          <button
            role="tab"
            className={`auth-tab ${mode === "signup" ? "is-on" : ""}`}
            aria-selected={mode === "signup"}
            onClick={() => { setMode("signup"); setErr(""); }}
          >Create account</button>
        </div>

        <form className="auth-form" onSubmit={submit}>
          <label className="auth-field">
            <span className="auth-field-label">Username</span>
            <input
              className="auth-input"
              value={username}
              onChange={e => setUsername(e.target.value)}
              placeholder="@yourname"
              autoComplete="username"
              autoCapitalize="off"
              spellCheck={false}
              aria-label="Username"
            />
          </label>

          <label className="auth-field">
            <span className="auth-field-label">Password</span>
            <input
              className="auth-input"
              type="password"
              value={password}
              onChange={e => setPassword(e.target.value)}
              placeholder="at least 4 characters"
              autoComplete={mode === "signin" ? "current-password" : "new-password"}
              aria-label="Password"
            />
          </label>

          {mode === "signup" && (
            <label className="auth-field">
              <span className="auth-field-label">I'm a…</span>
              <div className="segmented" role="radiogroup" aria-label="Account type">
                <button type="button" role="radio" aria-checked={accountType === "student"}
                  className={`segmented-btn ${accountType === "student" ? "is-on" : ""}`}
                  onClick={() => setAccountType("student")}>Student</button>
                <button type="button" role="radio" aria-checked={accountType === "teacher"}
                  className={`segmented-btn ${accountType === "teacher" ? "is-on" : ""}`}
                  onClick={() => setAccountType("teacher")}>Teacher</button>
              </div>
              <span style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 6 }}>
                Sets your starting view in Class — you can do both later.
              </span>
            </label>
          )}

          {mode === "signup" && (
            <label className="auth-field">
              <span className="auth-field-label">Which exam?</span>
              <div className="segmented" role="radiogroup" aria-label="Exam">
                <button type="button" role="radio" aria-checked={examType === "ege"}
                  className={`segmented-btn ${examType === "ege" ? "is-on" : ""}`}
                  onClick={() => setExamType("ege")}>ЕГЭ</button>
                <button type="button" role="radio" aria-checked={examType === "oge"}
                  className={`segmented-btn ${examType === "oge" ? "is-on" : ""}`}
                  onClick={() => setExamType("oge")}>ОГЭ</button>
              </div>
              <span style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 6 }}>
                Switch anytime in Settings — each exam keeps its own progress.
              </span>
            </label>
          )}

          {err && <div className="auth-err">{err}</div>}

          <button className="cta-primary auth-submit" type="submit" disabled={busy}>
            <span>{busy ? "…" : (mode === "signin" ? "Sign in" : "Create account")}</span>
            <span className="cta-arrow">→</span>
          </button>
        </form>

        <div className="auth-foot">
          <button className="text-link" onClick={onGuest}>Continue as guest</button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { useAuth, AuthScreen, normUsername, isValidUsername, TOKEN_KEY });
