import type { Req, Res, Next } from '../types/http';

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

// ─────────────────────────────────────────────────────────────────────────────
// CSRF Middleware — double-submit token protection for state-changing routes.
// ─────────────────────────────────────────────────────────────────────────────

const CSRF_HEADER_NAME = 'x-csrf-token';

const CSRF_HTTP_ONLY = process.env.CSRF_HTTP_ONLY !== 'false'; // defaults to true
const CSRF_SAME_SITE = process.env.CSRF_SAME_SITE || 'strict';
const CSRF_SECURE = process.env.CSRF_SECURE !== 'false'; // defaults to true
const CSRF_COOKIE_NAME = process.env.CSRF_COOKIE_NAME || 'bt_csrf_token';

function generateCsrfToken(): string {
  return crypto.randomBytes(32).toString('hex');
}

/**
 * Detect HTTPS the same way services/authService.cookieOpts does.
 */
function requestLooksHttps(req?: Req): boolean {
  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: string) => s.trim())
    .includes('https');
}

/**
 * Get or create CSRF token for the current session (readable double-submit cookie).
 */
function getCsrfToken(req: Req, res: Res): string {
  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 (x-csrf-token header or csrf_token body field).
 */
function validateCsrfToken(req: Req): boolean {
  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.
 */
function csrfMiddleware(req: Req, res: Res, next: Next): any {
  // Exempt the login endpoint only — no session exists yet to hijack.
  const fullPath = (req.originalUrl || '').split('?')[0];
  if (fullPath === '/api/auth/login') {
    return next();
  }

  if (!['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
    return next();
  }

  if (req.method === 'OPTIONS') {
    return next();
  }

  if (req.csrfSkip) {
    return next();
  }

  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.
 */
function csrfTokenProvider(req: Req, res: Res, next: Next): void {
  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,
};
