import { NextRequest, NextResponse } from 'next/server';

const PUBLIC_PATHS = ['/login', '/reset-password'];

/**
 * Build the Content-Security-Policy for a single request.
 *
 * Production uses a strict, nonce-based policy with `strict-dynamic`: only scripts
 * carrying this request's nonce (and scripts they load) may execute, which neutralises
 * reflected/stored XSS regardless of any host allowlist. Next.js applies the nonce to
 * its own bootstrap/chunk scripts automatically because middleware forwards it on the
 * request `Content-Security-Policy` header (see below); our own inline scripts read it
 * from the `x-nonce` request header in the root layout.
 *
 * `strict-dynamic` also covers the jsQR script the boarding scanner injects at runtime
 * (a trusted script's dynamically-created <script> is allowed), so no CDN host needs
 * allowlisting.
 *
 * Development relaxes `script-src` (Next.js HMR/react-refresh needs `unsafe-eval` and
 * inline) and allows the HMR websocket, and drops `upgrade-insecure-requests` so
 * plain-HTTP localhost keeps working.
 */
function buildCsp(nonce: string): string {
  const isProd = process.env.NODE_ENV === 'production';

  // Origin the browser calls for API/XHR/fetch — must be allowed in connect-src.
  let apiOrigin = '';
  try {
    apiOrigin = new URL(process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000').origin;
  } catch {
    apiOrigin = '';
  }

  const scriptSrc = isProd
    ? `'self' 'nonce-${nonce}' 'strict-dynamic'`
    : `'self' 'unsafe-inline' 'unsafe-eval'`;

  // The Socket.IO WebSocket upgrade connects to wss://<api-host>; under CSP a
  // `https://host` source does NOT cover `wss://host`, so add it explicitly.
  const wsOrigin = apiOrigin.replace(/^http/, 'ws'); // https→wss, http→ws

  const connectSrc = isProd
    ? `'self' ${apiOrigin} ${wsOrigin}`.trim()
    : `'self' ${apiOrigin} ws: wss:`.trim();

  const directives = [
    `default-src 'self'`,
    `base-uri 'self'`,
    `script-src ${scriptSrc}`,
    // Inline styles (Tailwind runtime + React `style=` attributes) can't execute JS;
    // nonce-ing them reliably breaks Next/React, so 'unsafe-inline' is the accepted stance.
    `style-src 'self' 'unsafe-inline'`,
    `img-src 'self' data: blob: ${apiOrigin}`.trim(),
    `font-src 'self' data:`,
    `connect-src ${connectSrc}`,
    `worker-src 'self' blob:`,
    `frame-src 'self'`,
    `object-src 'none'`,
    `form-action 'self'`,
    `frame-ancestors 'none'`,
    ...(isProd ? ['upgrade-insecure-requests'] : []),
  ];

  return directives.join('; ');
}

export function middleware(request: NextRequest) {
  const nonce = btoa(crypto.randomUUID());
  const csp = buildCsp(nonce);

  // Forward the nonce + CSP on the *request* so Next.js nonces its own scripts and our
  // layout can read `x-nonce`. The browser-enforced copy is set on the response below.
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);
  requestHeaders.set('Content-Security-Policy', csp);

  const render = () => NextResponse.next({ request: { headers: requestHeaders } });

  const { pathname } = request.nextUrl;

  let response: NextResponse;
  if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) {
    response = render();
  } else {
    // Token is stored in localStorage (client-side only), so middleware can't
    // read it directly. We use a cookie set on login as the server-side signal.
    const token = request.cookies.get('auth_token')?.value;
    response = token ? render() : NextResponse.redirect(new URL('/login', request.url));
  }

  response.headers.set('Content-Security-Policy', csp);
  // Anti-clickjacking. `frame-ancestors 'none'` (in the CSP above) is the modern control;
  // X-Frame-Options: DENY is the legacy equivalent for older browsers and scanners.
  response.headers.set('X-Frame-Options', 'DENY');
  return response;
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|api).*)'],
};
