/**
 * `withAuth` — Higher-Order-Function for Next.js API routes that injects a
 * validated UserContext.
 *
 * Usage:
 *
 *   import { withAuth } from "@/core/auth";
 *
 *   export const POST = withAuth(async (req, ctx) => {
 *     // ctx is a UserContext — userId, workspaceIds, permissions
 *     return NextResponse.json({ ok: true, user: ctx.userId });
 *   });
 *
 * Optional: require a specific permission or workspace-id.
 *
 *   export const POST = withAuth(async (req, ctx) => { ... },
 *     { requirePermission: "email:send", requireWorkspaceId: req.query.workspaceId });
 */

import { NextRequest, NextResponse } from 'next/server';
import type { UserContext } from '@/core/types';
import { verifyJwt, JwtError } from './jwt';
import { extractToken } from './cookies';

/**
 * Generic over the framework "second argument" Next.js passes to route
 * handlers. Static routes use `{ params: Promise<{}> }`; dynamic routes
 * narrow it to e.g. `{ params: Promise<{ id: string }> }`. We pick the
 * loose default so the wrapper is assignable to Next.js's RouteHandler type
 * even when callers omit the generic.
 */
export type AuthedHandler<RouteContext = { params: Promise<Record<string, string>> }> = (
  req: NextRequest,
  ctx: UserContext,
  routeCtx: RouteContext,
) => Promise<NextResponse> | NextResponse;

export interface AuthOptions {
  /** Require the user to have a specific permission string. Sends 403 if missing. */
  requirePermission?: string;
  /** Require the request to be scoped to a workspace the user can access. */
  requireWorkspaceId?: string | (() => string | undefined);
  /**
   * Allow the route even when the user is not authenticated. UserContext will
   * be a stub with empty workspaceIds. Useful for tracking pixels / webhooks.
   * Default: false.
   */
  allowAnonymous?: boolean;
}

function unauthorized(message: string, kind?: string): NextResponse {
  return NextResponse.json({ error: message, errorKind: kind }, { status: 401 });
}

function forbidden(message: string): NextResponse {
  return NextResponse.json({ error: message }, { status: 403 });
}

export function withAuth<
  RouteContext = { params: Promise<Record<string, string>> },
>(handler: AuthedHandler<RouteContext>, opts: AuthOptions = {}) {
  return async (req: NextRequest, routeCtx: RouteContext): Promise<NextResponse> => {
    const token = extractToken(req);

    if (!token) {
      if (opts.allowAnonymous) {
        return handler(
          req,
          { userId: 'anonymous', workspaceIds: [], permissions: [] },
          routeCtx,
        );
      }
      return unauthorized('Missing or empty Authorization / session cookie.', 'no-token');
    }

    let ctx: UserContext;
    try {
      ctx = await verifyJwt(token);
    } catch (err) {
      if (err instanceof JwtError) {
        const status = err.kind === 'missing-secret' ? 500 : 401;
        return NextResponse.json(
          { error: err.message, errorKind: err.kind },
          { status },
        );
      }
      throw err;
    }

    if (opts.requirePermission && !ctx.permissions.includes(opts.requirePermission)) {
      return forbidden(`Missing required permission: ${opts.requirePermission}`);
    }

    if (opts.requireWorkspaceId) {
      const required =
        typeof opts.requireWorkspaceId === 'function'
          ? opts.requireWorkspaceId()
          : opts.requireWorkspaceId;
      if (required && !ctx.workspaceIds.includes(required)) {
        return forbidden(
          `Workspace ${required} is not in your accessible workspaces (${ctx.workspaceIds.join(',') || 'none'}).`,
        );
      }
    }

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

/**
 * Async variant of `requireWorkspaceId` for cases where the workspace id
 * must be extracted from the request body. Use inside the handler:
 *
 *   const workspaceId = await assertWorkspaceAccess(ctx, body.workspaceId);
 */
export function assertWorkspaceAccess(ctx: UserContext, workspaceId: string): void {
  if (!ctx.workspaceIds.includes(workspaceId)) {
    throw new Response(
      JSON.stringify({
        error: `Workspace ${workspaceId} is not in your accessible workspaces.`,
      }),
      { status: 403, headers: { 'Content-Type': 'application/json' } },
    );
  }
}
