import { Repository, FindOptionsWhere, ILike, In } from 'typeorm';
import { Permission, PermissionAction, Role } from '../entities/index.js';

export interface PermissionSearchParams {
  search?: string;
  resource?: string;
  action?: PermissionAction;
  module?: string;
  limit?: number;
  offset?: number;
}

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

export interface UpdatePermissionDto {
  description?: string;
  module?: string;
}

export interface BulkCreatePermissionsDto {
  resource: string;
  actions: PermissionAction[];
  module?: string;
}

export class PermissionsService {
  constructor(
    private readonly permissionRepository: Repository<Permission>,
    private readonly roleRepository: Repository<Role>,
  ) {}

  async findAll(params: PermissionSearchParams): Promise<{ data: Permission[]; total: number }> {
    const {
      search,
      resource,
      action,
      module,
      limit = 100,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<Permission>[] = [];
    const baseWhere: FindOptionsWhere<Permission> = {};

    if (resource) {
      baseWhere.resource = resource;
    }

    if (action) {
      baseWhere.action = action;
    }

    if (module) {
      baseWhere.module = module;
    }

    if (search) {
      where.push(
        { ...baseWhere, resource: ILike(`%${search}%`) },
        { ...baseWhere, description: ILike(`%${search}%`) },
        { ...baseWhere, module: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.permissionRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { module: 'ASC', resource: 'ASC', action: 'ASC' },
    });

    return { data, total };
  }

  async findOne(id: string): Promise<Permission | null> {
    return this.permissionRepository.findOne({
      where: { id },
      relations: ['roles'],
    });
  }

  async findByResourceAction(resource: string, action: PermissionAction): Promise<Permission | null> {
    return this.permissionRepository.findOne({
      where: { resource, action },
    });
  }

  async findByModule(module: string): Promise<Permission[]> {
    return this.permissionRepository.find({
      where: { module },
      order: { resource: 'ASC', action: 'ASC' },
    });
  }

  async findByResource(resource: string): Promise<Permission[]> {
    return this.permissionRepository.find({
      where: { resource },
      order: { action: 'ASC' },
    });
  }

  async create(dto: CreatePermissionDto): Promise<Permission> {
    // Check if permission already exists
    const existing = await this.findByResourceAction(dto.resource, dto.action);
    if (existing) {
      throw new Error(`Ya existe el permiso ${dto.action} para el recurso ${dto.resource}`);
    }

    const permission = this.permissionRepository.create({
      resource: dto.resource,
      action: dto.action,
      description: dto.description ?? null,
      module: dto.module ?? null,
    });

    return this.permissionRepository.save(permission);
  }

  async update(id: string, dto: UpdatePermissionDto): Promise<Permission | null> {
    const permission = await this.findOne(id);
    if (!permission) return null;

    if (dto.description !== undefined) {
      permission.description = dto.description ?? null;
    }
    if (dto.module !== undefined) {
      permission.module = dto.module ?? null;
    }

    return this.permissionRepository.save(permission);
  }

  // Create multiple permissions for a resource (CRUD operations)
  async bulkCreateForResource(dto: BulkCreatePermissionsDto): Promise<Permission[]> {
    const created: Permission[] = [];

    for (const action of dto.actions) {
      const existing = await this.findByResourceAction(dto.resource, action);
      if (!existing) {
        const permission = this.permissionRepository.create({
          resource: dto.resource,
          action,
          description: `${action} ${dto.resource}`,
          module: dto.module ?? null,
        });
        const saved = await this.permissionRepository.save(permission);
        created.push(saved);
      }
    }

    return created;
  }

  // Create standard CRUD permissions for a resource
  async createCrudPermissions(resource: string, module?: string): Promise<Permission[]> {
    const crudActions = [
      PermissionAction.CREATE,
      PermissionAction.READ,
      PermissionAction.UPDATE,
      PermissionAction.DELETE,
    ];

    return this.bulkCreateForResource({
      resource,
      actions: crudActions,
      module,
    });
  }

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

    return permissions.map(p => p.resource);
  }

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

    return permissions.map(p => p.module);
  }

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

    const grouped = new Map<string, Permission[]>();

    for (const permission of permissions) {
      const module = permission.module ?? 'other';
      if (!grouped.has(module)) {
        grouped.set(module, []);
      }
      grouped.get(module)!.push(permission);
    }

    return grouped;
  }

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

    const grouped = new Map<string, Permission[]>();

    for (const permission of permissions) {
      if (!grouped.has(permission.resource)) {
        grouped.set(permission.resource, []);
      }
      grouped.get(permission.resource)!.push(permission);
    }

    return grouped;
  }

  // Check if a role has a specific permission
  async roleHasPermission(
    roleId: string,
    resource: string,
    action: PermissionAction
  ): Promise<boolean> {
    const permission = await this.findByResourceAction(resource, action);
    if (!permission) return false;

    const role = await this.roleRepository.findOne({
      where: { id: roleId },
      relations: ['permissions'],
    });

    if (!role) return false;

    return role.permissions.some(p => p.id === permission.id);
  }

  // Get roles that have a specific permission
  async getRolesWithPermission(permissionId: string): Promise<Role[]> {
    const permission = await this.findOne(permissionId);
    if (!permission) return [];

    return permission.roles ?? [];
  }

  async delete(id: string): Promise<boolean> {
    const permission = await this.findOne(id);
    if (!permission) return false;

    // Check if permission is assigned to any roles
    if (permission.roles && permission.roles.length > 0) {
      throw new Error('No se puede eliminar un permiso asignado a roles. Primero quite el permiso de los roles.');
    }

    const result = await this.permissionRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  // Delete all permissions for a resource
  async deleteByResource(resource: string): Promise<number> {
    const permissions = await this.findByResource(resource);

    // Check if any permission is assigned to roles
    for (const permission of permissions) {
      const fullPermission = await this.findOne(permission.id);
      if (fullPermission?.roles && fullPermission.roles.length > 0) {
        throw new Error(`El permiso ${permission.action} de ${resource} esta asignado a roles`);
      }
    }

    const result = await this.permissionRepository.delete({ resource });
    return result.affected ?? 0;
  }
}
