/**
 * Content Security Policy (CSP) configuration.
 *
 * Using Report-Only mode initially to identify violations without breaking functionality.
 * After reviewing reports and tuning the policy, switch to enforcing mode.
 *
 * BASE DOMAINS (always included - core app functionality):
 * - Stripe: js.stripe.com, api.stripe.com, hooks.stripe.com
 * - Google OAuth: accounts.google.com, *.googleusercontent.com
 * - Apple OAuth: appleid.apple.com, appleid.cdn-apple.com
 * - Upstash (rate limiting): *.upstash.io
 * - ClickUp (contact form): forms.clickup.com, app-cdn.clickup.com
 *
 * TRACKING SERVICES (configurable via config/csp.config.mjs):
 * Default services: Google Analytics/GTM, Facebook, TikTok, LinkedIn, Pinterest
 * Override via CSP_EXTRA_* environment variables (see .env.example)
 *
 * @see config/csp.config.mjs for tracking service configuration
 */
import {
  extraScriptSrc,
  extraImgSrc,
  extraConnectSrc,
  extraFrameSrc,
} from './config/csp.config.mjs';

// Build CSP directives with base + extra domains
const ContentSecurityPolicy = [
  // Default fallback - restrict to same origin
  "default-src 'self'",

  // Scripts: Allow self, inline (Next.js requires this), eval (dev mode), Stripe, OAuth providers, ClickUp + extras
  `script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com https://accounts.google.com https://appleid.cdn-apple.com https://app-cdn.clickup.com${extraScriptSrc.length ? ' ' + extraScriptSrc.join(' ') : ''}`,

  // Styles: Allow self and inline (Tailwind/styled-components require inline styles)
  "style-src 'self' 'unsafe-inline'",

  // Images: Allow self, data URIs, blobs, Google profile pics, Stripe + extras
  `img-src 'self' data: blob: https://*.googleusercontent.com https://*.stripe.com${extraImgSrc.length ? ' ' + extraImgSrc.join(' ') : ''}`,

  // Fonts: Allow self only
  "font-src 'self'",

  // API connections: Allow self, Stripe API, Upstash, Google OAuth, Apple OAuth + extras
  `connect-src 'self' https://api.stripe.com https://*.upstash.io https://accounts.google.com https://appleid.apple.com${extraConnectSrc.length ? ' ' + extraConnectSrc.join(' ') : ''}`,

  // Frames: Allow Stripe for payment UI, Google and Apple for OAuth, ClickUp for contact form + extras
  `frame-src https://js.stripe.com https://hooks.stripe.com https://accounts.google.com https://appleid.apple.com https://forms.clickup.com${extraFrameSrc.length ? ' ' + extraFrameSrc.join(' ') : ''}`,

  // Object embeds: Disallow all (security best practice)
  "object-src 'none'",

  // Base URI: Restrict to self to prevent base tag hijacking
  "base-uri 'self'",

  // Form actions: Restrict to self and OAuth providers
  "form-action 'self' https://accounts.google.com https://appleid.apple.com",

  // Frame ancestors: Deny embedding to prevent clickjacking (equivalent to X-Frame-Options: DENY)
  "frame-ancestors 'none'",

  // Report violations to our collection endpoint so we can monitor before full enforcement
  'report-uri /api/csp-report',
].join('; ');

/**
 * Security headers configuration.
 * These provide defense-in-depth against common web vulnerabilities.
 */
const securityHeaders = [
  {
    // CSP in Report-Only mode - collects violations without blocking.
    // Includes report-uri so violations hit /api/csp-report for monitoring.
    // Promote to enforcing after 1-2 weeks of clean reports (GitHub #141, #223).
    key: 'Content-Security-Policy-Report-Only',
    value: ContentSecurityPolicy,
  },
  {
    // Prevent clickjacking by disallowing framing
    key: 'X-Frame-Options',
    value: 'DENY',
  },
  {
    // Prevent MIME type sniffing (can lead to XSS via incorrect content types)
    key: 'X-Content-Type-Options',
    value: 'nosniff',
  },
  {
    // Control referrer information sent with requests
    key: 'Referrer-Policy',
    value: 'strict-origin-when-cross-origin',
  },
  {
    // Enable XSS filtering (legacy browsers, most modern browsers ignore)
    key: 'X-XSS-Protection',
    value: '1; mode=block',
  },
  {
    // Permissions Policy: Restrict access to browser features
    key: 'Permissions-Policy',
    value: 'camera=(), microphone=(), geolocation=()',
  },
];

// Production-only headers. Both headers must NOT be sent on localhost (dev):
// - HSTS: forces HTTPS for localhost, breaking local development permanently
// - Enforcing CSP: upgrade-insecure-requests rewrites http:// resources to https://,
//   which breaks HMR websockets and other localhost HTTP connections in next dev
if (process.env.NODE_ENV === 'production') {
  securityHeaders.push(
    {
      key: 'Strict-Transport-Security',
      value: 'max-age=31536000; includeSubDomains',
    },
    {
      // Minimal enforcing CSP. Satisfies securityheaders.com and provides
      // real protection for the highest-risk directives without touching the
      // script/image/frame allowlists used by dealer pages.
      //
      // - upgrade-insecure-requests: browser rewrites http:// sub-resources to https://
      // - frame-ancestors 'none':   hard-blocks clickjacking (CSP enforcement, not just X-Frame-Options)
      // - object-src 'none':        disallows <object>/<embed>/<applet> (Flash/plugin vectors)
      // - base-uri 'self':          prevents <base href> hijacking of relative URLs
      key: 'Content-Security-Policy',
      value:
        "upgrade-insecure-requests; frame-ancestors 'none'; object-src 'none'; base-uri 'self'",
    }
  );
}

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  // productionBrowserSourceMaps must stay off: browser source maps embed the
  // original source of everything in the client bundles (app code and
  // node_modules), and .map files under .next/static are served publicly.
  // The Apache-level .js.map block on staging/prod is a second layer, not a
  // reason to re-enable. For one-off debugging, build locally with
  // `productionBrowserSourceMaps: true` instead of shipping maps.
  // Allow dev server access from external hostnames
  allowedDevOrigins: ['amsoil-dlp.acdev3.com', 'aimclear.biz'],
  // Disable in-memory ISR cache to ensure revalidatePath() takes effect immediately.
  // Without this, Next.js serves stale pages from memory for up to 5 minutes even after
  // revalidation is triggered. With this set to 0, pages are served from filesystem cache
  // only, and revalidatePath() properly invalidates on the next request.
  cacheMaxMemorySize: 0,
  // Allow isomorphic-dompurify and pino to work with their dependencies
  serverExternalPackages: ['isomorphic-dompurify', 'pino', 'pino-pretty'],
  images: {
    // Disable Next.js image optimization - using Cloudflare for image hosting/optimization
    unoptimized: true,
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'lh3.googleusercontent.com',
      },
      {
        protocol: 'https',
        hostname: 'lh4.googleusercontent.com',
      },
      {
        protocol: 'https',
        hostname: 'lh5.googleusercontent.com',
      },
    ],
  },
  async headers() {
    const headers = [
      {
        // Apply security headers to all routes
        source: '/(.*)',
        headers: securityHeaders,
      },
    ];
    // Long-lived immutable cache for Next.js static chunks.
    // Safe because /_next/static/* filenames are content-hashed — a code change
    // always produces a new URL, so there's no stale-content risk. Dev serves
    // these files dynamically at changing paths, so only gate this in prod.
    if (process.env.NODE_ENV === 'production') {
      headers.push({
        source: '/_next/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      });
    }
    // OG images live in public/og-cache and are therefore also directly
    // routable as static files (/og-cache/<name>.png), bypassing the /og
    // routes that send X-Robots-Tag: noindex. Mirror the header here so the
    // raw cache URLs can't be indexed as standalone results either.
    headers.push({
      source: '/og-cache/:path*',
      headers: [
        {
          key: 'X-Robots-Tag',
          value: 'noindex',
        },
      ],
    });
    return headers;
  },
  /**
   * URL Rewrites
   *
   * EXECUTION ORDER: Middleware runs BEFORE these rewrites.
   * For root path `/`:
   *   1. Middleware checks auth - redirects to /dashboard if authenticated
   *   2. If unauthenticated, middleware calls NextResponse.next()
   *   3. Next.js routing layer applies this rewrite: / → /index.html
   *   4. Static landing page is served from public/index.html
   *
   * Note: Query parameters are preserved by Next.js through rewrites
   * (accessible via window.location.search in client JS).
   */
  async rewrites() {
    return [
      {
        source: '/',
        destination: '/index.html',
      },
    ];
  },
  async redirects() {
    return [
      {
        source: '/landing',
        destination: '/',
        permanent: true,
      },
      {
        source: '/landing/',
        destination: '/',
        permanent: true,
      },
    ];
  },
};

export default nextConfig;
