import { NextRequest, NextResponse } from "next/server";
import { handleCors } from "@/lib/http/cors";
import { blockSensitivePath } from "@/lib/http/sensitivePaths";
import { CSRF_COOKIE, csrfCookieOptions, issueCsrfToken } from "@/lib/auth/csrf";
import { SESSION_COOKIE } from "@/lib/auth/config";
import { siteUrl } from "@/lib/seo/site";
import { decideSeoLocalePath } from "@/lib/seo/slugs";
import { decideSubdomain, isAdminPath, isDashboardPath } from "@/lib/routing/subdomain";

/**
 * Middleware for subdomain-based routing and locale-aware landing pages.
 *
 * 1. Requests on `app.<domain>` are internally rewritten to the `/dashboard`
 *    route group so the dashboard runs on its own CNAME without an extra
 *    deployment or server. Only the dashboard surface and its API are served
 *    there — auth pages, the admin and the legal pages are redirected to the
 *    main host (the Google sign-in return cookie still brings the user back
 *    to `app.*` after a sign-in started there). Any other path is treated as
 *    a dashboard sub-route.
 * 2. Requests on `admin.<domain>` are internally rewritten to the `/admin`
 *    route group so that Next.js serves the admin dashboard without an extra
 *    deployment or server.
 * 3. Requests to the bare root `/` (or any other host) are redirected to the
 *    localized landing page (`/de` or `/en`) based on the visitor's
 *    `Accept-Language` header so both language versions get their own
 *    indexable URL.
 *
 * Additionally, every page response carries the CSRF double-submit cookie
 * (`sr_csrf`) when the request did not already have one, so the token exists
 * in the browser before any page/form/API call needs it (see
 * `src/lib/auth/csrf.ts`).
 *
 * Everything else passes through untouched.
 */

/**
 * Ensures the `sr_csrf` double-submit cookie reaches the browser on this
 * response. Only sets it when the request had none, so the token stays stable
 * for the lifetime of the cookie and is never rotated mid-session.
 */
function withCsrfCookie(response: NextResponse, request: NextRequest): NextResponse {
  if (!request.cookies.get(CSRF_COOKIE)) {
    response.cookies.set(CSRF_COOKIE, issueCsrfToken(), csrfCookieOptions());
  }
  return response;
}

/**
 * Edge-level defense-in-depth for the admin surface. This is NOT the admin
 * check — it cannot be, since real admin status is a DB-backed email lookup
 * (`getAdminUser()`, Edge/`pg` cannot reach the database) and that check
 * still runs, authoritatively, in `admin/layout.tsx` and independently in
 * every `/api/admin/*` route handler. This only rejects requests carrying NO
 * session cookie at all before they reach any page/route code, so a future
 * admin route that forgets to call `getAdminUser()` still cannot be hit by a
 * fully anonymous request. An authenticated non-admin still passes this
 * check and is correctly rejected downstream by the real check.
 */
function blockAnonymousAdminAccess(
  request: NextRequest,
  effectivePathname: string
): NextResponse | undefined {
  if (!isAdminPath(effectivePathname)) return undefined;
  if (request.cookies.get(SESSION_COOKIE)?.value) return undefined;

  if (effectivePathname.startsWith("/api/admin")) {
    return NextResponse.json({ error: "unauthorized" }, { status: 401 });
  }
  // Redirect to the login page on the site's main host, never on the app./admin.
  // subdomain itself — the middleware rewrites `admin.<domain>/auth/*` away from
  // the real auth routes (→ /admin/auth/* = 404), so redirecting via request.url
  // would bounce between two redirects forever. The main-host login also matches
  // where the OAuth callback (redirect_uri = siteUrl) actually completes the
  // sign-in, so the user ends up where the session cookie gets written.
  return withCsrfCookie(NextResponse.redirect(new URL("/auth/login", siteUrl), 307), request);
}

/**
 * Dashboard requires a session. Login itself already refuses unverified
 * local accounts, so a session cookie means a verified user. Unsigned
 * visitors are sent to /auth/login instead of an empty guest scanner.
 */
function blockAnonymousDashboardAccess(
  request: NextRequest,
  effectivePathname: string
): NextResponse | undefined {
  if (!isDashboardPath(effectivePathname)) return undefined;
  if (request.cookies.get(SESSION_COOKIE)?.value) return undefined;

  const login = new URL("/auth/login", siteUrl);
  return withCsrfCookie(NextResponse.redirect(login, 307), request);
}

export function middleware(request: NextRequest) {
  const hostname = request.headers.get("host") || "";
  const { pathname } = request.nextUrl;

  // [TASK-3-HOOK] Sensitive-path / directory-listing hardening: 404-block
  // dotfiles/dot-dirs, node_modules / .next(.next-corrupt-*) / drizzle /
  // scripts, and sensitive filenames/extensions (see sensitivePaths.ts).
  // Runs before the CORS hook below. Must be Edge-runtime compatible.
  const sensitive = blockSensitivePath(request);
  if (sensitive) return sensitive;
  // [TASK-2-HOOK] CORS policy handling goes here. Enforce the origin allowlist
  // and answer preflight OPTIONS requests; return undefined to continue for
  // same-origin / allowed-origin non-preflight requests. Must be Edge-runtime
  // compatible (no Node.js APIs).
  const corsResponse = handleCors(request);
  if (corsResponse) return corsResponse;

  // Subdomain routing. The pure decision lives in src/lib/routing/subdomain.ts
  // (unit-tested); here the decision is applied with the CSRF cookie and the
  // anonymous-admin guard. admin.* → /admin route group (API + dashboard pass
  // through so the admin UI keeps working), app.* → dashboard route group.
  const decision = decideSubdomain(hostname, pathname);

  if (decision.kind === "rewrite") {
    const anonymousBlock = blockAnonymousAdminAccess(request, decision.pathname);
    if (anonymousBlock) return anonymousBlock;
    const dashboardBlock = blockAnonymousDashboardAccess(request, decision.pathname);
    if (dashboardBlock) return dashboardBlock;
    const url = request.nextUrl.clone();
    url.pathname = decision.pathname;
    return withCsrfCookie(NextResponse.rewrite(url), request);
  }

  if (decision.kind === "redirect") {
    // Main-host-only content started on app.* — hand it back to the bare
    // domain, keeping path + query string.
    const target = new URL(decision.pathname, siteUrl);
    target.search = request.nextUrl.search;
    return withCsrfCookie(NextResponse.redirect(target, 307), request);
  }

  // "next" (routed subdomain serving its own paths) and "none" (other hosts)
  // both just pass through; /api/admin stays anonymous-gated everywhere.
  const anonymousBlock = blockAnonymousAdminAccess(request, pathname);
  if (anonymousBlock) return anonymousBlock;
  const dashboardBlock = blockAnonymousDashboardAccess(request, pathname);
  if (dashboardBlock) return dashboardBlock;

  // Root redirect to the localized landing page (/en by default).
  if (pathname === "/") {
    return withCsrfCookie(NextResponse.redirect(new URL("/en", request.url), 307), request);
  }

  // Locale-specific SEO slugs: German keyword URLs rewrite onto the shared
  // App Router folders; English slugs under /de/ (and the reverse) 301 to
  // the matching public slug so canonicals and hreflang stay unique.
  const seoDecision = decideSeoLocalePath(pathname);
  if (seoDecision.kind === "redirect") {
    const target = request.nextUrl.clone();
    target.pathname = seoDecision.pathname;
    return withCsrfCookie(NextResponse.redirect(target, 308), request);
  }
  if (seoDecision.kind === "rewrite") {
    const url = request.nextUrl.clone();
    url.pathname = seoDecision.pathname;
    return withCsrfCookie(NextResponse.rewrite(url), request);
  }

  return withCsrfCookie(NextResponse.next(), request);
}

export const config = {
  // Run on all routes except static files and Next.js internals
  matcher: ["/((?!_next/static|_next/image|favicon.ico|icon.png|apple-icon.png|demo/).*)"],
};
