import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

/**
 * Paths that do not require an authenticated session.
 * Checked with startsWith, so /auth/ covers /auth/callback etc.
 */
const PUBLIC_PATHS: string[] = [
  '/login',
  '/reset-password',
  '/set-password',
  '/auth/',
  '/api/auth/',
  '/api/public/',
  '/api/health',
  '/api/webhooks/',
  '/scan',
  '/portal/',
  '/api/portal/',
  // Token-gated email-change endpoints. The confirm/cancel links land in
  // a fresh browser (the user may not be signed in on this device), so
  // they need to bypass the session 401 gate. The endpoints validate a
  // signed sha256-hashed token instead — that's the auth.
  '/api/v1/me/email/confirm/',
  '/api/v1/me/email/cancel/',
];

function isPublicPath(pathname: string): boolean {
  // Per-port PWA manifests sit under `/<portSlug>/scan/manifest.webmanifest`
  // and need to be fetchable without a session - browsers fetch them eagerly
  // during install / first paint. The manifest only contains port name +
  // icon paths, no sensitive data, so making it public is safe.
  if (pathname.endsWith('/scan/manifest.webmanifest')) return true;
  return PUBLIC_PATHS.some((prefix) => pathname === prefix || pathname.startsWith(prefix));
}

function isApiRoute(pathname: string): boolean {
  return pathname.startsWith('/api/');
}

const STATE_CHANGING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);

/**
 * SameSite=Lax cookies block top-level cross-site POSTs in modern browsers,
 * but defense-in-depth: every state-changing request to a session-authed
 * `/api/v1/**` endpoint must originate from the same origin as the app.
 * Webhooks (`/api/webhooks/**`) and public posts (`/api/public/**`) are
 * exempt because they're called by external systems with no session
 * cookie. Auth flows (`/api/auth/**`) and portal (`/api/portal/**`) handle
 * their own origin/CSRF checks via better-auth.
 */
function isOriginCheckedPath(pathname: string): boolean {
  if (!pathname.startsWith('/api/v1/')) return false;
  return true;
}

function originAllowed(request: NextRequest): boolean {
  const origin = request.headers.get('origin');
  const referer = request.headers.get('referer');
  // Same-origin fetch from the app sends both Origin AND a matching host.
  // Use request.nextUrl.origin (the deployed origin) as the source of truth.
  const expectedOrigin = request.nextUrl.origin;
  if (origin) return origin === expectedOrigin;
  if (referer) {
    try {
      return new URL(referer).origin === expectedOrigin;
    } catch {
      return false;
    }
  }
  // Neither header present: most browser fetches always send Origin on
  // POST/PUT/PATCH/DELETE, so this likely means a same-origin server-side
  // call (e.g. Next.js internal fetch). Allow.
  return true;
}

export function proxy(request: NextRequest): NextResponse {
  const { pathname } = request.nextUrl;

  // CSRF defense-in-depth: state-changing requests to authed /api/v1
  // endpoints must come from the app's own origin. Skipped in dev so
  // LAN testing (e.g. real iPhone hitting the Mac via 192.168.x.x while
  // a Mac browser tab is loaded from localhost) doesn't trip on the
  // origin mismatch. Production keeps the check.
  if (
    process.env.NODE_ENV !== 'development' &&
    STATE_CHANGING_METHODS.has(request.method) &&
    isOriginCheckedPath(pathname) &&
    !originAllowed(request)
  ) {
    return NextResponse.json(
      { error: 'Cross-origin state-changing request rejected' },
      { status: 403 },
    );
  }

  // Always allow public paths through
  if (isPublicPath(pathname)) {
    return NextResponse.next();
  }

  const sessionToken = request.cookies.get('pn-crm.session_token');

  if (!sessionToken?.value) {
    if (isApiRoute(pathname)) {
      // API routes return 401 JSON - never redirect
      return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
    }

    // Page routes redirect to /login, preserving the intended destination
    const loginUrl = new URL('/login', request.url);
    loginUrl.searchParams.set('redirect', pathname + request.nextUrl.search);
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.next();
}

export const config = {
  matcher: [
    /*
     * Match all request paths except:
     * - _next/static     (static files)
     * - _next/image      (Next.js image optimisation)
     * - favicon.ico      (browser tab icon)
     * - /images/         (public image assets)
     * - manifest.json    (PWA manifest — must be unauthed for installability)
     * - icon-*.png       (PWA + apple-touch icons referenced by manifest)
     * - apple-touch-icon (iOS home-screen icon)
     */
    '/((?!_next/static|_next/image|favicon\\.ico|images/|manifest\\.json|icon-|apple-touch-icon).*)',
  ],
};
