import { SetMetadata, UseGuards, applyDecorators } from '@nestjs/common';
import type { Request } from 'express';
import type { ScopableResource } from 'shared-auth';
import { RequireScopeGuard } from './require-scope.guard';

/**
 * Reflector metadata key carrying the per-route resource
 * extractor. The metadata value is the extractor function
 * itself — `Reflect.metadata` happily stores callables and the
 * guard invokes it on every request.
 */
export const REQUIRE_SCOPE_METADATA = 'auth:require-scope';

/**
 * Extractor signature consumed by `@RequireScope`. Receives the
 * incoming Express request, returns the resource descriptor the
 * matcher will compare against the principal's scopes. May be
 * async — a typical extractor loads the resource by
 * `@Param('id')` from Prisma, denormalises its parentage chain
 * (`etablissementFiness` / `delegationCode` / `regionCode`), and
 * returns the descriptor.
 *
 * Returning `null` from the extractor means "the resource the
 * route protects could not be identified" (a 404 in disguise) —
 * the guard treats it as a denial and emits the audit row with
 * `required: []`. The route handler still runs if the guard
 * lets it through; routes that want a true 404 should surface
 * it from the handler.
 */
export type RequireScopeExtractor = (
  req: Request,
) => ScopableResource | null | Promise<ScopableResource | null>;

/**
 * `@RequireScope(req => …)` — gates a route on
 * `principalCoversResource(principal, extractor(req))` per
 * [ADR-0025 §"Guard surface"](../../../../docs/decisions/0025-authorization-model-privileges-roles-scopes.md).
 *
 * The extractor receives the Express request — typically pulls
 * an id from `req.params`, loads the resource, returns its
 * scope-relevant chain (FINESS / delegation / region). The guard
 * does not assume the extractor is cheap — routes that load the
 * resource a second time inside the handler should pass the
 * Prisma instance via a request-scoped CLS or DI, not via the
 * extractor.
 */
export function RequireScope(extractor: RequireScopeExtractor): ClassDecorator & MethodDecorator {
  return applyDecorators(
    SetMetadata(REQUIRE_SCOPE_METADATA, extractor),
    UseGuards(RequireScopeGuard),
  );
}
