import { Repository, In } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Permission, PermissionAction, Role, User } from '../auth/entities/index.js';
import { PaginationParams } from '../../shared/types/index.js';
import { logger } from '../../shared/utils/logger.js';

// ===== Interfaces =====

export interface PermissionFilter {
  module?: string;
  resource?: string;
  action?: PermissionAction;
}

export interface EffectivePermission {
  resource: string;
  action: string;
  module: string | null;
  fromRoles: string[];
}

// ===== PermissionsService Class =====

class PermissionsService {
  private permissionRepository: Repository<Permission>;
  private roleRepository: Repository<Role>;
  private userRepository: Repository<User>;

  constructor() {
    this.permissionRepository = AppDataSource.getRepository(Permission);
    this.roleRepository = AppDataSource.getRepository(Role);
    this.userRepository = AppDataSource.getRepository(User);
  }

  /**
   * Get all permissions with optional filtering and pagination
   */
  async findAll(
    params: PaginationParams,
    filter?: PermissionFilter
  ): Promise<{ permissions: Permission[]; total: number }> {
    try {
      const { page, limit, sortBy = 'resource', sortOrder = 'asc' } = params;
      const skip = (page - 1) * limit;

      const queryBuilder = this.permissionRepository
        .createQueryBuilder('permission')
        .orderBy(`permission.${sortBy}`, sortOrder.toUpperCase() as 'ASC' | 'DESC')
        .skip(skip)
        .take(limit);

      // Apply filters
      if (filter?.module) {
        queryBuilder.andWhere('permission.module = :module', { module: filter.module });
      }
      if (filter?.resource) {
        queryBuilder.andWhere('permission.resource LIKE :resource', {
          resource: `%${filter.resource}%`,
        });
      }
      if (filter?.action) {
        queryBuilder.andWhere('permission.action = :action', { action: filter.action });
      }

      const [permissions, total] = await queryBuilder.getManyAndCount();

      logger.debug('Permissions retrieved', { count: permissions.length, total, filter });

      return { permissions, total };
    } catch (error) {
      logger.error('Error retrieving permissions', {
        error: (error as Error).message,
      });
      throw error;
    }
  }

  /**
   * Get permission by ID
   */
  async findById(permissionId: string): Promise<Permission | null> {
    return await this.permissionRepository.findOne({
      where: { id: permissionId },
    });
  }

  /**
   * Get permissions by IDs
   */
  async findByIds(permissionIds: string[]): Promise<Permission[]> {
    return await this.permissionRepository.find({
      where: { id: In(permissionIds) },
    });
  }

  /**
   * Get all unique modules
   */
  async getModules(): Promise<string[]> {
    const result = await this.permissionRepository
      .createQueryBuilder('permission')
      .select('DISTINCT permission.module', 'module')
      .where('permission.module IS NOT NULL')
      .orderBy('permission.module', 'ASC')
      .getRawMany();

    return result.map(r => r.module);
  }

  /**
   * Get all permissions for a specific module
   */
  async getByModule(module: string): Promise<Permission[]> {
    return await this.permissionRepository.find({
      where: { module },
      order: { resource: 'ASC', action: 'ASC' },
    });
  }

  /**
   * Get all unique resources
   */
  async getResources(): Promise<string[]> {
    const result = await this.permissionRepository
      .createQueryBuilder('permission')
      .select('DISTINCT permission.resource', 'resource')
      .orderBy('permission.resource', 'ASC')
      .getRawMany();

    return result.map(r => r.resource);
  }

  /**
   * Get permissions grouped by module
   */
  async getGroupedByModule(): Promise<Record<string, Permission[]>> {
    const permissions = await this.permissionRepository.find({
      order: { module: 'ASC', resource: 'ASC', action: 'ASC' },
    });

    const grouped: Record<string, Permission[]> = {};

    for (const permission of permissions) {
      const module = permission.module || 'other';
      if (!grouped[module]) {
        grouped[module] = [];
      }
      grouped[module].push(permission);
    }

    return grouped;
  }

  /**
   * Get effective permissions for a user (combining all role permissions)
   */
  async getEffectivePermissions(
    tenantId: string,
    userId: string
  ): Promise<EffectivePermission[]> {
    try {
      const user = await this.userRepository.findOne({
        where: { id: userId, tenantId, deletedAt: undefined },
        relations: ['roles', 'roles.permissions'],
      });

      if (!user || !user.roles) {
        return [];
      }

      // Map to collect permissions with their source roles
      const permissionMap = new Map<string, EffectivePermission>();

      for (const role of user.roles) {
        if (role.deletedAt) continue;

        for (const permission of role.permissions || []) {
          const key = `${permission.resource}:${permission.action}`;

          if (permissionMap.has(key)) {
            // Add role to existing permission
            const existing = permissionMap.get(key)!;
            if (!existing.fromRoles.includes(role.name)) {
              existing.fromRoles.push(role.name);
            }
          } else {
            // Create new permission entry
            permissionMap.set(key, {
              resource: permission.resource,
              action: permission.action,
              module: permission.module,
              fromRoles: [role.name],
            });
          }
        }
      }

      const effectivePermissions = Array.from(permissionMap.values());

      logger.debug('Effective permissions calculated', {
        userId,
        tenantId,
        permissionCount: effectivePermissions.length,
      });

      return effectivePermissions;
    } catch (error) {
      logger.error('Error calculating effective permissions', {
        error: (error as Error).message,
        userId,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Check if a user has a specific permission
   */
  async hasPermission(
    tenantId: string,
    userId: string,
    resource: string,
    action: string
  ): Promise<boolean> {
    try {
      const user = await this.userRepository.findOne({
        where: { id: userId, tenantId, deletedAt: undefined },
        relations: ['roles', 'roles.permissions'],
      });

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

      // Check if user is superuser (has all permissions)
      if (user.isSuperuser) {
        return true;
      }

      // Check through all roles
      for (const role of user.roles) {
        if (role.deletedAt) continue;

        // Super admin role has all permissions
        if (role.code === 'super_admin') {
          return true;
        }

        for (const permission of role.permissions || []) {
          if (permission.resource === resource && permission.action === action) {
            return true;
          }
        }
      }

      return false;
    } catch (error) {
      logger.error('Error checking permission', {
        error: (error as Error).message,
        userId,
        tenantId,
        resource,
        action,
      });
      return false;
    }
  }

  /**
   * Check multiple permissions at once (returns all that user has)
   */
  async checkPermissions(
    tenantId: string,
    userId: string,
    permissionChecks: Array<{ resource: string; action: string }>
  ): Promise<Array<{ resource: string; action: string; granted: boolean }>> {
    const effectivePermissions = await this.getEffectivePermissions(tenantId, userId);
    const permissionSet = new Set(
      effectivePermissions.map(p => `${p.resource}:${p.action}`)
    );

    // Check if user is superuser
    const user = await this.userRepository.findOne({
      where: { id: userId, tenantId },
    });
    const isSuperuser = user?.isSuperuser || false;

    return permissionChecks.map(check => ({
      resource: check.resource,
      action: check.action,
      granted: isSuperuser || permissionSet.has(`${check.resource}:${check.action}`),
    }));
  }

  /**
   * Get permission matrix for UI display (roles vs permissions)
   */
  async getPermissionMatrix(
    tenantId: string
  ): Promise<{
    roles: Array<{ id: string; name: string; code: string }>;
    permissions: Permission[];
    matrix: Record<string, string[]>;
  }> {
    try {
      // Get all roles for tenant
      const roles = await this.roleRepository.find({
        where: { tenantId, deletedAt: undefined },
        relations: ['permissions'],
        order: { name: 'ASC' },
      });

      // Get all permissions
      const permissions = await this.permissionRepository.find({
        order: { module: 'ASC', resource: 'ASC', action: 'ASC' },
      });

      // Build matrix: roleId -> [permissionIds]
      const matrix: Record<string, string[]> = {};
      for (const role of roles) {
        matrix[role.id] = (role.permissions || []).map(p => p.id);
      }

      return {
        roles: roles.map(r => ({ id: r.id, name: r.name, code: r.code })),
        permissions,
        matrix,
      };
    } catch (error) {
      logger.error('Error building permission matrix', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }
}

// ===== Export Singleton Instance =====

export const permissionsService = new PermissionsService();
