import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { Role } from './role.entity';
import { Permission } from '../permissions/permission.entity';

@Injectable()
export class RolesService {
  constructor(
    @InjectRepository(Role) private readonly repo: Repository<Role>,
    @InjectRepository(Permission) private readonly permissionsRepo: Repository<Permission>,
  ) {}

  findAll(): Promise<Role[]> {
    return this.repo.find({ relations: ['permissions'], order: { name: 'ASC' } });
  }

  findById(id: number): Promise<Role | null> {
    return this.repo.findOne({ where: { id }, relations: ['permissions'] });
  }

  findByName(name: string): Promise<Role | null> {
    return this.repo.findOne({ where: { name }, relations: ['permissions'] });
  }

  async create(data: {
    name: string;
    description?: string;
    permissionIds?: number[];
  }): Promise<Role> {
    const existing = await this.repo.findOne({ where: { name: data.name } });
    if (existing) throw new ConflictException('A role with that name already exists');

    const role = this.repo.create({ name: data.name, description: data.description });
    if (data.permissionIds?.length) {
      role.permissions = await this.permissionsRepo.findBy({ id: In(data.permissionIds) });
    }
    return this.repo.save(role);
  }

  async update(
    id: number,
    data: { name?: string; description?: string; permissionIds?: number[] },
  ): Promise<Role> {
    const role = await this.findById(id);
    if (!role) throw new NotFoundException('Role not found');

    if (data.name && data.name !== role.name) {
      const clash = await this.repo.findOne({ where: { name: data.name } });
      if (clash) throw new ConflictException('A role with that name already exists');
      role.name = data.name;
    }
    if (data.description !== undefined) role.description = data.description;
    if (data.permissionIds) {
      role.permissions = data.permissionIds.length
        ? await this.permissionsRepo.findBy({ id: In(data.permissionIds) })
        : [];
    }

    return this.repo.save(role);
  }

  async delete(id: number): Promise<boolean> {
    const role = await this.repo.findOne({ where: { id } });
    if (!role) return false;
    // The bootstrap role must always exist or the platform can lock itself out.
    if (role.name === 'SUPER_ADMIN') {
      throw new ConflictException('The SUPER_ADMIN role cannot be deleted');
    }
    const result = await this.repo.delete(id);
    return (result.affected || 0) > 0;
  }

  findByIds(ids: number[]): Promise<Role[]> {
    if (!ids?.length) return Promise.resolve([]);
    return this.repo.find({ where: { id: In(ids) } });
  }
}
