import { CanActivate, ExecutionContext, ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthorizationService } from './AuthorizationService';
import { PUBLIC_ROUTE_METADATA_KEY } from './Public';
import { REQUIRE_AUTHENTICATED_USER_METADATA_KEY } from './RequireAuthenticatedUser';
import { REQUIRE_ROLES_METADATA_KEY, RequireRolesMetadata } from './RequireRoles';

type AuthenticatedRequest = {
  user?: { userId: string; role?: string | undefined };
};

@Injectable()
export class AuthorizationGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly authorizationService: AuthorizationService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const handler = context.getHandler();
    const controllerClass = context.getClass();

    const isPublic =
      this.reflector.getAllAndOverride<{ public: true } | undefined>(
        PUBLIC_ROUTE_METADATA_KEY,
        [handler, controllerClass],
      )?.public ?? false;

    if (isPublic) {
      return true;
    }

    const requiresAuth =
      this.reflector.getAllAndOverride<{ required: true } | undefined>(
        REQUIRE_AUTHENTICATED_USER_METADATA_KEY,
        [handler, controllerClass],
      )?.required ?? false;

    const rolesMetadata =
      this.reflector.getAllAndOverride<RequireRolesMetadata | undefined>(
        REQUIRE_ROLES_METADATA_KEY,
        [handler, controllerClass],
      ) ?? null;

    // Protect all endpoints by default:
    // - if a route is not marked public, it requires an authenticated user.
    const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
    const userId = request.user?.userId;

    if (!userId) {
      throw new UnauthorizedException('Unauthorized');
    }

    // If the route explicitly requires auth, that's already satisfied by userId.
    void requiresAuth;

    if (rolesMetadata && rolesMetadata.anyOf.length > 0) {
      let userRoles = this.authorizationService.getRolesForUser(userId);
      
      // If no roles from service, check request for demo login roles
      if (userRoles.length === 0 && request.user?.role) {
        userRoles = [request.user.role];
      }
      
      // Map demo login roles to API expected roles
      const mappedRoles = userRoles.map(role => {
        if (role === 'league-admin') return 'admin';
        if (role === 'league-owner') return 'owner';
        if (role === 'league-steward') return 'steward';
        if (role === 'system-owner') return 'owner';
        if (role === 'super-admin') return 'admin';
        return role;
      });
      
      const hasAnyRole = rolesMetadata.anyOf.some((r) => mappedRoles.includes(r));
      if (!hasAnyRole) {
        throw new ForbiddenException('Forbidden');
      }
    }

    return true;
  }
}