package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.MembreRole;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.jboss.logging.Logger;

/**
 * Repository pour l'entité MembreRole
 *
 * @author UnionFlow Team
 * @version 3.0
 * @since 2025-01-29
 */
@ApplicationScoped
public class MembreRoleRepository implements PanacheRepository<MembreRole> {

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

  /**
   * Trouve une attribution membre-role par son UUID
   *
   * @param id UUID de l'attribution
   * @return Attribution ou Optional.empty()
   */
  public Optional<MembreRole> findMembreRoleById(UUID id) {
    return find("id = ?1 AND actif = true", id).firstResultOptional();
  }

  /**
   * Trouve tous les rôles d'un membre
   *
   * @param membreId ID du membre
   * @return Liste des attributions de rôles
   */
  public List<MembreRole> findByMembreId(UUID membreId) {
    return find("membreOrganisation.membre.id = ?1 AND actif = true", membreId).list();
  }

  /**
   * Trouve tous les rôles actifs d'un membre (dans la période valide)
   *
   * @param membreId ID du membre
   * @return Liste des attributions de rôles actives
   */
  public List<MembreRole> findActifsByMembreId(UUID membreId) {
    LocalDate aujourdhui = LocalDate.now();
    return find(
            "membreOrganisation.membre.id = ?1 AND actif = true"
                + " AND (dateDebut IS NULL OR dateDebut <= ?2)"
                + " AND (dateFin IS NULL OR dateFin >= ?2)",
            membreId,
            aujourdhui)
        .list();
  }

  /**
   * Trouve tous les membres ayant un rôle spécifique
   *
   * @param roleId ID du rôle
   * @return Liste des attributions de rôles
   */
  public List<MembreRole> findByRoleId(UUID roleId) {
    return find("role.id = ?1 AND actif = true", roleId).list();
  }

  /**
   * Trouve une attribution spécifique membre-role
   *
   * @param membreId ID du membre
   * @param roleId ID du rôle
   * @return Attribution ou null
   */
  public MembreRole findByMembreAndRole(UUID membreId, UUID roleId) {
    return find("membreOrganisation.membre.id = ?1 AND role.id = ?2", membreId, roleId)
        .firstResult();
  }

  /**
   * Compte les administrateurs actifs d'une organisation.
   *
   * <p>Le code DB du rôle admin d'organisation est {@code ORGADMIN}
   * (cf. seed V13__Seed_Standard_Roles.sql). Ne pas confondre avec le rôle
   * Keycloak {@code ADMIN_ORGANISATION} utilisé dans {@code @RolesAllowed} —
   * les deux représentent le même concept mais avec un code différent par
   * source (Keycloak vs table roles DB).
   *
   * <p>L'unique constraint {@code uk_mr_membre_org_role} garantit qu'un même
   * membre n'est comptabilisé qu'une fois même s'il se voit attribuer
   * plusieurs fois le rôle.
   *
   * @param organisationId ID de l'organisation
   * @return nombre d'admins actifs de cette organisation
   */
  public long countAdminsByOrganisationId(UUID organisationId) {
    final LocalDate today = LocalDate.now();

    // Diagnostic : inventaire complet des MembreRole liés à cette organisation
    final long totalForOrg = count("organisation.id = ?1", organisationId);
    final List<MembreRole> allForOrg = list("organisation.id = ?1", organisationId);
    final String codesFound = allForOrg.stream()
        .map(mr -> String.format(
            "%s[actif=%s,dateDebut=%s,dateFin=%s]",
            mr.getRole() != null ? mr.getRole().getCode() : "null",
            mr.getActif(),
            mr.getDateDebut(),
            mr.getDateFin()))
        .reduce((a, b) -> a + ", " + b)
        .orElse("(aucun)");

    final long strictCount = count(
        "organisation.id = ?1 AND role.code = ?2 AND actif = true "
            + "AND (dateDebut IS NULL OR dateDebut <= ?3) "
            + "AND (dateFin IS NULL OR dateFin >= ?3)",
        organisationId,
        "ORGADMIN",
        today);

    LOG.infof(
        "countAdminsByOrganisationId(org=%s) → strict=%d, total_membres_roles_pour_cette_org=%d, detail=[%s]",
        organisationId, strictCount, totalForOrg, codesFound);

    // Fallback : si aucun match strict mais qu'il existe des entrées actives
    // avec un code admin alternatif (ex. ADMIN_ORGANISATION résiduel), on les
    // compte quand même pour éviter un faux zéro.
    if (strictCount == 0 && totalForOrg > 0) {
      final long fallbackCount = count(
          "organisation.id = ?1 AND role.code IN (?2) AND actif = true "
              + "AND (dateDebut IS NULL OR dateDebut <= ?3) "
              + "AND (dateFin IS NULL OR dateFin >= ?3)",
          organisationId,
          List.of("ORGADMIN", "ADMIN_ORGANISATION", "ADMIN"),
          today);
      if (fallbackCount > 0) {
        LOG.warnf(
            "countAdminsByOrganisationId(org=%s) strict=0 mais fallback (codes alternatifs)=%d — le seed V13 utilise 'ORGADMIN', vérifier les assignations",
            organisationId, fallbackCount);
        return fallbackCount;
      }
    }
    return strictCount;
  }
}

