const crypto = require('crypto');
const { logAudit } = require('../services/auditService');

// ─────────────────────────────────────────────────────────────────────────────
// CSRF Middleware
// Protects state-changing routes (POST, PUT, DELETE) from cross-site request
// forgery by validating tokens stored in session cookie.
// ─────────────────────────────────────────────────────────────────────────────

const CSRF_HEADER_NAME = 'x-csrf-token';

// CSRF cookie httpOnly setting - configurable via environment variable
// Default: true — the SPA fetches the token from GET /api/auth/csrf-token and stores
// it in memory, so JavaScript does not need direct access to document.cookie.
// httpOnly=true removes the token from the XSS-accessible cookie surface while
// preserving the double-submit validation on the server.
const CSRF_HTTP_ONLY = process.env.CSRF_HTTP_ONLY !== 'false'; // defaults to true

// CSRF cookie sameSite setting - configurable via environment variable
// Options: 'lax', 'strict', 'none'
// Default: 'strict' (most secure)
// Set CSRF_SAME_SITE=lax for SPA cross-site scenarios
const CSRF_SAME_SITE = process.env.CSRF_SAME_SITE || 'strict';

// CSRF cookie secure setting - configurable via environment variable
// Default: true (only send over HTTPS)
// Set CSRF_SECURE=false for HTTP development (NOT recommended for production)
const CSRF_SECURE = process.env.CSRF_SECURE !== 'false'; // defaults to true

// CSRF cookie name - configurable via environment variable
// Default: 'bt_csrf_token'
// Use CSRF_COOKIE_NAME to customize for multi-app deployments
const CSRF_COOKIE_NAME = process.env.CSRF_COOKIE_NAME || 'bt_csrf_token';

// Generate a cryptographically secure CSRF token
function generateCsrfToken() {
  return crypto.randomBytes(32).toString('hex');
}

/**
 * Detect HTTPS the same way services/authService.cookieOpts does:
 * req.secure (honors trust proxy) with an x-forwarded-proto fallback for
 * deployments where TRUST_PROXY is not configured.
 */
function requestLooksHttps(req) {
  if (!req) return false;
  if (req.secure) return true;
  const proto = req.get?.('x-forwarded-proto') || req.headers?.['x-forwarded-proto'];
  return String(proto || '').split(',').map(s => s.trim()).includes('https');
}

/**
 * Get or create CSRF token for the current session.
 * In the SPA's double-submit flow, tokens are stored in a readable cookie so
 * client/api.js can copy the value into the x-csrf-token header.
 */
function getCsrfToken(req, res) {
  let token = req.cookies?.[CSRF_COOKIE_NAME];
  
  if (!token) {
    token = generateCsrfToken();
    res.cookie(CSRF_COOKIE_NAME, token, {
      httpOnly: CSRF_HTTP_ONLY,
      sameSite: CSRF_SAME_SITE,
      secure: CSRF_SECURE && requestLooksHttps(req),
      path: '/',
    });
  }
  
  return token;
}

/**
 * Validate CSRF token from request.
 * Tokens can be provided via:
 * - x-csrf-token header (API clients)
 * - csrf_token body field (form submissions)
 * Query-parameter tokens are deliberately NOT accepted — URLs leak into
 * access logs, browser history, and Referer headers.
 */
function validateCsrfToken(req) {
  const cookieToken = req.cookies?.[CSRF_COOKIE_NAME];
  if (!cookieToken) return false;

  const headerToken = req.headers?.[CSRF_HEADER_NAME];
  if (headerToken && headerToken === cookieToken) return true;

  const bodyToken = req.body?.csrf_token;
  if (bodyToken && bodyToken === cookieToken) return true;

  return false;
}

/**
 * CSRF middleware - validates tokens for state-changing methods.
 * Skips validation for: GET, HEAD, OPTIONS (safe methods)
 * Requires token for: POST, PUT, DELETE, PATCH (state-changing)
 */
function csrfMiddleware(req, res, next) {
  // Exempt the login endpoint only — no session exists yet to hijack.
  // Compare against originalUrl (sans query string) so a "/login" subpath on
  // some other mounted router is NOT accidentally exempted.
  const fullPath = (req.originalUrl || '').split('?')[0];
  if (fullPath === '/api/auth/login') {
    return next();
  }

  // Only validate state-changing methods
  if (!['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
    return next();
  }

  // Skip validation for OPTIONS (preflight)
  if (req.method === 'OPTIONS') {
    return next();
  }

  // Allow API routes to opt-in explicitly via a flag
  // This allows flexibility for routes that use alternate auth (e.g., API keys)
  if (req.csrfSkip) {
    return next();
  }

  // Validate the CSRF token
  if (!validateCsrfToken(req)) {
    logAudit({ user_id: req.user?.id || null, action: 'csrf.failure', ip_address: req.ip, user_agent: req.get('user-agent') });
    return res.status(403).json({
      error: 'CSRF token validation failed',
      message: 'Your session has expired or this request may be fraudulent. Please refresh the page and try again.',
      code: 'CSRF_INVALID',
    });
  }

  next();
}

/**
 * Attach CSRF token to response locals for template rendering.
 * Frontend can access req.csrfToken() in templates.
 */
function csrfTokenProvider(req, res, next) {
  res.locals.csrfToken = getCsrfToken(req, res);
  next();
}

module.exports = {
  CSRF_COOKIE_NAME,
  CSRF_HEADER_NAME,
  CSRF_HTTP_ONLY,
  CSRF_SAME_SITE,
  CSRF_SECURE,
  generateCsrfToken,
  getCsrfToken,
  validateCsrfToken,
  csrfMiddleware,
  csrfTokenProvider,
};
