/**
 * middleware/auth.js
 * requireLogin, requireRole, and per-request helpers.
 */

'use strict';

const User = require('../models/user');

function loadUser(req, res, next) {
  if (req.session && req.session.userId) {
    const u = User.getById(req.session.userId);
    if (u) {
      // Session invalidation: if the user's password_changed_at moved past
      // the value stamped on this session at login, the session is from
      // before a password change and must be torn down.
      if (u.password_changed_at && req.session.password_at &&
          u.password_changed_at > req.session.password_at) {
        // Mark for destruction; requireLogin will redirect/401.
        req.session.userId = null;
        req.user = null;
        res.locals.user = null;
        // Stash a one-time flag so the next response can explain why
        if (!req.session.flash) req.session.flash = { type: 'info', message: 'Your session ended because your password was changed from another device.' };
        // Drop password_at too so the check is consistent if session is reused
        delete req.session.password_at;
        return next();
      }
      // Same for email: if email_changed_at moved past this session's
      // email_at (set on login), this session's email-bound state is
      // stale. For now we keep the user logged in but we could re-prompt
      // for 2FA — tracked for future hardening.
      req.user = User.safeUser(u);
      res.locals.user = req.user;
    } else {
      req.user = null;
      res.locals.user = null;
    }
  } else {
    req.user = null;
    res.locals.user = null;
  }
  next();
}

function exposeLocals(req, res, next) {
  res.locals.csrfToken = req.session && req.session.csrfToken;
  res.locals.flash = req.session && req.session.flash;
  if (req.session) req.session.flash = null;
  res.locals.appName = 'Shield';
  next();
}

function requireLogin(req, res, next) {
  // 2FA-pending: the user has provided the right password but hasn't entered
  // their 2FA code yet. Allow the request through ONLY if it's targeting the
  // 2FA verification endpoint or the logout endpoint.
  if (!req.user && req.session && req.session.pending_2fa_user_id && req.session.pending_2fa_expires > Date.now()) {
    if (req.path === '/2fa/auth/login/2fa' || req.path === '/auth/logout' || req.path === '/auth/2fa-status') {
      return next();
    }
    return res.status(401).json({ error: 'TWO_FA_REQUIRED' });
  }
  if (!req.user) {
    if (req.accepts('html')) {
      return res.redirect('/login?next=' + encodeURIComponent(req.originalUrl));
    }
    return res.status(401).json({ error: 'UNAUTHENTICATED' });
  }
  if (req.user.suspended) {
    req.session.destroy(() => {});
    return res.status(403).json({ error: 'SUSPENDED', message: 'Account suspended.' });
  }
  next();
}

function requireRole(...roles) {
  return (req, res, next) => {
    if (!req.user) return res.status(401).json({ error: 'UNAUTHENTICATED' });
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'FORBIDDEN', need: roles });
    }
    next();
  };
}

/**
 * Gate routes behind a plan tier. The user's plan is read from the
 * session and the plans config — overrides per-user (used by sales
 * to grant a Pro trial to a Free user) are honored.
 *
 *   router.get('/findings/:id/page', requireLogin, requirePlan('pro','agency'), handler)
 *
 * Returns 403 with `need: {plans:[...]}` so the client can show
 * an "upgrade to Pro" CTA with a single source of truth for which
 * plan unlocks the feature.
 */
function requirePlan(...allowedPlanKeys) {
  return (req, res, next) => {
    if (!req.user) return res.status(401).json({ error: 'UNAUTHENTICATED' });
    // Superadmin bypass — they can always see what the top plans see.
    if (req.user.role === 'superadmin') return next();
    const { getPlan } = require('../config/plans');
    const plan = getPlan(req.user.plan, req.user.plan_override_json);
    const userPlanKey = req.user.plan || 'free';
    if (!allowedPlanKeys.includes(userPlanKey)) {
      return res.status(403).json({
        error: 'PLAN_REQUIRED',
        need: { plans: allowedPlanKeys, current_plan: userPlanKey },
        message: `This feature requires one of: ${allowedPlanKeys.join(', ')} (current plan: ${userPlanKey}).`,
      });
    }
    next();
  };
}

/**
 * Gate superadmin routes behind a verified 2FA session.
 * - Reads `user_2fa.enabled` and `req.session.twofa_verified_at`.
 * - If the user has 2FA enabled but the session was NOT marked as
 *   2FA-verified in this request, return 401 with `require_2fa: true`.
 *   The client should then POST to /2fa/verify-on-login.
 * - "Remember this device" cookie (`n2fa_trust`) bypasses the gate
 *   on subsequent requests: if the cookie's token hash matches a
 *   live row in `trusted_devices`, we mark the session as 2FA-
 *   verified and roll the 1h window forward. Survives deploys
 *   because the cookie is client-side.
 * - Refreshes the 2FA verified-window on every request (rolling 1h).
 * - Set NCII_REQUIRE_SUPERADMIN_2FA=0 to disable enforcement for
 *   staging or single-tenant demo deployments.
 */
function requireSuperadmin2FA(req, res, next) {
  if (!req.user) return res.status(401).json({ error: 'UNAUTHENTICATED' });
  if (req.user.role !== 'superadmin') return next();   // only gates admins
  if (process.env.NCII_REQUIRE_SUPERADMIN_2FA === '0') return next();
  const { db } = require('../config/db');
  const twofaRow = db.prepare('SELECT enabled FROM user_2fa WHERE user_id = ?').get(req.user.id);
  if (!twofaRow || !twofaRow.enabled) {
    return res.status(401).json({
      error: 'TWO_FA_REQUIRED',
      message: 'Superadmin must enable 2FA before accessing this area. Visit /2fa to enroll.',
    });
  }
  // Trusted-device cookie check — if present and valid, treat this
  // request as freshly 2FA-verified. This is the path that lets
  // the operator deploy without re-entering the 6-digit code.
  try {
    const trustedDevice = require('../services/trustedDevice');
    const row = trustedDevice.check(req);
    if (row && row.user_id === req.user.id) {
      req.session.twofa_verified_at = Date.now();
      return next();
    }
  } catch (_) { /* best-effort: fall through to session check */ }
  const verifiedAt = req.session.twofa_verified_at;
  const ONE_HOUR = 60 * 60 * 1000;
  if (!verifiedAt || (Date.now() - verifiedAt) > ONE_HOUR) {
    return res.status(401).json({ error: 'TWO_FA_REQUIRED', message: '2FA verification required.', pending_user_id: req.user.id });
  }
  // Roll the window forward on every successful admin call
  req.session.twofa_verified_at = Date.now();
  next();
}

function flash(req, type, message) {
  if (req.session) {
    req.session.flash = { type, message, at: Date.now() };
  }
}

module.exports = { loadUser, exposeLocals, requireLogin, requireRole, requirePlan, requireSuperadmin2FA, flash };
