import { CanActivate, ExecutionContext, ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { firstValueFrom } from 'rxjs';
import { CoreGrpcService } from '../core-grpc.service';
import { assertSessionUnlocked, verifyAccessToken } from '../session-auth';

export interface AdminRequestUser {
  id: string;
  isSuperAdmin: boolean;
  canAccessAdmin: boolean;
  canManageRoles: boolean;
  canViewOAuth: boolean;
  canManageOAuth: boolean;
  canManageAllOAuth: boolean;
  canManageUsers: boolean;
  canManageAllUsers: boolean;
  canViewUsers: boolean;
  canManageSettings: boolean;
  canVerifyUsers: boolean;
  roles: string[];
  permissions: string[];
}

@Injectable()
export class AdminGuard implements CanActivate {
  constructor(
    private readonly jwt: JwtService,
    private readonly core: CoreGrpcService
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<{ headers: { authorization?: string }; adminUser?: AdminRequestUser }>();

    let payload;
    try {
      payload = await verifyAccessToken(this.jwt, request.headers.authorization);
      await assertSessionUnlocked(this.core, payload);
    } catch (error) {
      if (error instanceof UnauthorizedException || error instanceof ForbiddenException) {
        throw error;
      }
      throw new UnauthorizedException('Недействительный токен доступа');
    }

    const profile = (await firstValueFrom(this.core.auth.GetMe({ userId: payload.sub }))) as AdminRequestUser & { id: string };
    if (!profile.canAccessAdmin) {
      throw new ForbiddenException('Доступ к админ-панели запрещён');
    }

    request.adminUser = {
      id: profile.id,
      isSuperAdmin: profile.isSuperAdmin,
      canAccessAdmin: Boolean(profile.canAccessAdmin),
      canManageRoles: Boolean(profile.canManageRoles),
      canViewOAuth: Boolean(profile.canViewOAuth),
      canManageOAuth: Boolean(profile.canManageOAuth),
      canManageAllOAuth: Boolean(profile.canManageAllOAuth),
      canManageUsers: Boolean(profile.canManageUsers),
      canManageAllUsers: Boolean(profile.canManageAllUsers),
      canManageSettings: Boolean(profile.canManageSettings),
      canViewUsers: Boolean(profile.canViewUsers),
      canVerifyUsers: Boolean(profile.canVerifyUsers),
      roles: profile.roles ?? [],
      permissions: profile.permissions ?? []
    };

    return true;
  }
}

@Injectable()
export class RbacManageGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest<{ adminUser?: AdminRequestUser }>();
    if (request.adminUser?.isSuperAdmin || request.adminUser?.canManageRoles) {
      return true;
    }
    throw new ForbiddenException('Недостаточно прав для управления ролями и правами');
  }
}

@Injectable()
export class SuperAdminGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest<{ adminUser?: AdminRequestUser }>();
    if (!request.adminUser?.isSuperAdmin) {
      throw new ForbiddenException('Только супер-администратор может выполнять это действие');
    }
    return true;
  }
}

type AdminPermissionKey = keyof Pick<
  AdminRequestUser,
  'canViewOAuth' | 'canManageOAuth' | 'canManageUsers' | 'canViewUsers' | 'canManageSettings'
>;

export function assertAdminPermission(user: AdminRequestUser | undefined, permission: AdminPermissionKey) {
  if (!user?.[permission]) {
    throw new ForbiddenException('Недостаточно прав для выполнения действия');
  }
}

export function assertAdminAnyPermission(user: AdminRequestUser | undefined, ...permissions: AdminPermissionKey[]) {
  if (!user || !permissions.some((permission) => user[permission])) {
    throw new ForbiddenException('Недостаточно прав для выполнения действия');
  }
}
