package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.entity.Permission;
import dev.lions.unionflow.server.entity.Role;
import dev.lions.unionflow.server.entity.RolePermission;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.PermissionRepository;
import dev.lions.unionflow.server.repository.RoleRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;

/**
 * Service métier pour la gestion des rôles
 *
 * @author UnionFlow Team
 * @version 3.0
 * @since 2025-01-29
 */
@ApplicationScoped
public class RoleService {

  private static final Logger LOG = Logger.getLogger(RoleService.class);

  @Inject
  RoleRepository roleRepository;

  @Inject
  OrganisationRepository organisationRepository;

  @Inject
  KeycloakService keycloakService;

  @Inject
  PermissionRepository permissionRepository;

  /**
   * Crée un nouveau rôle
   *
   * @param role Rôle à créer
   * @return Rôle créé
   */
  @Transactional
  public Role creerRole(Role role) {
    LOG.infof("Création d'un nouveau rôle: %s", role.getCode());

    // Vérifier l'unicité du code
    if (roleRepository.findByCode(role.getCode()).isPresent()) {
      throw new IllegalArgumentException("Un rôle avec ce code existe déjà: " + role.getCode());
    }

    // Métadonnées
    role.setCreePar(keycloakService.getCurrentUserEmail());

    roleRepository.persist(role);
    LOG.infof("Rôle créé avec succès: ID=%s, Code=%s", role.getId(), role.getCode());

    return role;
  }

  /**
   * Met à jour un rôle existant
   *
   * @param id          ID du rôle
   * @param roleModifie Rôle avec les modifications
   * @return Rôle mis à jour
   */
  @Transactional
  public Role mettreAJourRole(UUID id, Role roleModifie) {
    LOG.infof("Mise à jour du rôle ID: %s", id);

    Role role = roleRepository
        .findRoleById(id)
        .orElseThrow(() -> new NotFoundException("Rôle non trouvé avec l'ID: " + id));

    // Vérifier l'unicité du code si modifié
    if (!role.getCode().equals(roleModifie.getCode())) {
      if (roleRepository.findByCode(roleModifie.getCode()).isPresent()) {
        throw new IllegalArgumentException("Un rôle avec ce code existe déjà: " + roleModifie.getCode());
      }
    }

    // Mise à jour
    role.setCode(roleModifie.getCode());
    role.setLibelle(roleModifie.getLibelle());
    role.setDescription(roleModifie.getDescription());
    role.setNiveauHierarchique(roleModifie.getNiveauHierarchique());
    role.setTypeRole(roleModifie.getTypeRole());
    role.setOrganisation(roleModifie.getOrganisation());
    role.setModifiePar(keycloakService.getCurrentUserEmail());

    roleRepository.persist(role);
    LOG.infof("Rôle mis à jour avec succès: ID=%s", id);

    return role;
  }

  /**
   * Trouve un rôle par son ID
   *
   * @param id ID du rôle
   * @return Rôle ou null
   */
  public Role trouverParId(UUID id) {
    return roleRepository.findRoleById(id).orElse(null);
  }

  /**
   * Trouve un rôle par son code
   *
   * @param code Code du rôle
   * @return Rôle ou null
   */
  public Role trouverParCode(String code) {
    return roleRepository.findByCode(code).orElse(null);
  }

  /**
   * Liste tous les rôles système
   *
   * @return Liste des rôles système
   */
  public List<Role> listerRolesSysteme() {
    return roleRepository.findRolesSysteme();
  }

  /**
   * Liste tous les rôles d'une organisation
   *
   * @param organisationId ID de l'organisation
   * @return Liste des rôles
   */
  public List<Role> listerParOrganisation(UUID organisationId) {
    return roleRepository.findByOrganisationId(organisationId);
  }

  /**
   * Liste tous les rôles actifs
   *
   * @return Liste des rôles actifs
   */
  public List<Role> listerTousActifs() {
    return roleRepository.findAllActifs();
  }

  /**
   * Liste les rôles par catégorie (PLATEFORME, FONCTIONNEL, METIER)
   */
  public List<Role> listerParCategorie(String categorie) {
    return roleRepository.findByCategorie(categorie);
  }

  /**
   * Liste les rôles assignables (FONCTIONNEL + METIER) — pour UI d'assignation.
   */
  public List<Role> listerRolesAssignables() {
    return roleRepository.findRolesAssignables();
  }

  /**
   * Retourne les codes de permissions attribuées à un rôle.
   */
  public Set<String> getPermissionsDuRole(UUID roleId) {
    Role role = roleRepository.findRoleById(roleId)
        .orElseThrow(() -> new NotFoundException("Rôle non trouvé : " + roleId));
    return role.getPermissions().stream()
        .map(rp -> rp.getPermission().getCode())
        .collect(Collectors.toSet());
  }

  /**
   * Assigne une permission à un rôle (si pas déjà assignée).
   */
  @Transactional
  public void assignerPermission(UUID roleId, UUID permissionId) {
    Role role = roleRepository.findRoleById(roleId)
        .orElseThrow(() -> new NotFoundException("Rôle non trouvé : " + roleId));
    Permission permission = permissionRepository.findByIdOptional(permissionId)
        .orElseThrow(() -> new NotFoundException("Permission non trouvée : " + permissionId));

    boolean dejaAssignee = role.getPermissions().stream()
        .anyMatch(rp -> rp.getPermission().getId().equals(permissionId));
    if (dejaAssignee) {
      return;
    }

    RolePermission rp = new RolePermission();
    rp.setRole(role);
    rp.setPermission(permission);
    rp.setCreePar(keycloakService.getCurrentUserEmail());
    role.getPermissions().add(rp);
    roleRepository.persist(role);
    LOG.infof("Permission %s assignée au rôle %s", permission.getCode(), role.getCode());
  }

  /**
   * Supprime (désactive) un rôle
   *
   * @param id ID du rôle
   */
  @Transactional
  public void supprimerRole(UUID id) {
    LOG.infof("Suppression du rôle ID: %s", id);

    Role role = roleRepository
        .findRoleById(id)
        .orElseThrow(() -> new NotFoundException("Rôle non trouvé avec l'ID: " + id));

    // Vérifier si c'est un rôle système
    if (role.isRoleSysteme()) {
      throw new IllegalStateException("Impossible de supprimer un rôle système");
    }

    role.setActif(false);
    role.setModifiePar(keycloakService.getCurrentUserEmail());

    roleRepository.persist(role);
    LOG.infof("Rôle supprimé avec succès: ID=%s", id);
  }
}
