import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
  ForbiddenException,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { AuthGuard } from "@nestjs/passport";
import { Request } from "express";
import {
  IS_PUBLIC_KEY,
  ROLES_KEY,
  PERMISSIONS_KEY,
} from "../../../common/decorators";
import { normalizeRole } from "../../../common/constants/roles";

interface AuthenticatedUser {
  id: string;
  email: string;
  roles: string[];
  role?: string;
  permissions: string[];
}

/**
 * JWT Auth Guard - Validates JWT token
 */
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest<Request>();
    if (request?.method === "OPTIONS") {
      return true;
    }

    // Check if route is public
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) {
      return true;
    }

    return super.canActivate(context);
  }

  handleRequest<TUser = AuthenticatedUser>(
    err: Error | null,
    user: TUser | false,
    info: any,
  ): TUser {
    if (err || !user) {
      if (info?.name === "TokenExpiredError") {
        throw new UnauthorizedException("TOKEN_EXPIRED");
      }
      throw err || new UnauthorizedException("AUTH_REQUIRED");
    }
    return user;
  }
}

/**
 * Roles Guard - Check if user has required roles
 */
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest<Request>();
    if (req?.method === "OPTIONS") {
      return true;
    }

    const requiredRoles = this.reflector.getAllAndOverride<string[]>(
      ROLES_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!requiredRoles || requiredRoles.length === 0) {
      return true;
    }

    const user = req.user as AuthenticatedUser | undefined;

    if (!user) {
      return false;
    }

    const normalizedUserRoles = (
      user.roles?.length ? user.roles : user.role ? [user.role] : []
    ).map((role) => normalizeRole(role));

    const normalizedRequiredRoles = requiredRoles.map((role) =>
      normalizeRole(role),
    );

    if (normalizedUserRoles.length === 0) {
      return false;
    }

    const hasRole = normalizedRequiredRoles.some((role) =>
      normalizedUserRoles.includes(role),
    );
    if (!hasRole) {
      throw new ForbiddenException("PERMISSION_DENIED");
    }

    return true;
  }
}

/**
 * Permissions Guard - Check if user has required permissions
 */
@Injectable()
export class PermissionsGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest<Request>();
    if (req?.method === "OPTIONS") {
      return true;
    }

    const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
      PERMISSIONS_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!requiredPermissions || requiredPermissions.length === 0) {
      return true;
    }

    const user = req.user as AuthenticatedUser | undefined;

    if (!user || !user.permissions) {
      return false;
    }

    const hasPermission = requiredPermissions.every((permission) =>
      user.permissions.includes(permission),
    );

    if (!hasPermission) {
      throw new ForbiddenException("PERMISSION_DENIED");
    }

    return true;
  }
}
