/**
 * audit3-followup (2026-05-29) — single-helper auth gate for API routes.
 *
 * Wraps a Next.js App Router route handler with the standard auth
 * check, role enforcement, and NOC-session fallback. Replaces the
 * copy-pasted `const session = await getServerSession(authOptions);
 * if (!session) return NextResponse.json(...)` boilerplate at the top
 * of ~75 route files.
 *
 * Goal: make it structurally impossible to ship a new authenticated
 * route without an explicit auth statement. The companion smoke
 * (`audit3-withauth-coverage.ts`) asserts every route.ts either uses
 * `withAuth` or is on the documented public-route allowlist.
 *
 * Usage:
 *
 *   export const POST = withAuth(
 *     { requireRole: ['ADMIN', 'EDITOR'] },
 *     async (req, ctx) => {
 *       // ctx.userId, ctx.role guaranteed; otherwise 401 returned.
 *       const body = await req.json();
 *       // ...
 *       return NextResponse.json({ ok: true });
 *     }
 *   );
 *
 *   // With dynamic-segment routes:
 *   export const PATCH = withAuth(
 *     { requireRole: 'ADMIN' },
 *     async (req, ctx, routeCtx: { params: Promise<{ id: string }> }) => {
 *       const { id } = await routeCtx.params;
 *       // ...
 *     }
 *   );
 *
 *   // Open to any authenticated user:
 *   export const GET = withAuth({}, async (req, ctx) => {
 *     return NextResponse.json({ userId: ctx.userId });
 *   });
 *
 * Returns:
 *   - 401 { error: 'Unauthenticated' } when no session
 *   - 403 { error: 'Forbidden' } when role check fails
 *   - whatever the handler returns on success
 */
import { NextRequest, NextResponse } from 'next/server';
import { requireAuthContext } from '@/lib/authorization/session-context';
import { AuthContext, AuthorizationError, Role } from '@/lib/authorization/types';

export interface WithAuthOptions {
    /**
     * Roles allowed to access the route. Omit for "any authenticated
     * user." Accepts a single role or an array.
     */
    requireRole?: Role | Role[];
}

type RouteHandler<TRouteCtx = unknown> = (
    req: NextRequest,
    authCtx: AuthContext,
    routeCtx: TRouteCtx,
) => Promise<Response> | Response;

export function withAuth<TRouteCtx = unknown>(
    options: WithAuthOptions,
    handler: RouteHandler<TRouteCtx>,
): (req: NextRequest, routeCtx: TRouteCtx) => Promise<Response> {
    return async (req, routeCtx) => {
        let ctx: AuthContext;
        try {
            ctx = await requireAuthContext();
        } catch (err) {
            if (err instanceof AuthorizationError) {
                return NextResponse.json({ error: err.message }, { status: err.status });
            }
            // Unexpected — keep response shape predictable to clients.
            return NextResponse.json({ error: 'Unauthenticated' }, { status: 401 });
        }

        if (options.requireRole) {
            const allowedRoles: ReadonlyArray<Role> = Array.isArray(options.requireRole)
                ? options.requireRole
                : [options.requireRole];
            if (!allowedRoles.includes(ctx.role)) {
                return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
            }
        }

        return handler(req, ctx, routeCtx);
    };
}
