// Authentication & user management — zero dependencies.
//   • Users stored in data/users.json, passwords scrypt-hashed with per-user salt.
//   • Sessions are stateless signed cookies (HMAC-SHA256), 30-day expiry,
//     invalidated automatically when a user's password changes.
//   • Roles: superadmin (everything) → admin (agency staff) → client (own sites only).

import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DATA_DIR = path.join(__dirname, '..', 'data');
const USERS_FILE = path.join(DATA_DIR, 'users.json');
const SECRET_FILE = path.join(DATA_DIR, 'secret.json');

export const ROLES = ['superadmin', 'admin', 'client'];

// ── secret (auto-generated once, survives restarts) ────────────────────
let _secret = null;
export function secret() {
  if (_secret) return _secret;
  if (process.env.SESSION_SECRET) return (_secret = process.env.SESSION_SECRET);
  try {
    _secret = JSON.parse(fs.readFileSync(SECRET_FILE, 'utf8')).secret;
  } catch {
    _secret = crypto.randomBytes(32).toString('hex');
    fs.mkdirSync(DATA_DIR, { recursive: true });
    fs.writeFileSync(SECRET_FILE, JSON.stringify({ secret: _secret }));
  }
  return _secret;
}

// ── user store ─────────────────────────────────────────────────────────
export function loadUsers() {
  try { return JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')).users || []; } catch { return []; }
}
function saveUsers(users) {
  fs.mkdirSync(DATA_DIR, { recursive: true });
  fs.writeFileSync(USERS_FILE, JSON.stringify({ users }, null, 2));
}

function hashPassword(password, salt) {
  return crypto.scryptSync(String(password), salt, 64).toString('hex');
}

export function createUser({ name, email, password, role = 'client', status = 'pending', siteIds = [] }) {
  const users = loadUsers();
  const em = String(email).trim().toLowerCase();
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(em)) throw new Error('Invalid email address.');
  if (String(password).length < 8) throw new Error('Password must be at least 8 characters.');
  if (users.some((u) => u.email === em)) throw new Error('An account with that email already exists.');
  const salt = crypto.randomBytes(16).toString('hex');
  const user = {
    id: crypto.randomBytes(8).toString('hex'),
    name: String(name || '').trim() || em.split('@')[0],
    email: em,
    salt,
    passHash: hashPassword(password, salt),
    role: ROLES.includes(role) ? role : 'client',
    status, // 'active' | 'pending' | 'disabled'
    siteIds, // array of site ids (clients); admins/superadmins see all
    createdAt: new Date().toISOString(),
  };
  users.push(user);
  saveUsers(users);
  return publicUser(user);
}

export function verifyLogin(email, password) {
  const em = String(email).trim().toLowerCase();
  const user = loadUsers().find((u) => u.email === em);
  if (!user) return { error: 'Unknown email or wrong password.' };
  const hash = hashPassword(password, user.salt);
  if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(user.passHash))) {
    return { error: 'Unknown email or wrong password.' };
  }
  if (user.status === 'pending') return { error: 'Your account is awaiting approval by an administrator.' };
  if (user.status === 'disabled') return { error: 'This account has been disabled.' };
  return { user };
}

export function updateUser(id, patch) {
  const users = loadUsers();
  const user = users.find((u) => u.id === id);
  if (!user) throw new Error('User not found.');
  if (patch.role !== undefined) {
    if (!ROLES.includes(patch.role)) throw new Error('Invalid role.');
    user.role = patch.role;
  }
  if (patch.status !== undefined) {
    if (!['active', 'pending', 'disabled'].includes(patch.status)) throw new Error('Invalid status.');
    user.status = patch.status;
  }
  if (patch.siteIds !== undefined) user.siteIds = Array.isArray(patch.siteIds) ? patch.siteIds : [];
  if (patch.name !== undefined) user.name = String(patch.name).trim() || user.name;
  if (patch.password !== undefined) {
    if (String(patch.password).length < 8) throw new Error('Password must be at least 8 characters.');
    user.salt = crypto.randomBytes(16).toString('hex');
    user.passHash = hashPassword(patch.password, user.salt); // invalidates existing sessions
  }
  saveUsers(users);
  return publicUser(user);
}

export function deleteUser(id) {
  const users = loadUsers();
  const idx = users.findIndex((u) => u.id === id);
  if (idx === -1) throw new Error('User not found.');
  users.splice(idx, 1);
  saveUsers(users);
}

export function publicUser(u) {
  return { id: u.id, name: u.name, email: u.email, role: u.role, status: u.status, siteIds: u.siteIds || [], createdAt: u.createdAt };
}

// ── sessions (stateless signed cookie) ─────────────────────────────────
const b64u = (s) => Buffer.from(s).toString('base64url');
const sign = (payload) => crypto.createHmac('sha256', secret()).update(payload).digest('base64url');

export function sessionCookie(user) {
  const payload = b64u(JSON.stringify({
    uid: user.id,
    exp: Date.now() + 30 * 86400000,
    pv: user.passHash.slice(0, 12), // password change invalidates
  }));
  return `${payload}.${sign(payload)}`;
}

export function getSessionUser(req) {
  const cookies = Object.fromEntries(
    (req.headers.cookie || '').split(';').map((c) => {
      const i = c.indexOf('=');
      return i === -1 ? [c.trim(), ''] : [c.slice(0, i).trim(), c.slice(i + 1).trim()];
    })
  );
  const token = cookies.lp_sess;
  if (!token) return null;
  const dot = token.lastIndexOf('.');
  if (dot === -1) return null;
  const payload = token.slice(0, dot), sig = token.slice(dot + 1);
  try {
    if (!crypto.timingSafeEqual(Buffer.from(sign(payload)), Buffer.from(sig))) return null;
    const data = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
    if (data.exp < Date.now()) return null;
    const user = loadUsers().find((u) => u.id === data.uid);
    if (!user || user.status !== 'active') return null;
    if (user.passHash.slice(0, 12) !== data.pv) return null;
    return user;
  } catch { return null; }
}

export function setSessionHeader(res, user) {
  res.setHeader('Set-Cookie',
    `lp_sess=${sessionCookie(user)}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${30 * 86400}`);
}
export function clearSessionHeader(res) {
  res.setHeader('Set-Cookie', 'lp_sess=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0');
}

export const isAdmin = (u) => u && (u.role === 'admin' || u.role === 'superadmin');
