import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { CHECK_POLICIES_KEY } from '../decorators/check-policies.decorator';
import { IS_PUBLIC_KEY } from '../../auth/decorators/public.decorator';
import { CaslAbilityFactory } from '../casl-ability.factory';
import { AppAbility, AuthorizationRequest, PolicyHandler } from '../interfaces';

/**
 * Evaluates CASL policies declared via @CheckPolicies().
 *
 * Registered as a global APP_GUARD — runs after JwtAuthGuard and
 * PermissionGuard in the guard chain.  Only activates when a route
 * carries @CheckPolicies metadata.
 *
 * ## Interaction with other guards
 *
 * - @Public() → PoliciesGuard skips (same as other guards).
 * - @CheckPolicies alone (no @RequirePermission) → PermissionGuard
 *   passes through if user is authenticated; PoliciesGuard evaluates.
 * - @CheckPolicies + @RequirePermission → both guards run independently;
 *   both must pass.
 *
 * ## Handler types
 *
 * Two forms are supported:
 *
 * ```ts
 * // Callback form
 * @CheckPolicies((ability) => ability.can('read', 'Student'))
 *
 * // Class-based form (testable, NestJS official pattern)
 * class ReadStudentPolicyHandler implements IPolicyHandler {
 *   handle(ability: AppAbility) { return ability.can('read', 'Student'); }
 * }
 * @CheckPolicies(new ReadStudentPolicyHandler())
 * ```
 */
@Injectable()
export class PoliciesGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private abilityFactory: CaslAbilityFactory,
  ) {}

  canActivate(context: ExecutionContext): boolean {
    // @Public() routes skip all authorization
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) return true;

    const handlers = this.reflector.getAllAndMerge<PolicyHandler[]>(CHECK_POLICIES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    // No @CheckPolicies → let other guards decide
    if (!handlers || handlers.length === 0) return true;

    const request = context.switchToHttp().getRequest<AuthorizationRequest>();
    const user = request.user;
    if (!user) return false;

    const ability = this.abilityFactory.createForUser(user);

    return handlers.every((handler) => this.execHandler(handler, ability));
  }

  /**
   * Execute a policy handler — supports both callback and class-based forms.
   */
  private execHandler(handler: PolicyHandler, ability: AppAbility): boolean {
    if (typeof handler === 'function') {
      return handler(ability);
    }
    return handler.handle(ability);
  }
}
