import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';

import { AppAbility, AbilityFactory } from './ability.factory';
import { PolicyHandler } from './policy-handler.interface';
import { CHECK_POLICY_KEY } from '../check-policies.decorator';

@Injectable()
export class PoliciesGuard implements CanActivate {
  constructor(
    private readonly $reflector: Reflector,
    private readonly $abilityFactory: AbilityFactory
  ) {}

  public canActivate(
    context: ExecutionContext
  ): boolean | Promise<boolean> | Observable<boolean> {
    const handlers =
      this.$reflector.get<PolicyHandler[]>(
        CHECK_POLICY_KEY,
        context.getHandler()
      ) || [];

    const { user } = context.switchToHttp().getRequest();

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

    return handlers.every((handler: any) => {
      return this.execPolicyHandler(handler, ability);
    });
  }

  private execPolicyHandler(
    handler: PolicyHandler,
    ability: AppAbility
  ): boolean {
    if (typeof handler === 'function') {
      return handler(ability);
    }

    return handler.handle(ability);
  }
}
