import { logger } from '@goodgo/logger';
import { PrismaClient } from '@prisma/client';

import { getPrismaClient } from '../../config/database.config';
import { cacheService } from '../../core/cache/cache.service';
import { RoleRepository, PermissionRepository } from '../../repositories/role.repository';
import { UserRepository } from '../../repositories/user.repository';

/**
 * EN: RBAC Service for role-based access control
 * VI: Service RBAC cho kiểm soát truy cập dựa trên vai trò
 */
export class RBACService {
  private prisma: PrismaClient;
  private userRepo: UserRepository;
  private roleRepo: RoleRepository;
  private permissionRepo: PermissionRepository;

  constructor() {
    this.prisma = getPrismaClient();
    this.userRepo = new UserRepository(this.prisma);
    this.roleRepo = new RoleRepository(this.prisma);
    this.permissionRepo = new PermissionRepository(this.prisma);
  }

  /**
   * EN: Check if user has specific permission
   * VI: Kiểm tra người dùng có quyền cụ thể
   */
  async hasPermission(
    userId: string,
    resource: string,
    action: string,
    scope?: string
  ): Promise<boolean> {
    const cacheKey = cacheService.keys.userPermissions(userId);
    
    // Try cache first
    const cached = await cacheService.get<Record<string, boolean>>(cacheKey);
    const permissionKey = `${resource}:${action}:${scope || 'all'}`;
    
    if (cached && cached[permissionKey] !== undefined) {
      return cached[permissionKey];
    }

    // Get user with permissions
    const user = await this.userRepo.findWithPermissions(userId);
    if (!user || !('userPermissions' in user)) {
      return false;
    }

    // Check direct user permissions (highest priority)
    const userPermission = (user as any).userPermissions.find(
      (up: any) => 
        up.permission.resource === resource &&
        up.permission.action === action &&
        (up.permission.scope === scope || (!up.permission.scope && !scope))
    );

    if (userPermission) {
      const result = userPermission.granted; // Can be false to deny
      await this.updatePermissionCache(userId, permissionKey, result);
      return result;
    }

    // Check role permissions
    const hasRolePermission = (user as any).userRoles.some((userRole: any) => {
      if (userRole.expiresAt && userRole.expiresAt < new Date()) {
        return false; // Role expired
      }

      return userRole.role.permissions.some(
        (rp: any) =>
          rp.permission.resource === resource &&
          rp.permission.action === action &&
          (rp.permission.scope === scope || (!rp.permission.scope && !scope))
      );
    });

    await this.updatePermissionCache(userId, permissionKey, hasRolePermission);
    return hasRolePermission;
  }

  /**
   * EN: Get all permissions for user
   * VI: Lấy tất cả quyền của người dùng
   */
  async getUserPermissions(userId: string): Promise<string[]> {
    const cacheKey = cacheService.keys.userPermissions(userId);
    
    // Try cache
    const cached = await cacheService.get<string[]>(cacheKey);
    if (cached) {
      return cached;
    }

    const user = await this.userRepo.findWithPermissions(userId);
    if (!user) {
      return [];
    }

    const permissions = new Set<string>();

    // Add role permissions
    (user as any).userRoles.forEach((userRole: any) => {
      if (userRole.expiresAt && userRole.expiresAt < new Date()) {
        return; // Skip expired roles
      }

      userRole.role.permissions.forEach((rp: any) => {
        const key = `${rp.permission.resource}:${rp.permission.action}:${rp.permission.scope || 'all'}`;
        permissions.add(key);
      });
    });

    // Add/override with direct user permissions
    (user as any).userPermissions.forEach((up: any) => {
      if (up.expiresAt && up.expiresAt < new Date()) {
        return; // Skip expired permissions
      }

      const key = `${up.permission.resource}:${up.permission.action}:${up.permission.scope || 'all'}`;
      if (up.granted) {
        permissions.add(key);
      } else {
        permissions.delete(key); // Deny permission
      }
    });

    const permissionArray = Array.from(permissions);
    
    // Cache for 5 minutes
    await cacheService.set(cacheKey, permissionArray, 300);
    
    return permissionArray;
  }

  /**
   * EN: Get all roles for user
   * VI: Lấy tất cả roles của người dùng
   */
  async getUserRoles(userId: string): Promise<string[]> {
    const cacheKey = cacheService.keys.userRoles(userId);
    
    // Try cache
    const cached = await cacheService.get<string[]>(cacheKey);
    if (cached) {
      return cached;
    }

    const user = await this.userRepo.findWithPermissions(userId);
    if (!user) {
      return [];
    }

    const roles = (user as any).userRoles
      .filter((ur: any) => !ur.expiresAt || ur.expiresAt >= new Date())
      .map((ur: any) => ur.role.name);

    // Cache for 5 minutes
    await cacheService.set(cacheKey, roles, 300);
    
    return roles;
  }

  /**
   * EN: Assign role to user
   * VI: Gán role cho người dùng
   */
  async assignRole(
    userId: string,
    roleId: string,
    options?: {
      grantedBy?: string;
      expiresAt?: Date;
    }
  ): Promise<void> {
    await this.prisma.userRole.upsert({
      where: {
        userId_roleId: {
          userId,
          roleId,
        },
      },
      create: {
        userId,
        roleId,
        grantedBy: options?.grantedBy,
        expiresAt: options?.expiresAt,
      },
      update: {
        expiresAt: options?.expiresAt,
      },
    });

    // Invalidate cache
    await cacheService.delMany([
      cacheService.keys.userPermissions(userId),
      cacheService.keys.userRoles(userId),
    ]);

    logger.info('Role assigned', { userId, roleId });
  }

  /**
   * EN: Revoke role from user
   * VI: Thu hồi role từ người dùng
   */
  async revokeRole(userId: string, roleId: string): Promise<void> {
    await this.prisma.userRole.deleteMany({
      where: {
        userId,
        roleId,
      },
    });

    // Invalidate cache
    await cacheService.delMany([
      cacheService.keys.userPermissions(userId),
      cacheService.keys.userRoles(userId),
    ]);

    logger.info('Role revoked', { userId, roleId });
  }

  /**
   * EN: Grant permission to user
   * VI: Cấp quyền cho người dùng
   */
  async grantPermission(
    userId: string,
    permissionId: string,
    options?: {
      grantedBy?: string;
      expiresAt?: Date;
    }
  ): Promise<void> {
    await this.prisma.userPermission.upsert({
      where: {
        userId_permissionId: {
          userId,
          permissionId,
        },
      },
      create: {
        userId,
        permissionId,
        granted: true,
        grantedBy: options?.grantedBy,
        expiresAt: options?.expiresAt,
      },
      update: {
        granted: true,
        expiresAt: options?.expiresAt,
      },
    });

    // Invalidate cache
    await cacheService.del(cacheService.keys.userPermissions(userId));

    logger.info('Permission granted', { userId, permissionId });
  }

  /**
   * EN: Deny permission to user
   * VI: Từ chối quyền cho người dùng
   */
  async denyPermission(
    userId: string,
    permissionId: string,
    options?: {
      grantedBy?: string;
    }
  ): Promise<void> {
    await this.prisma.userPermission.upsert({
      where: {
        userId_permissionId: {
          userId,
          permissionId,
        },
      },
      create: {
        userId,
        permissionId,
        granted: false,
        grantedBy: options?.grantedBy,
      },
      update: {
        granted: false,
      },
    });

    // Invalidate cache
    await cacheService.del(cacheService.keys.userPermissions(userId));

    logger.info('Permission denied', { userId, permissionId });
  }

  /**
   * EN: Update permission cache
   * VI: Cập nhật cache permission
   */
  private async updatePermissionCache(
    userId: string,
    permissionKey: string,
    granted: boolean
  ): Promise<void> {
    const cacheKey = cacheService.keys.userPermissions(userId);
    const cached = await cacheService.get<Record<string, boolean>>(cacheKey);
    
    if (cached) {
      cached[permissionKey] = granted;
      await cacheService.set(cacheKey, cached, 300);
    }
  }
}

export const rbacService = new RBACService();
