import { defineMiddleware } from 'astro:middleware';
import { logger } from './utils/logger';
import { captureException } from './utils/sentry';

/**
 * Security Middleware
 * Implements security headers and domain validation for workroot.in
 *
 * Security Headers Implemented:
 * - Content-Security-Policy (CSP): Controls resource loading
 * - X-Frame-Options: Prevents clickjacking
 * - X-Content-Type-Options: Prevents MIME sniffing
 * - Referrer-Policy: Controls referrer information
 * - Permissions-Policy: Controls browser features
 * - Strict-Transport-Security (HSTS): Enforces HTTPS
 */

// CSRF protection: validate Origin/Referer for state-changing API requests
function validateCsrfOrigin(request: Request, url: URL): boolean {
  const method = request.method;
  const path = url.pathname;

  // Only enforce on state-changing API methods
  if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) return true;
  if (!path.startsWith('/api/')) return true;

  // Runtime check: allow localhost/127.0.0.1 (testing, local dev, staging on same host)
  const requestHost = url.hostname;
  if (requestHost === 'localhost' || requestHost === '127.0.0.1' || requestHost === '0.0.0.0') {
    return true;
  }

  // In development build, allow any origin
  if (!import.meta.env.PROD) return true;

  const origin = request.headers.get('origin');
  const referer = request.headers.get('referer');

  if (origin) {
    // Allow same-host origin (handles any port on same hostname)
    try {
      const originHost = new URL(origin).hostname;
      if (originHost === requestHost) return true;
    } catch {}
    return origin === 'https://workroot.in' || origin === 'https://www.workroot.in';
  }
  if (referer) {
    try {
      const refererHost = new URL(referer).hostname;
      if (refererHost === requestHost) return true;
    } catch {}
    return referer.startsWith('https://workroot.in') || referer.startsWith('https://www.workroot.in');
  }

  // If neither origin nor referer, reject (suspicious for non-localhost)
  return false;
}

export const onRequest = defineMiddleware(async (context, next) => {
  const startTime = Date.now();
  const method = context.request.method;
  const path = context.url.pathname;

  // Normalize paths corrupted by a trailing stray ")" before routing.
  // These come from monitors/links that captured a URL together with a
  // closing parenthesis — e.g. "https://workroot.in/)" copied out of the
  // markdown/shell form "(https://workroot.in/)". Without this, every such
  // probe hits a non-existent route and 404s on a loop (recurring "GET /)").
  // Stripping the trailing paren(s) lets the request resolve to the intended
  // route (e.g. "/)" -> "/"), so the monitor gets a 301 -> 200 instead.
  const normalizedPath = path.replace(/\)+$/, '');
  if (normalizedPath !== path && normalizedPath.length > 0) {
    return context.redirect(`${normalizedPath}${context.url.search}`, 301);
  }

  // CSRF origin validation for API mutations
  if (!validateCsrfOrigin(context.request, context.url)) {
    logger.warn('middleware', 'CSRF origin check failed', {
      method,
      path,
      origin: context.request.headers.get('origin'),
      referer: context.request.headers.get('referer'),
    });
    const requestOrigin = context.request.headers.get('origin') ?? '';
    const allowedOrigins = ['https://workroot.in', 'https://www.workroot.in'];
    const corsOrigin = allowedOrigins.includes(requestOrigin) ? requestOrigin : 'https://workroot.in';
    return new Response(
      JSON.stringify({ success: false, error: 'Forbidden: invalid origin.' }),
      {
        status: 403,
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': corsOrigin,
          'Access-Control-Allow-Methods': 'POST, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type',
        },
      }
    );
  }

  let response: Response;
  try {
    response = await next();
  } catch (err) {
    const durationMs = Date.now() - startTime;
    await captureException(err, {
      scope: 'middleware',
      extra: { method, path, durationMs },
    });
    // Re-throw so Astro can render its 500 page
    throw err;
  }

  const durationMs = Date.now() - startTime;
  const status = response.status;

  // Log all SSR errors (5xx) and not-found (404) responses
  if (status >= 500) {
    logger.error('middleware', `SSR error ${status}`, { method, path, status, durationMs });
  } else if (status === 404) {
    // 404s are dominated by bots/scanners probing malformed or garbage paths
    // (e.g. "/)"), which would otherwise flood warn-level logs and trip alerts.
    // Treat a 404 as a warning only when it likely originated from our own site
    // (same-origin referer) — that indicates a real broken internal link worth
    // fixing. Everything else (no referer / external referer) is logged at info.
    const referer = context.request.headers.get('referer');
    let internalNavigation = false;
    if (referer) {
      try {
        internalNavigation = new URL(referer).hostname === context.url.hostname;
      } catch {
        // Malformed referer header → treat as external/untrusted
      }
    }
    const notFoundMeta = { method, path, status, durationMs, referer: referer ?? undefined };
    if (internalNavigation) {
      logger.warn('middleware', `Not found (broken internal link)`, notFoundMeta);
    } else {
      logger.info('middleware', `Not found`, notFoundMeta);
    }
  } else if (status >= 400) {
    logger.warn('middleware', `Client error ${status}`, { method, path, status, durationMs });
  } else {
    logger.debug('middleware', `Request`, { method, path, status, durationMs });
  }

  // Clone the response to add headers
  const modifiedResponse = new Response(response.body, response);

  // Content Security Policy (CSP)
  // Allows:
  // - self: Same-origin resources
  // - https://images.unsplash.com: External images (explicit, not wildcard https:)
  // - https://fonts.googleapis.com & https://fonts.gstatic.com: Google Fonts
  // - inline scripts/styles: Required for Astro framework
  // - https://www.googletagmanager.com & https://www.google-analytics.com: GA4
  // - https://plausible.io: Plausible Analytics
  // - https://api.wrnexus.com: WRNexus web-push SDK (sdk.js) + runtime config
  // Note: unsafe-eval is removed in production (only needed during Astro HMR dev mode)
  const isProduction = import.meta.env.PROD;
  const scriptSrc = isProduction
    ? "script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io https://api.wrnexus.com"
    : "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com https://plausible.io https://api.wrnexus.com";

  const csp = [
    "default-src 'self'",
    scriptSrc,
    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
    "img-src 'self' data: https://images.unsplash.com https://www.google-analytics.com https://www.googletagmanager.com https://maps.googleapis.com https://maps.gstatic.com",
    "font-src 'self' data: https://fonts.gstatic.com",
    "connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://stats.g.doubleclick.net https://plausible.io https://api.wrnexus.com",
    "worker-src 'self'",
    "manifest-src 'self'",
    "object-src 'none'", // Block Flash/plugins (defense-in-depth)
    "frame-src https://www.google.com", // Allow Google Maps iframe embed
    "frame-ancestors 'none'",
    "base-uri 'self'",
    "form-action 'self'",
    "upgrade-insecure-requests",
  ].join('; ');

  modifiedResponse.headers.set('Content-Security-Policy', csp);

  // X-Frame-Options: Prevent clickjacking
  // DENY prevents any domain from framing the content
  modifiedResponse.headers.set('X-Frame-Options', 'DENY');

  // X-Content-Type-Options: Prevent MIME sniffing
  // nosniff prevents browsers from interpreting files as different MIME type
  modifiedResponse.headers.set('X-Content-Type-Options', 'nosniff');

  // Referrer-Policy: Control referrer information
  // strict-origin-when-cross-origin sends full URL for same-origin, origin only for cross-origin
  modifiedResponse.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');

  // Permissions-Policy: Disable unnecessary browser features
  // Restricts access to sensitive APIs to prevent abuse
  modifiedResponse.headers.set('Permissions-Policy',
    'geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()'
  );

  // Strict-Transport-Security (HSTS): Enforce HTTPS
  // max-age=31536000: 1 year
  // includeSubDomains: Apply to all subdomains
  // preload: Allow inclusion in HSTS preload list
  // Only set HSTS in production and when using HTTPS
  const isHttps = context.url.protocol === 'https:';

  if (isProduction && isHttps) {
    modifiedResponse.headers.set(
      'Strict-Transport-Security',
      'max-age=31536000; includeSubDomains; preload'
    );
  }

  // X-Permitted-Cross-Domain-Policies: Restrict cross-domain access
  modifiedResponse.headers.set('X-Permitted-Cross-Domain-Policies', 'none');

  // X-DNS-Prefetch-Control: Control DNS prefetching
  modifiedResponse.headers.set('X-DNS-Prefetch-Control', 'on');

  // Domain validation: Ensure requests are for workroot.in
  const allowedHosts = ['workroot.in', 'www.workroot.in', 'localhost', '127.0.0.1', '0.0.0.0'];
  const requestHost = context.url.hostname;

  // In development, allow any host
  if (!isProduction || allowedHosts.includes(requestHost)) {
    return modifiedResponse;
  }

  // In production, if host doesn't match, redirect to canonical domain
  if (isProduction && !allowedHosts.includes(requestHost)) {
    const canonicalUrl = `https://workroot.in${context.url.pathname}${context.url.search}`;
    return Response.redirect(canonicalUrl, 301);
  }

  return modifiedResponse;
});
