import { NextResponse, type NextRequest } from "next/server"
import { getSessionCookie } from "better-auth/cookies"

const PROTECTED_PATHS = [
  "/admin",
  "/dashboard",
  "/properties",
  "/tenants",
  "/rent",
  "/maintenance",
  "/leases",
  "/expenses",
  "/settings",
  "/onboarding",
  "/calendar",
  "/inspections",
  "/vendors",
  "/reports",
  "/activity",
  "/ai",
  "/ai-dashboard",
  "/predictions",
  "/recommendations",
  "/impact",
  "/follow-ups",
  "/team",
]

const AUTH_PATHS = ["/login", "/signup", "/forgot-password"]

// Origin of the Sentry ingest endpoint, derived from the public DSN so the
// CSP stays in sync with whatever project/region the DSN points at. Returns
// null when Sentry is not configured.
function sentryIngestOrigin(): string | null {
  const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN
  if (!dsn) return null
  try {
    return new URL(dsn).origin
  } catch {
    return null
  }
}

// Build the Content-Security-Policy. `script-src` uses 'unsafe-inline' because
// Next.js 16's Turbopack build does NOT stamp a per-request nonce onto its
// inline hydration scripts (`self.__next_f.push(...)`). A nonce-based policy
// therefore blocks those inline scripts and the app never hydrates (blank page).
// `style-src` also keeps 'unsafe-inline' (Radix / Tailwind / framer-motion inject
// inline styles). NOTE: to restore the stricter nonce-based script policy, build
// with webpack (`next build --webpack`) so Next applies the nonce to its scripts.
function buildCsp(): string {
  const isDev = process.env.NODE_ENV !== "production"
  const sentry = sentryIngestOrigin()

  // Dev additionally needs 'unsafe-eval' (Turbopack HMR) plus a dev websocket
  // (added to connect-src below).
  const scriptSrc = isDev
    ? `script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com`
    : `script-src 'self' 'unsafe-inline' https://challenges.cloudflare.com`
  const connectSrc = [
    "connect-src 'self'",
    isDev ? "ws: wss:" : "",
    "https://api.stripe.com https://api.openai.com https://challenges.cloudflare.com",
    sentry ?? "",
  ]
    .filter(Boolean)
    .join(" ")

  return [
    "default-src 'self'",
    "img-src 'self' data: blob: https:",
    "style-src 'self' 'unsafe-inline'",
    scriptSrc,
    "font-src 'self' data:",
    connectSrc,
    // Sentry Session Replay spins up its compression worker from a blob: URL;
    // without worker-src the browser falls back to script-src and blocks it.
    "worker-src 'self' blob:",
    "frame-src https://js.stripe.com https://hooks.stripe.com https://challenges.cloudflare.com",
    "frame-ancestors 'none'",
    "base-uri 'self'",
    "form-action 'self'",
    "object-src 'none'",
  ].join("; ")
}

// Cookie presence is only a hint (cheap, no DB). Before bouncing a visitor off
// an auth page we confirm the session is actually alive — otherwise a stale
// cookie loops forever: /dashboard → /login (server sees no session) →
// /dashboard (proxy sees a cookie) → … until ERR_TOO_MANY_REDIRECTS.
// "unknown" (auth service unreachable / rate-limited) renders the auth page
// without touching cookies, which is safe in both directions.
async function sessionState(request: NextRequest): Promise<"valid" | "invalid" | "unknown"> {
  try {
    const base = process.env.BETTER_AUTH_URL ?? request.nextUrl.origin
    const res = await fetch(new URL("/api/auth/get-session", base), {
      headers: { cookie: request.headers.get("cookie") ?? "" },
      cache: "no-store",
    })
    if (!res.ok) return "unknown"
    // Better Auth returns JSON `null` when the session is missing or revoked.
    const session = await res.json()
    return session ? "valid" : "invalid"
  } catch {
    return "unknown"
  }
}

export async function proxy(request: NextRequest) {
  const pathname = request.nextUrl.pathname

  // Optimistic check based on the presence of the session cookie. Real
  // enforcement happens in routes / server components via getSessionUser().
  const sessionCookie = getSessionCookie(request)

  const isProtected = PROTECTED_PATHS.some((p) => pathname.startsWith(p))
  if (isProtected && !sessionCookie) {
    const url = request.nextUrl.clone()
    url.pathname = "/login"
    return NextResponse.redirect(url)
  }

  const isAuthPage = AUTH_PATHS.some((p) => pathname.startsWith(p))
  let dropStaleSessionCookie = false
  if (isAuthPage && sessionCookie) {
    const state = await sessionState(request)
    if (state === "valid") {
      const url = request.nextUrl.clone()
      url.pathname = "/dashboard"
      return NextResponse.redirect(url)
    }
    // Dead cookie (session revoked or expired): render the auth page and drop
    // the cookie below so protected paths stop treating this visitor as
    // signed in. On "unknown", render the page but keep the cookie.
    dropStaleSessionCookie = state === "invalid"
  }

  const csp = buildCsp()

  const response = NextResponse.next()
  // Set the CSP on the outgoing response so the browser enforces it.
  response.headers.set("Content-Security-Policy", csp)

  if (dropStaleSessionCookie) {
    // Covers both the plain and __Secure-prefixed Better Auth cookie names.
    for (const cookie of request.cookies.getAll()) {
      if (!cookie.name.includes("better-auth.session_token")) continue
      response.cookies.set(cookie.name, "", {
        maxAge: 0,
        path: "/",
        httpOnly: true,
        sameSite: "lax",
        secure: cookie.name.startsWith("__Secure-"),
      })
    }
  }

  return response
}

export const config = {
  matcher: [
    "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
  ],
}
