import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
import {
  contactLimiter,
  loginLimiter,
  uploadLimiter,
  getClientIp,
} from "@/server/http/rateLimit";

const MUTATING_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
const NEXTAUTH_SECRET = process.env.NEXTAUTH_SECRET;
const isDev = process.env.NODE_ENV === "development";

// ── CSP builder ──────────────────────────────────────────────────────────────

function buildCsp(nonce: string): string {
  return [
    "default-src 'self'",
    "base-uri 'self'",
    "object-src 'none'",
    "frame-ancestors 'self'",
    "form-action 'self'",
    "img-src 'self' data: https:",
    `script-src 'self' 'nonce-${nonce}' ${isDev ? "'unsafe-eval'" : ""} https://www.googletagmanager.com https://www.google-analytics.com https://www.google.com https://www.gstatic.com`,
    "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
    "font-src 'self' data: https:",
    "connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://www.googletagmanager.com",
    "frame-src 'self' https://www.google.com https://www.gstatic.com",
  ]
    .filter(Boolean)
    .join("; ");
}

// ── Route helpers ────────────────────────────────────────────────────────────

function isAlwaysProtectedApi(pathname: string): boolean {
  if (pathname === "/api/email/settings") return true;
  if (pathname === "/api/seo") return true;
  if (pathname.startsWith("/api/seo/")) return true;
  return false;
}

function isPublicApiMutation(pathname: string): boolean {
  if (pathname === "/api/contact") return true;
  if (pathname.startsWith("/api/auth")) return true;
  return false;
}

function isAdminPage(pathname: string): boolean {
  if (pathname === "/seo") return true;
  if (pathname.startsWith("/admin")) return true;
  return false;
}

// ── Rate-limit helper ────────────────────────────────────────────────────────

function rateLimitedResponse() {
  return NextResponse.json(
    { error: "Too many requests. Please try again later." },
    { status: 429, headers: { "Retry-After": "60" } },
  );
}

// ── CSRF helper ──────────────────────────────────────────────────────────────

function isValidCsrfHeader(req: NextRequest): boolean {
  return req.headers.get("x-requested-with") === "XMLHttpRequest";
}

// ── Main middleware ──────────────────────────────────────────────────────────

export async function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl;

  // Allow Next internal assets (static files, images, etc.)
  if (pathname.startsWith("/_next")) return NextResponse.next();

  // ── Generate CSP nonce ──────────────────────────────────────────────────
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");

  // ── Rate limiting (before auth, so attackers still get blocked) ──────────
  if (pathname.startsWith("/api")) {
    const ip = getClientIp(req);

    if (pathname === "/api/contact" && req.method === "POST") {
      if (!contactLimiter.check(ip)) return rateLimitedResponse();
    }

    if (pathname.startsWith("/api/auth") && req.method === "POST") {
      if (!loginLimiter.check(ip)) return rateLimitedResponse();
    }

    if (pathname === "/api/upload" && req.method === "POST") {
      if (!uploadLimiter.check(ip)) return rateLimitedResponse();
    }
  }

  // ── CSRF protection for API mutations ───────────────────────────────────
  if (pathname.startsWith("/api") && MUTATING_METHODS.has(req.method)) {
    const isNextAuthCallback = pathname.startsWith("/api/auth");
    if (!isNextAuthCallback && !isValidCsrfHeader(req)) {
      return NextResponse.json(
        { error: "Missing or invalid CSRF header" },
        { status: 403 },
      );
    }
  }

  // ── Admin pages: require session except login ───────────────────────────
  if (isAdminPage(pathname)) {
    if (pathname === "/admin/login") {
      return applySecurityHeaders(req, nonce);
    }

    if (!NEXTAUTH_SECRET) {
      const url = req.nextUrl.clone();
      url.pathname = "/admin/login";
      url.searchParams.set("callbackUrl", req.nextUrl.pathname + req.nextUrl.search);
      return NextResponse.redirect(url);
    }

    const token = await getToken({ req, secret: NEXTAUTH_SECRET });
    if (!token || (token as any).role !== "admin") {
      const url = req.nextUrl.clone();
      url.pathname = "/admin/login";
      url.searchParams.set("callbackUrl", req.nextUrl.pathname + req.nextUrl.search);
      return NextResponse.redirect(url);
    }

    return applySecurityHeaders(req, nonce);
  }

  // ── API mutations: require session except public whitelist ──────────────
  if (pathname.startsWith("/api") && MUTATING_METHODS.has(req.method)) {
    if (isPublicApiMutation(pathname)) {
      return applySecurityHeaders(req, nonce);
    }

    if (!NEXTAUTH_SECRET) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const token = await getToken({ req, secret: NEXTAUTH_SECRET });
    if (!token || (token as any).role !== "admin") {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
  }

  // ── Sensitive API reads ────────────────────────────────────────────────
  if (pathname.startsWith("/api") && req.method === "GET" && isAlwaysProtectedApi(pathname)) {
    if (!NEXTAUTH_SECRET) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const token = await getToken({ req, secret: NEXTAUTH_SECRET });
    if (!token || (token as any).role !== "admin") {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
  }

  return applySecurityHeaders(req, nonce);
}

/**
 * Applies the CSP header (with nonce) and passes the nonce to the page
 * via a request header so the layout can read it with `headers()`.
 */
function applySecurityHeaders(req: NextRequest, nonce: string): NextResponse {
  const requestHeaders = new Headers(req.headers);
  requestHeaders.set("x-nonce", nonce);

  const response = NextResponse.next({
    request: { headers: requestHeaders },
  });

  response.headers.set("Content-Security-Policy", buildCsp(nonce));

  return response;
}

export const config = {
  matcher: [
    /*
     * Match all request paths except static files:
     *   - _next/static (static JS/CSS)
     *   - _next/image  (image optimisation)
     *   - favicon.ico, sitemap, robots, manifest, images
     */
    "/((?!_next/static|_next/image|favicon\\.ico|sitemap.*\\.xml|robots\\.txt|site\\.webmanifest|images/).*)",
  ],
};
