// Nordbank — auth: password hashing via Web Crypto PBKDF2, session, register/login.
//
// Hash algorithm: PBKDF2-HMAC-SHA-256, 100 000 iterations, 16-byte salt,
// 256-bit derived key. Stored in DB as hex strings.
//
// In production this work moves to the server. The client never sees other
// users' hashes; the server enforces rate limits and refuses login on
// mismatched hash. This module is shaped so the call sites stay identical
// when that move happens.

const SESSION_KEY = 'nordbank.session';
const PBKDF2_ITERATIONS = 100000;

// --- helpers --------------------------------------------------------------
function bytesToHex(bytes) {
  let s = '';
  for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, '0');
  return s;
}
function hexToBytes(hex) {
  const out = new Uint8Array(hex.length / 2);
  for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.substr(i * 2, 2), 16);
  return out;
}

function generateSalt() {
  const arr = new Uint8Array(16);
  crypto.getRandomValues(arr);
  return bytesToHex(arr);
}

async function pbkdf2Hash(password, saltHex, iterations) {
  const enc = new TextEncoder();
  const keyMaterial = await crypto.subtle.importKey(
    'raw', enc.encode(password), { name: 'PBKDF2' }, false, ['deriveBits']
  );
  const bits = await crypto.subtle.deriveBits(
    {
      name: 'PBKDF2',
      salt: hexToBytes(saltHex),
      iterations: iterations,
      hash: 'SHA-256',
    },
    keyMaterial,
    256
  );
  return bytesToHex(new Uint8Array(bits));
}

// Constant-time string compare to avoid timing leaks. In a browser running
// at JS speed this is mostly theatre, but it's free.
function safeEqual(a, b) {
  if (a.length !== b.length) return false;
  let r = 0;
  for (let i = 0; i < a.length; i++) r |= a.charCodeAt(i) ^ b.charCodeAt(i);
  return r === 0;
}

// --- session ---------------------------------------------------------------

const _authListeners = new Set();
let _session = readStoredSession();

function readStoredSession() {
  try {
    const raw = localStorage.getItem(SESSION_KEY);
    if (!raw) return null;
    return JSON.parse(raw);
  } catch (e) { return null; }
}

function writeSession(s) {
  _session = s;
  if (s) localStorage.setItem(SESSION_KEY, JSON.stringify(s));
  else   localStorage.removeItem(SESSION_KEY);
  _authListeners.forEach(fn => fn());
}

function getCurrentUser() { return _session; }

function useCurrentUser() {
  const [, force] = React.useReducer(x => x + 1, 0);
  React.useEffect(() => {
    _authListeners.add(force);
    return () => { _authListeners.delete(force); };
  }, []);
  return _session;
}

// --- API -------------------------------------------------------------------

// Register a new user; throws if email already in use.
async function registerUser(email, password, options) {
  const opts = options || {};
  const normalised = email.trim().toLowerCase();
  const existing = await dbFindUserByEmail(normalised);
  if (existing) throw new Error('email-in-use');
  const salt = generateSalt();
  const hash = await pbkdf2Hash(password, salt, PBKDF2_ITERATIONS);
  const id = await dbCreateUser(normalised, hash, salt, PBKDF2_ITERATIONS);
  if (opts.claimApplications !== false) {
    await dbClaimApplicationsForUser(id, normalised);
  }
  writeSession({ id, email: normalised });
  return { id, email: normalised };
}

// Log in. Returns user on success, throws on failure.
async function loginUser(email, password) {
  const user = await dbFindUserByEmail(email);
  if (!user) throw new Error('invalid-credentials');
  const computed = await pbkdf2Hash(password, user.password_salt, user.iterations || PBKDF2_ITERATIONS);
  if (!safeEqual(computed, user.password_hash)) throw new Error('invalid-credentials');
  writeSession({ id: user.id, email: user.email });
  return { id: user.id, email: user.email };
}

function logoutUser() { writeSession(null); }

// Simple password strength heuristic. 0..4 (very weak..strong).
function passwordStrength(p) {
  if (!p) return 0;
  let score = 0;
  if (p.length >= 8) score++;
  if (p.length >= 12) score++;
  if (/[A-Z]/.test(p) && /[a-z]/.test(p)) score++;
  if (/\d/.test(p)) score++;
  if (/[^A-Za-z0-9]/.test(p)) score++;
  return Math.min(4, score);
}

Object.assign(window, {
  PBKDF2_ITERATIONS,
  generateSalt, pbkdf2Hash,
  getCurrentUser, useCurrentUser,
  registerUser, loginUser, logoutUser,
  passwordStrength,
});
