import { NextResponse } from "next/server";
import { auth } from "./server/auth-edge.js";

// UI routes that are accessible without a session (login page, reset flow,
// public invite acceptance). All other UI routes redirect unauthenticated
// visitors to /auth/signin.
const PUBLIC_UI_PREFIXES = ["/auth/", "/invite/"];

// API allowlist — only routes listed here are served. Everything else under
// `/api/*` returns 404. Each allowlisted route MUST perform its own
// authentication (session check via auth(), CRON_SECRET bearer header, etc.)
// because the edge middleware cannot do Node-only work like Prisma queries.
// Prefix entries must end with `/`; exact entries match only the literal
// pathname. A new /api route therefore requires a deliberate allowlist edit,
// preventing accidental default-public exposure (security ticket #44).
export const SELF_AUTH_API_PREFIXES = [
  "/api/auth/",
  "/api/trpc/",
  "/api/sse/",
  "/api/cron/",
  "/api/reports/",
];

export const SELF_AUTH_API_EXACT = ["/api/health", "/api/ready", "/api/perf"];

export function isApiAllowlisted(pathname: string): boolean {
  if (SELF_AUTH_API_EXACT.includes(pathname)) return true;
  return SELF_AUTH_API_PREFIXES.some((p) => pathname.startsWith(p));
}

function isPublicUiPath(pathname: string): boolean {
  return PUBLIC_UI_PREFIXES.some((prefix) => pathname.startsWith(prefix));
}

// Browser-side code never talks to AI providers directly — every OpenAI /
// Azure / Gemini call goes through a server tRPC route. Therefore connect-src
// is locked to 'self' with no wildcards (ticket #45). If a future feature
// needs a browser-originated cross-origin request, add it explicitly here.
function buildCsp(nonce: string, isProd: boolean): string {
  const scriptSrc = isProd ? `'self' 'nonce-${nonce}'` : `'self' 'unsafe-eval' 'unsafe-inline'`;

  const imgSrc = isProd ? "'self' data: blob:" : "'self' data: blob: https:";

  return [
    "default-src 'self'",
    `script-src ${scriptSrc}`,
    // style-src keeps 'unsafe-inline' because React inlines styles from
    // component-scoped CSS and @react-pdf/renderer emits inline style blocks.
    // A nonce-based style-src-elem breaks both. This is an accepted residual
    // risk documented in docs/security-architecture.md §5.
    "style-src 'self' 'unsafe-inline'",
    `img-src ${imgSrc}`,
    "font-src 'self' data:",
    "connect-src 'self'",
    "frame-ancestors 'none'",
    "frame-src 'none'",
    "object-src 'none'",
    "media-src 'self'",
    "worker-src 'self' blob:",
    "base-uri 'self'",
    "form-action 'self'",
  ].join("; ");
}

export default auth(function middleware(request) {
  const { pathname } = request.nextUrl;

  // /api/* — default-deny. Only allowlisted routes pass; everything else 404s.
  // Allowlisted routes are responsible for their own auth check (they are
  // reached in the route handler, not here, because edge middleware cannot do
  // Prisma queries).
  if (pathname.startsWith("/api/")) {
    if (!isApiAllowlisted(pathname)) {
      return NextResponse.json({ error: "Not Found" }, { status: 404 });
    }
    // fall through — continue to add CSP headers
  } else if (!isPublicUiPath(pathname) && !request.auth) {
    // UI route requires a session. Redirect to signin.
    const signInUrl = new URL("/auth/signin", request.url);
    signInUrl.searchParams.set("callbackUrl", request.url);
    return NextResponse.redirect(signInUrl);
  }

  // Generate a cryptographically random nonce for this request
  const nonceBytes = new Uint8Array(16);
  crypto.getRandomValues(nonceBytes);
  const nonce = btoa(String.fromCharCode(...nonceBytes));

  const isProd = process.env.NODE_ENV === "production";
  const csp = buildCsp(nonce, isProd);

  // Forward nonce to server components via request header
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-nonce", nonce);
  requestHeaders.set("Content-Security-Policy", csp);

  const response = NextResponse.next({ request: { headers: requestHeaders } });
  response.headers.set("Content-Security-Policy", csp);

  return response;
});

export const config = {
  matcher: [
    // Apply to all routes except Next.js internals and static assets
    "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
  ],
};
