import { doubleCsrf } from 'csrf-csrf';
import type { Request } from 'express';
import { COOKIE_CSRF_NAME } from './cookie-config';

/**
 * CSRF protection via double-submit cookie (csrf-csrf).
 *
 * When it applies:
 *   Cookie-authenticated state-changing requests (POST / PATCH / PUT
 *   / DELETE) must present a matching X-CSRF-Token header.
 *
 * When it's bypassed:
 *   - Requests carrying an `Authorization: Bearer …` header. Those
 *     clients aren't riding a cookie, so classic CSRF (browser
 *     auto-sends cookies) doesn't apply. This keeps the transition
 *     from Bearer→cookie backwards-compatible without weakening the
 *     cookie path.
 *   - Login / refresh / logout / forgot-password / reset-password.
 *     Login has to work before a CSRF token can be issued;
 *     refresh + logout are cookie-bound but self-contained (their
 *     only side effect is on the caller's own session); forgot /
 *     reset are unauthenticated flows that have their own throttler +
 *     token defenses (see security.md A04 / A07).
 *
 * Where the token lives:
 *   Header:  X-CSRF-Token
 *   Cookie:  dmc_csrf (host-only, non-httpOnly by necessity — the FE
 *            has to read it to echo into the header. Signed via the
 *            CSRF_SECRET so a page can't forge one.)
 */
export const CSRF_HEADER = 'x-csrf-token';

// Routes exempt from CSRF (auth surface + Bearer clients). See doc
// above. Pattern-matched with startsWith after stripping query.
const EXEMPT_PATHS = [
  '/api/auth/login',
  '/api/auth/refresh',
  '/api/auth/logout',
  '/api/auth/forgot-password',
  '/api/auth/reset-password',
];

export function isExemptFromCsrf(req: Request): boolean {
  // Bearer-token clients don't ride cookies — CSRF doesn't apply.
  const authHeader = req.headers.authorization;
  if (authHeader && authHeader.toLowerCase().startsWith('bearer ')) {
    return true;
  }
  const path = req.path || req.url.split('?')[0];
  return EXEMPT_PATHS.some((p) => path === p);
}

export function buildCsrf(secret: string) {
  return doubleCsrf({
    getSecret: () => secret,
    getSessionIdentifier: (req) => {
      // Tenant + IP fingerprint — good enough to bind the token to
      // "this browser talking to this tenant". A per-user identifier
      // would rotate on login/logout and break in-flight tokens.
      const anyReq = req as Request & { cookies?: Record<string, string> };
      const tenant = (anyReq.headers['x-tenant-id'] as string) || '';
      const ip = anyReq.ip || '';
      return `${tenant}|${ip}`;
    },
    cookieName: COOKIE_CSRF_NAME,
    cookieOptions: {
      httpOnly: false, // FE must read it to echo into the header
      sameSite: 'strict',
      secure: process.env.NODE_ENV === 'production',
      path: '/',
    },
    size: 64,
    // Only guard mutating verbs. Reads are non-CSRF.
    ignoredMethods: ['GET', 'HEAD', 'OPTIONS'],
    getCsrfTokenFromRequest: (req) => (req.headers[CSRF_HEADER] as string) || '',
  });
}
