import {
  CanActivate,
  ExecutionContext,
  ForbiddenException,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import type { Request } from 'express';
import { AuditWriter } from '../audit/audit.service';

/**
 * Single Entra app role that gates the entire `/api/admin/*` surface
 * per ADR-0020 §"Auth — `Portal.Admin` role claim". The value matches
 * the `value` field declared on the app role in the Entra app
 * registration manifest. Defined as a constant so spec assertions
 * can refer to the same source of truth.
 */
export const ADMIN_ROLE = 'Portal.Admin';

/**
 * `AdminRoleGuard` — enforces ADR-0020's role-based admin gate.
 *
 * Contract
 * --------
 * - **No session → 401.** The user is not authenticated at all; the
 *   SPA should kick off the login flow. We do not audit anonymous
 *   probes here because the route family is not a target — any
 *   unauthenticated 401 is normal traffic the absolute-timeout
 *   middleware would surface anyway.
 *
 * - **Session but missing `Portal.Admin` role → 403 + audit.** The user
 *   *is* authenticated; they just are not authorised for admin.
 *   This is the privilege-escalation attempt audit signal — every
 *   denial lands in `audit.events` with `outcome=denied`, the
 *   attempted route as `subject`, and the roles the user did hold
 *   in the payload. Per ADR-0013 §"Blocking writes": no audit ⇒ no
 *   action — if the audit write fails, the request fails too
 *   (consistent with the existing audit call sites in
 *   `AuthController`).
 *
 * - **Session with `Portal.Admin` role → pass through.** Downstream
 *   controllers see `req.session.user` populated and can rely on
 *   the role check having happened.
 */
@Injectable()
export class AdminRoleGuard implements CanActivate {
  constructor(private readonly audit: AuditWriter) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest<Request>();
    const user = req.session?.user;

    if (!user) {
      throw new UnauthorizedException();
    }

    if (!user.roles.includes(ADMIN_ROLE)) {
      await this.audit.adminAccessDenied({
        actor: { oid: user.oid },
        attemptedRoute: `${req.method} ${req.originalUrl}`,
        rolesHeld: user.roles,
      });
      throw new ForbiddenException();
    }

    return true;
  }
}
