// Authentication helper for the extension.
// Session lives in IndexedDB (settings store) — same storage layer as everything
// else, accessible from popup AND service worker, persists across restarts.

import { SYNC } from './config.js';
import { getSetting, setSetting } from './db.js';
import { obLog } from './logger.js';

const KEY_TOKEN = 'session.token';
const KEY_USERNAME = 'session.username';

// We persist the session in THREE independent stores and read from whichever
// still has it:
//   1. IndexedDB (settings)        — survives restarts, wiped on reinstall
//   2. chrome.storage.local        — survives restarts, wiped on reinstall
//   3. chrome.storage.SYNC         — survives REMOVE + RE-ADD (tied to the Google
//                                    account; the extension ID is stable via the
//                                    manifest `key`). This is what keeps the user
//                                    logged in across version upgrades done by
//                                    remove+reinstall (Load-Unpacked path), so
//                                    they never re-type username/password. The
//                                    Gemini key + settings then re-sync from the
//                                    server (D1) automatically on the next /me.
// Storing a long-lived session token in storage.sync is a deliberate "remember
// me" tradeoff — it is only readable by THIS extension under the user's own
// Google profile. Logout clears all three.
async function mirrorWrite(token, username) {
  try { await chrome.storage?.local?.set({ [KEY_TOKEN]: token, [KEY_USERNAME]: username }); } catch {}
  try {
    if (token) await chrome.storage?.sync?.set({ [KEY_TOKEN]: token, [KEY_USERNAME]: username });
    else       await chrome.storage?.sync?.remove([KEY_TOKEN, KEY_USERNAME]);
  } catch {}
}
async function mirrorReadLocal() {
  try { return (await chrome.storage?.local?.get([KEY_TOKEN, KEY_USERNAME])) || {}; } catch { return {}; }
}
async function mirrorReadSync() {
  try { return (await chrome.storage?.sync?.get([KEY_TOKEN, KEY_USERNAME])) || {}; } catch { return {}; }
}

export async function getSession() {
  let token = await getSetting(KEY_TOKEN);
  let username = await getSetting(KEY_USERNAME);
  if (!token) {
    // IndexedDB empty — try local mirror, then the SYNC mirror (survives
    // reinstall), and self-heal every store from whichever had it.
    let m = await mirrorReadLocal();
    if (!m[KEY_TOKEN]) m = await mirrorReadSync();
    if (m[KEY_TOKEN]) {
      token = m[KEY_TOKEN];
      username = m[KEY_USERNAME] || null;
      try { await setSetting(KEY_TOKEN, token); await setSetting(KEY_USERNAME, username); } catch {}
      await mirrorWrite(token, username); // re-populate local + sync
    }
  }
  return { token: token || null, username: username || null };
}

export async function saveSession(token, username) {
  await setSetting(KEY_TOKEN, token);
  await setSetting(KEY_USERNAME, username);
  await mirrorWrite(token, username);
}

export async function clearSession() {
  await setSetting(KEY_TOKEN, null);
  await setSetting(KEY_USERNAME, null);
  await mirrorWrite(null, null);
}

export async function login(username, password) {
  obLog.event('login_attempt', { context: { username } });
  const res = await fetch(`${SYNC.endpoint}/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password }),
  });
  let json;
  try { json = await res.json(); }
  catch { obLog.error('login_bad_response', { context: { status: res.status } }); return { ok: false, error: `HTTP ${res.status}` }; }
  if (!json.ok) {
    obLog.warn('login_failed', { context: { username, error: json.error || 'unknown' } });
    return { ok: false, error: json.error || 'login failed' };
  }
  // 2FA: the server verified the password (factor 1) and emailed a code. No
  // session yet — the caller must collect the code and call verifyOtp().
  if (json.otpRequired) {
    obLog.event('login_otp_required', { context: { username } });
    return { ok: true, otpRequired: true, identifier: username, email: json.email || null, otpSent: !!json.otpSent };
  }
  // Legacy / no-email edge case: server returned a token directly.
  await saveSession(json.token, json.username);
  obLog.event('login_ok', { context: { username: json.username } });
  return { ok: true, username: json.username };
}

// Second factor: exchange the emailed code for a session.
export async function verifyOtp(identifier, code) {
  const res = await fetch(`${SYNC.endpoint}/auth/otp/verify`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ identifier, code }),
  });
  let json;
  try { json = await res.json(); }
  catch { return { ok: false, error: `HTTP ${res.status}` }; }
  if (!json.ok || !json.token) return { ok: false, error: json.error || 'קוד שגוי' };
  await saveSession(json.token, json.username);
  obLog.event('login_otp_ok', { context: { username: json.username } });
  return { ok: true, username: json.username };
}

// Resend the code = re-run the password step (the server issues a fresh code).
export async function resendOtp(username, password) {
  return login(username, password);
}

export async function logout() {
  const { token } = await getSession();
  if (token) {
    try {
      await fetch(`${SYNC.endpoint}/logout`, {
        method: 'POST',
        headers: { 'X-Session-Token': token },
      });
    } catch { /* network failure is fine — we still clear locally */ }
  }
  await clearSession();
}

// Verify the current session is still valid server-side.
// If the server rotated our token (because it was getting old), save the new one.
export async function verifySession() {
  const { token, username } = await getSession();
  if (!token) return { ok: false };
  try {
    const res = await fetch(`${SYNC.endpoint}/me`, {
      headers: { 'X-Session-Token': token },
    });
    const json = await res.json();
    if (json.ok) {
      if (json.rotatedToken) {
        await saveSession(json.rotatedToken, json.username);
      }
      return { ok: true, username: json.username, role: json.role || 'user', isAdmin: !!json.isAdmin };
    }
    return { ok: false };
  } catch {
    return { ok: false, networkError: true };
  }
}

// Logout from ALL devices — useful when account compromise is suspected.
export async function logoutEverywhere() {
  const { token } = await getSession();
  if (token) {
    try {
      await fetch(`${SYNC.endpoint}/logout/all`, {
        method: 'POST',
        headers: { 'X-Session-Token': token },
      });
    } catch {}
  }
  await clearSession();
}
