import { clerkMiddleware, type ClerkMiddlewareAuth } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { SITE, type Lang } from "./lib/config";
import {
  otpRateLimiter,
  authRateLimiter,
  dashboardWriteRateLimiter,
  webhookRateLimiter,
  generalRateLimiter,
  getClientIp,
} from "./lib/rate-limit";
import {
  buildCanonicalLogLine,
  formatCanonicalLog,
  categorizeRequest,
} from "./lib/request-logger";
import { recordRequest } from "./lib/metrics";

const isDev = process.env.NODE_ENV === "development";
// CSP enforcement: enforced by default in production, report-only in development.
// Set CSP_ENFORCE=false to disable enforcement in production (e.g., during rollout).
// Set CSP_ENFORCE=true to enable enforcement in development (for testing).
// CSP_REPORT_ONLY_UNTIL=<ISO date> temporarily overrides to report-only mode.
const cspReportOnlyUntil = parseReportOnlyUntil(
  process.env.CSP_REPORT_ONLY_UNTIL,
);
const shouldEnforceCsp =
  !isWithinReportOnlyWindow(cspReportOnlyUntil) &&
  (isDev ? process.env.CSP_ENFORCE === "true" : process.env.CSP_ENFORCE !== "false");
const MAX_JSON_BODY_BYTES = 1 * 1024 * 1024; // 1MB max for JSON API requests
const MAX_RESUME_UPLOAD_BYTES = 12 * 1024 * 1024; // 12MB to cover form overhead above 10MB file cap
const MAX_CONTACT_UPLOAD_BYTES = 32 * 1024 * 1024; // Contact form: 3 files x 10MB + multipart overhead
const MULTIPART_UPLOAD_PATHS = ["/api/apply-job", "/api/contact/submit"];
const BLOCKED_PUBLIC_PATHS = new Set([
  "/test-auth",
  "/test-auth.html",
  "/pricing-redesign",
  "/pricing-redesign.html",
]);

function parseReportOnlyUntil(rawValue: string | undefined): number | null {
  if (!rawValue) return null;
  const parsed = Date.parse(rawValue);
  if (Number.isNaN(parsed)) {
    console.warn(
      "Invalid CSP_REPORT_ONLY_UNTIL value; falling back to default report-only behaviour",
    );
    return null;
  }
  return parsed;
}

function isWithinReportOnlyWindow(reportOnlyUntil: number | null): boolean {
  if (!reportOnlyUntil) return false;
  return Date.now() < reportOnlyUntil;
}

// Generate nonce using Web Crypto API (Edge runtime compatible)
function generateNonce(): string {
  const array = new Uint8Array(16);
  crypto.getRandomValues(array);
  // Convert Uint8Array to string without spread operator for compatibility
  let binary = "";
  for (let i = 0; i < array.length; i++) {
    binary += String.fromCharCode(array[i]);
  }
  return btoa(binary);
}

function buildCsp(nonce: string): string {
  return [
    "default-src 'self'",
    `script-src 'self' 'nonce-${nonce}'${isDev ? " 'unsafe-eval'" : ""} https://www.google.com https://www.gstatic.com https://js.stripe.com https://unpkg.com https://elevenlabs.io https://*.elevenlabs.io https://*.clerk.accounts.dev https://challenges.cloudflare.com`,
    `style-src 'self' 'nonce-${nonce}' https://fonts.googleapis.com`,
    "img-src 'self' data: https://js.stripe.com https://storage.googleapis.com https://elevenlabs.io https://*.elevenlabs.io https://*.clerk.accounts.dev https://img.clerk.com",
    "font-src 'self' https://fonts.gstatic.com",
    "frame-src 'self' https://www.google.com https://js.stripe.com https://hooks.stripe.com https://*.clerk.accounts.dev https://challenges.cloudflare.com",
    "connect-src 'self' https://*.convex.cloud https://*.convex.site https://*.clerk.accounts.dev https://*.clerk.com https://clerk.shared.global https://clerk-telemetry.com https://api.stripe.com https://api.elevenlabs.io https://api.us.elevenlabs.io https://elevenlabs.io https://*.elevenlabs.io wss://api.elevenlabs.io wss://api.us.elevenlabs.io wss://livekit.rtc.elevenlabs.io https://www.google.com https://www.gstatic.com",
    "worker-src 'self' blob: https://unpkg.com https://elevenlabs.io https://*.elevenlabs.io",
    "media-src 'self' blob: https://api.elevenlabs.io https://api.us.elevenlabs.io",
    "object-src 'none'",
    "base-uri 'self'",
    "form-action 'self'",
    "report-uri /api/csp-report",
  ].join("; ");
}

/**
 * Apply CSP and tracing headers to a NextResponse.
 */
function applyCspHeaders(
  response: NextResponse,
  nonce: string,
  correlationId: string,
  requestId: string,
): NextResponse {
  const csp = buildCsp(nonce);
  const headerName = shouldEnforceCsp
    ? "Content-Security-Policy"
    : "Content-Security-Policy-Report-Only";
  response.headers.set(headerName, csp);
  response.headers.set("x-nonce", nonce);
  response.headers.set("x-correlation-id", correlationId);
  response.headers.set("x-request-id", requestId);
  return response;
}

function getPreferredLanguage(req: NextRequest): Lang {
  // Get the Accept-Language header
  const acceptLanguage = req.headers.get('accept-language');

  if (acceptLanguage) {
    // Parse the Accept-Language header
    // Example: "ar-SA,ar;q=0.9,en-US;q=0.8,en;q=0.7"
    const languages = acceptLanguage
      .split(',')
      .map(lang => {
        const parts = lang.trim().split(';');
        const code = parts[0].split('-')[0]; // Get language code without region
        const quality = parts[1] ? parseFloat(parts[1].split('=')[1]) : 1;
        return { code, quality };
      })
      .sort((a, b) => b.quality - a.quality); // Sort by quality (preference)

    // Find the first supported language
    for (const lang of languages) {
      if (lang.code === 'ar') return 'ar';
      if (lang.code === 'en') return 'en';
    }
  }

  // Default to Arabic if no preference is detected
  return SITE.defaultLang;
}

/**
 * Generate a simple unique ID for correlation/request tracking.
 * Edge-compatible (no Node.js crypto required).
 */
function generateTraceId(): string {
  const array = new Uint8Array(12);
  crypto.getRandomValues(array);
  let hex = "";
  for (let i = 0; i < array.length; i++) {
    hex += array[i].toString(16).padStart(2, "0");
  }
  return hex;
}

/**
 * Emit a canonical log line and record metrics for a middleware response.
 * Runs in Edge runtime — uses console.log with structured JSON (no Pino).
 */
function emitCanonicalLog(
  req: NextRequest,
  statusCode: number,
  startTime: number,
  rateLimited: boolean,
) {
  const { pathname } = req.nextUrl;
  const line = buildCanonicalLogLine({
    method: req.method,
    pathname,
    statusCode,
    startTime,
    correlationId: req.headers.get('x-correlation-id') || '-',
    requestId: '-',
    clientIp: getClientIp(req),
    userAgent: req.headers.get('user-agent') || '',
    contentType: req.headers.get('content-type') || '',
    rateLimited,
  });

  // Structured JSON to stdout (picked up by Loki/log aggregator)
  console.log(formatCanonicalLog(line));

  // Record in-memory metrics for the staff dashboard
  recordRequest({
    method: req.method,
    category: categorizeRequest(pathname),
    statusCode,
    durationMs: line.durationMs,
  });
}

async function mawidiMiddlewareHandler(req: NextRequest, clerkAuth?: ClerkMiddlewareAuth) {
  try {
  const startTime = Date.now();
  const { pathname } = req.nextUrl;

  // Generate tracing IDs and nonce for this request
  const nonce = generateNonce();
  const correlationId = generateTraceId();
  const requestId = generateTraceId();

  // Build request headers with tracing context
  const requestHeaders = new Headers(req.headers);
  requestHeaders.set("x-nonce", nonce);
  requestHeaders.set("x-correlation-id", correlationId);
  requestHeaders.set("x-request-id", requestId);

  // FORCE HTTPS IN PRODUCTION (Prevent session hijacking)
  if (process.env.NODE_ENV === 'production') {
    const host = req.headers.get('host') || '';
    const isLocalhost = host.startsWith('localhost') || host.startsWith('127.0.0.1') || host.startsWith('0.0.0.0');

    if (!isLocalhost) {
      const protocol = req.headers.get('x-forwarded-proto');
      if (protocol !== 'https') {
        const httpsUrl = new URL(req.url);
        httpsUrl.protocol = 'https:';
        emitCanonicalLog(req, 308, startTime, false);
        return NextResponse.redirect(httpsUrl, 308);
      }
    }
  }

  // Fast path: Handle language routing first for non-API routes
  if (!pathname.startsWith('/api/')) {
    if (pathname === '/') {
      const preferredLang = getPreferredLanguage(req);
      emitCanonicalLog(req, 302, startTime, false);
      return NextResponse.redirect(new URL(`/${preferredLang}`, req.url));
    }

    if (pathname.startsWith('/staff/dashboard')) {
      emitCanonicalLog(req, 200, startTime, false);
      return applyCspHeaders(
        NextResponse.next({ request: { headers: requestHeaders } }),
        nonce,
        correlationId,
        requestId,
      );
    }

    // Staff login page - no language prefix
    if (pathname.startsWith('/staff/login')) {
      emitCanonicalLog(req, 200, startTime, false);
      return applyCspHeaders(
        NextResponse.next({ request: { headers: requestHeaders } }),
        nonce,
        correlationId,
        requestId,
      );
    }

    // Public quotation portal - no auth required (token-based access)
    if (pathname.startsWith("/quotation/")) {
      return applyCspHeaders(
        NextResponse.next({ request: { headers: requestHeaders } }),
        nonce,
        correlationId,
        requestId,
      );
    }

    // All other non-API routes (dashboard pages, public pages, etc.)
    return applyCspHeaders(
      NextResponse.next({ request: { headers: requestHeaders } }),
      nonce,
      correlationId,
      requestId,
    );
  }

  // ======================================================================
  // API routes: rate limiting
  // ======================================================================

  if (pathname.startsWith('/api/')) {
    const ip = getClientIp(req);

    // OTP endpoints - strictest rate limiting (5 per 15 minutes)
    if (pathname === '/api/send-otp' || pathname === '/api/auth/verify-signup-otp') {
      if (otpRateLimiter) {
        const { success, limit, reset } = await otpRateLimiter.limit(ip);

        if (!success) {
          const retryAfter = Math.ceil((reset - Date.now()) / 1000);
          emitCanonicalLog(req, 429, startTime, true);
          return NextResponse.json(
            { error: 'Too many requests. Please try again later.' },
            {
              status: 429,
              headers: {
                'X-RateLimit-Limit': limit.toString(),
                'X-RateLimit-Remaining': '0',
                'X-RateLimit-Reset': reset.toString(),
                'Retry-After': retryAfter.toString(),
              },
            }
          );
        }
      }
    }
    // Sensitive operations - stricter limit (10 per hour)
    else if (
      pathname === '/api/register' ||
      pathname === '/api/contact/submit' ||
      pathname === '/api/auth/forgot-password' ||
      pathname === '/api/auth/reset-password' ||
      pathname === '/api/demo/book'
    ) {
      if (authRateLimiter) {
        const { success, limit, reset } = await authRateLimiter.limit(ip);

        if (!success) {
          const retryAfter = Math.ceil((reset - Date.now()) / 1000);
          emitCanonicalLog(req, 429, startTime, true);
          return NextResponse.json(
            { error: 'Too many requests. Please try again later.' },
            {
              status: 429,
              headers: {
                'X-RateLimit-Limit': limit.toString(),
                'X-RateLimit-Remaining': '0',
                'X-RateLimit-Reset': reset.toString(),
                'Retry-After': retryAfter.toString(),
              },
            }
          );
        }
      }
    }
    // Auth endpoints - standard rate limiting (100 per hour)
    else if (
      pathname.startsWith("/api/auth/") &&
      !pathname.includes("/api/auth/session") &&
      !pathname.includes("/api/auth/csrf") &&
      !pathname.includes("/api/csrf") &&
      !pathname.includes("/api/auth/providers")
    ) {
      if (authRateLimiter) {
        const { success, limit, reset } = await authRateLimiter.limit(ip);

        if (!success) {
          const retryAfter = Math.ceil((reset - Date.now()) / 1000);
          emitCanonicalLog(req, 429, startTime, true);
          return NextResponse.json(
            { error: 'Too many requests. Please try again later.' },
            {
              status: 429,
              headers: {
                'X-RateLimit-Limit': limit.toString(),
                'X-RateLimit-Remaining': '0',
                'X-RateLimit-Reset': reset.toString(),
                'Retry-After': retryAfter.toString(),
              },
            }
          );
        }
      }
    }
    // Skip rate limiting entirely for high-frequency session endpoints
    else if (pathname === '/api/csrf' || pathname === '/api/auth/session') {
      // Called on every page load — must not be rate limited
    }
    // Skip middleware rate limiting for webhook POST deliveries that have unconditional route-level
    // rate limiters. Must come BEFORE the authenticated routes block so
    // /api/integrations/whatsapp/webhook isn't captured by the /api/integrations matcher.
    else if (
      req.method === 'POST' &&
      (pathname.startsWith('/api/webhooks/n8n/') || pathname.startsWith('/api/integrations/whatsapp/webhook'))
    ) {
      // These routes apply their own unconditional per-route rate limiting (defense-in-depth)
    }
    // Webhook routes without unconditional route-level limiters get a dedicated generous
    // middleware limiter (200 req/min) to handle legitimate bursts without blocking deliveries.
    else if (
      req.method === 'POST' &&
      (pathname.startsWith('/api/stripe/webhook') || pathname.startsWith('/api/webhooks/'))
    ) {
      const { success, limit, reset } = await webhookRateLimiter.limit(ip);
      if (!success) {
        const retryAfter = Math.ceil((reset - Date.now()) / 1000);
        emitCanonicalLog(req, 429, startTime, true);
        return NextResponse.json(
          { error: 'Too many requests. Please try again later.' },
          {
            status: 429,
            headers: {
              'X-RateLimit-Limit': limit.toString(),
              'X-RateLimit-Remaining': '0',
              'X-RateLimit-Reset': reset.toString(),
              'Retry-After': retryAfter.toString(),
            },
          }
        );
      }
    }
    // Public review links — customers clicking email links should never be rate-limited
    else if (pathname.startsWith('/api/public/review/')) {
      // Token-based access — the token itself is the auth; no aggressive rate limit needed
    }
    // Authenticated routes — these endpoints require auth (Clerk/NextAuth/staff session).
    // The auth check itself is the protection. Rate limiting authenticated reads causes
    // dashboard breakage at scale (8 API calls per page load × 1000 companies × auto-refresh).
    //
    // Strategy:
    // - Authenticated GET/HEAD: NO rate limit (auth is the gate)
    // - Authenticated writes: per-user rate limit (prevents abuse from compromised accounts)
    // - Unauthenticated (no session): strict IP-based limit (brute-force protection)
    else if (
      pathname.startsWith('/api/dashboard/') ||
      pathname.startsWith('/api/subscription/') ||
      pathname.startsWith('/api/subscriptions/') ||
      pathname.startsWith('/api/user/') ||
      pathname.startsWith('/api/organization/') ||
      pathname.startsWith('/api/staff/') ||
      pathname.startsWith('/api/admin/') ||
      pathname.startsWith('/api/support/') ||
      pathname.startsWith('/api/bookings') ||
      pathname.startsWith('/api/notifications') ||
      pathname.startsWith('/api/conversations') ||
      pathname.startsWith('/api/integrations') ||
      pathname.startsWith('/api/analytics/') ||
      pathname.startsWith('/api/settings/') ||
      pathname.startsWith('/api/billing/') ||
      pathname.startsWith('/api/leads') ||
      pathname.startsWith('/api/quotations') ||
      pathname.startsWith('/api/knowledge-base') ||
      pathname.startsWith('/api/onboarding') ||
      pathname.startsWith('/api/payment/')
    ) {
      // Check if user is authenticated
      let isAuthenticated = false;
      let identifier = ip;

      if (clerkAuth) {
        try {
          const authObj = await clerkAuth();
          if (authObj.userId) {
            isAuthenticated = true;
            identifier = `user:${authObj.userId}`;
          }
        } catch {
          // Clerk auth unavailable
        }
      }

      // Check session cookies as fallback
      if (!isAuthenticated) {
        const sessionCookie =
          req.cookies.get('__session')?.value ||
          req.cookies.get('next-auth.session-token')?.value ||
          req.cookies.get('__Secure-next-auth.session-token')?.value ||
          req.cookies.get('authjs.session-token')?.value ||
          req.cookies.get('__Secure-authjs.session-token')?.value ||
          req.cookies.get('auth_token')?.value ||
          req.cookies.get('staff-session')?.value;
        if (sessionCookie) {
          isAuthenticated = true;
          const msgBuf = new TextEncoder().encode(sessionCookie);
          const hashBuf = await crypto.subtle.digest('SHA-256', msgBuf);
          const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
          identifier = `session:${hashHex.slice(0, 16)}`;
        }
      }

      const method = req.method;

      if (isAuthenticated) {
        // Authenticated reads: NO rate limit — auth is the gate.
        // Scales to 1000+ companies without hitting limits.
        if (method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {
          // Writes only: per-user rate limiting (300/min) to prevent abuse
          const { success, limit, reset } = await dashboardWriteRateLimiter.limit(identifier);
          if (!success) {
            const retryAfter = Math.ceil((reset - Date.now()) / 1000);
            emitCanonicalLog(req, 429, startTime, true);
            return NextResponse.json(
              { error: 'Too many requests. Please try again later.' },
              {
                status: 429,
                headers: {
                  'X-RateLimit-Limit': limit.toString(),
                  'X-RateLimit-Remaining': '0',
                  'X-RateLimit-Reset': reset.toString(),
                  'Retry-After': retryAfter.toString(),
                },
              }
            );
          }
        }
        // Authenticated GETs pass through — no rate limit check needed
      } else {
        // Unauthenticated requests to authenticated routes: strict IP limit
        // These are likely probing/scanning — the route handler will return 401 anyway
        const { success, limit, reset } = await authRateLimiter.limit(ip);
        if (!success) {
          const retryAfter = Math.ceil((reset - Date.now()) / 1000);
          emitCanonicalLog(req, 429, startTime, true);
          return NextResponse.json(
            { error: 'Too many requests. Please try again later.' },
            {
              status: 429,
              headers: {
                'X-RateLimit-Limit': limit.toString(),
                'X-RateLimit-Remaining': '0',
                'X-RateLimit-Reset': reset.toString(),
                'Retry-After': retryAfter.toString(),
              },
            }
          );
        }
      }
    }
    // All other API endpoints - general rate limiting
    else if (generalRateLimiter) {
      const { success, limit, reset } = await generalRateLimiter.limit(ip);

      if (!success) {
        const retryAfter = Math.ceil((reset - Date.now()) / 1000);
        emitCanonicalLog(req, 429, startTime, true);
        return NextResponse.json(
          { error: 'Too many requests. Please try again later.' },
          {
            status: 429,
            headers: {
              'X-RateLimit-Limit': limit.toString(),
              'X-RateLimit-Remaining': '0',
              'X-RateLimit-Reset': reset.toString(),
              'Retry-After': retryAfter.toString(),
            },
          }
        );
      }
    }
  }

  emitCanonicalLog(req, 200, startTime, false);
  return NextResponse.next();
  } catch (error) {
    console.error("[Middleware] Unhandled error:", error instanceof Error ? error.message : error);
    return NextResponse.next(); // Don't block requests on middleware errors
  }
}

/**
 * Compose Clerk middleware with existing Mawidi middleware.
 * Clerk injects auth context (for DS_AUTH=convex routes) without
 * enforcing authentication — all existing CSP / rate-limiting /
 * i18n logic inside mawidiMiddlewareHandler is preserved.
 *
 * When NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is not set, clerkMiddleware
 * runs in keyless/passthrough mode and adds no overhead.
 */
export default clerkMiddleware(
  (clerkAuth, req) => mawidiMiddlewareHandler(req, clerkAuth),
);

export const config = {
  // Run middleware on all routes except static assets
  matcher: [
    /*
     * Match all request paths except:
     * - _next/static (static files)
     * - _next/image (image optimization)
     * - favicon.ico
     * - public folder files
     */
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico|woff|woff2|ttf|eot)$).*)',
  ],
};
