package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.organisation.request.CreateOrganisationRequest;
import dev.lions.unionflow.server.api.dto.organisation.request.UpdateOrganisationRequest;
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse;
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationSummaryResponse;
import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.repository.AdresseRepository;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.MembreRoleRepository;
import dev.lions.unionflow.server.repository.TypeReferenceRepository;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;

/**
 * Service métier pour la gestion des organisations
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@ApplicationScoped
public class OrganisationService {

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

  @Inject
  OrganisationRepository organisationRepository;

  @Inject
  MembreRepository membreRepository;

  @Inject
  DefaultsService defaultsService;

  @Inject
  TypeReferenceRepository typeReferenceRepository;

  @Inject
  MembreOrganisationRepository membreOrganisationRepository;

  @Inject
  AdresseRepository adresseRepository;

  @Inject
  EvenementRepository evenementRepository;

  @Inject
  MembreRoleRepository membreRoleRepository;

  /**
   * Crée une nouvelle organisation
   *
   * @param organisation l'organisation à créer
   * @param utilisateur  identifiant de l'utilisateur effectuant la création
   *                     (email ou "system")
   * @return l'organisation créée
   */
  @Transactional
  public Organisation creerOrganisation(Organisation organisation, String utilisateur) {
    LOG.infof("Création d'une nouvelle organisation: %s", organisation.getNom());

    // Vérifier l'unicité de l'email
    if (organisationRepository.findByEmail(organisation.getEmail()).isPresent()) {
      throw new IllegalStateException("Une organisation avec cet email existe déjà");
    }

    // Vérifier l'unicité du nom
    if (organisationRepository.findByNom(organisation.getNom()).isPresent()) {
      throw new IllegalArgumentException("Une organisation avec ce nom existe déjà");
    }

    // Vérifier l'unicité du numéro d'enregistrement si fourni
    if (organisation.getNumeroEnregistrement() != null
        && !organisation.getNumeroEnregistrement().isEmpty()) {
      if (organisationRepository
          .findByNumeroEnregistrement(organisation.getNumeroEnregistrement())
          .isPresent()) {
        throw new IllegalArgumentException(
            "Une organisation avec ce numéro d'enregistrement existe déjà");
      }
    }

    // Définir les valeurs par défaut
    if (organisation.getStatut() == null) {
      organisation.setStatut("ACTIVE");
    }
    if (organisation.getTypeOrganisation() == null) {
      organisation.setTypeOrganisation("ASSOCIATION");
    }

    // Initialiser modulesActifs et categorieType depuis types_reference
    // Cela permet aux types créés via CRUD (ex: "TANTANPION") d'hériter
    // automatiquement de leurs modules_requis sans modifier le code Java.
    if (organisation.getModulesActifs() == null || organisation.getModulesActifs().isBlank()) {
      typeReferenceRepository
          .findByDomaineAndCode("TYPE_ORGANISATION", organisation.getTypeOrganisation())
          .ifPresentOrElse(
              tr -> {
                if (tr.getModulesRequis() != null && !tr.getModulesRequis().isBlank()) {
                  organisation.setModulesActifs(tr.getModulesRequis());
                  LOG.infof("Modules initialisés depuis types_reference pour le type '%s': %s",
                      organisation.getTypeOrganisation(), tr.getModulesRequis());
                }
                if (tr.getCategorie() != null && organisation.getCategorieType() == null) {
                  organisation.setCategorieType(tr.getCategorie());
                }
              },
              () -> LOG.warnf(
                  "Type d'organisation '%s' absent de types_reference — modules non initialisés. " +
                  "Ajoutez ce type via l'administration pour activer les modules métier.",
                  organisation.getTypeOrganisation()));
    }

    // Audit : créé par / modifié par (BaseEntity n'initialise pas creePar dans
    // @PrePersist)
    String auditUser = utilisateur != null && !utilisateur.isBlank() ? utilisateur : "system";
    organisation.setCreePar(auditUser);
    organisation.setModifiePar(auditUser);
    if (organisation.getDateCreation() == null) {
      organisation.setDateCreation(LocalDateTime.now());
    }
    organisation.setDateModification(organisation.getDateCreation());

    organisationRepository.persist(organisation);
    LOG.infof(
        "Organisation créée avec succès: ID=%s, Nom=%s", organisation.getId(), organisation.getNom());

    return organisation;
  }

  /**
   * Met à jour une organisation existante
   *
   * @param id                    l'ID de l'organisation
   * @param organisationMiseAJour les données de mise à jour
   * @param utilisateur           l'utilisateur effectuant la modification
   * @return l'organisation mise à jour
   */
  @Transactional
  public Organisation mettreAJourOrganisation(
      UUID id, Organisation organisationMiseAJour, String utilisateur) {
    LOG.infof("Mise à jour de l'organisation ID: %s", id);

    Organisation organisation = organisationRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id));

    // Vérifier l'unicité de l'email si modifié
    if (!organisation.getEmail().equals(organisationMiseAJour.getEmail())) {
      if (organisationRepository.findByEmail(organisationMiseAJour.getEmail()).isPresent()) {
        throw new IllegalStateException("Une organisation avec cet email existe déjà");
      }
      organisation.setEmail(organisationMiseAJour.getEmail());
    }

    // Vérifier l'unicité du nom si modifié
    if (!organisation.getNom().equals(organisationMiseAJour.getNom())) {
      if (organisationRepository.findByNom(organisationMiseAJour.getNom()).isPresent()) {
        throw new IllegalArgumentException("Une organisation avec ce nom existe déjà");
      }
      organisation.setNom(organisationMiseAJour.getNom());
    }

    // Mettre à jour tous les champs métier (alignés sur detail.xhtml et
    // organisation-form)
    organisation.setNomCourt(organisationMiseAJour.getNomCourt());
    organisation.setDescription(organisationMiseAJour.getDescription());
    organisation.setDateFondation(organisationMiseAJour.getDateFondation());
    organisation.setNumeroEnregistrement(organisationMiseAJour.getNumeroEnregistrement());
    organisation.setTelephone(organisationMiseAJour.getTelephone());
    organisation.setTelephoneSecondaire(organisationMiseAJour.getTelephoneSecondaire());
    organisation.setEmailSecondaire(organisationMiseAJour.getEmailSecondaire());
    organisation.setAdresse(organisationMiseAJour.getAdresse());
    organisation.setVille(organisationMiseAJour.getVille());
    organisation.setRegion(organisationMiseAJour.getRegion());
    organisation.setPays(organisationMiseAJour.getPays());
    organisation.setCodePostal(organisationMiseAJour.getCodePostal());
    organisation.setLatitude(organisationMiseAJour.getLatitude());
    organisation.setLongitude(organisationMiseAJour.getLongitude());
    organisation.setSiteWeb(organisationMiseAJour.getSiteWeb());
    organisation.setLogo(organisationMiseAJour.getLogo());
    organisation.setReseauxSociaux(organisationMiseAJour.getReseauxSociaux());
    organisation.setObjectifs(organisationMiseAJour.getObjectifs());
    organisation.setActivitesPrincipales(organisationMiseAJour.getActivitesPrincipales());
    organisation.setCertifications(organisationMiseAJour.getCertifications());
    organisation.setPartenaires(organisationMiseAJour.getPartenaires());
    organisation.setNotes(organisationMiseAJour.getNotes());
    if (organisationMiseAJour.getStatut() != null) {
      organisation.setStatut(organisationMiseAJour.getStatut());
    }
    organisation.setTypeOrganisation(organisationMiseAJour.getTypeOrganisation());
    organisation.setNiveauHierarchique(
        organisationMiseAJour.getNiveauHierarchique() != null ? organisationMiseAJour.getNiveauHierarchique() : 0);
    organisation.setNombreMembres(
        organisationMiseAJour.getNombreMembres() != null ? organisationMiseAJour.getNombreMembres() : 0);
    organisation.setNombreAdministrateurs(
        organisationMiseAJour.getNombreAdministrateurs() != null ? organisationMiseAJour.getNombreAdministrateurs()
            : 0);
    // Budget & Finances
    organisation.setBudgetAnnuel(organisationMiseAJour.getBudgetAnnuel());
    organisation.setDevise(
        organisationMiseAJour.getDevise() != null ? organisationMiseAJour.getDevise() : defaultsService.getDevise());
    organisation.setCotisationObligatoire(
        organisationMiseAJour.getCotisationObligatoire() != null ? organisationMiseAJour.getCotisationObligatoire()
            : false);
    organisation.setMontantCotisationAnnuelle(organisationMiseAJour.getMontantCotisationAnnuelle());
    organisation.setOrganisationPublique(
        organisationMiseAJour.getOrganisationPublique() != null ? organisationMiseAJour.getOrganisationPublique()
            : true);
    organisation.setAccepteNouveauxMembres(
        organisationMiseAJour.getAccepteNouveauxMembres() != null ? organisationMiseAJour.getAccepteNouveauxMembres()
            : true);
    // Hiérarchie
    organisation.setOrganisationParente(organisationMiseAJour.getOrganisationParente());

    // Conformité (Sprint 1) — propagation des nouveaux champs
    if (organisationMiseAJour.getReferentielComptable() != null) {
      organisation.setReferentielComptable(organisationMiseAJour.getReferentielComptable());
    }
    if (organisationMiseAJour.getComplianceOfficerId() != null) {
      organisation.setComplianceOfficerId(organisationMiseAJour.getComplianceOfficerId());
    }

    organisation.marquerCommeModifie(utilisateur);

    LOG.infof("Organisation mise à jour avec succès: ID=%s", id);
    return organisation;
  }

  /**
   * Supprime une organisation
   *
   * @param id          l'UUID de l'organisation
   * @param utilisateur l'utilisateur effectuant la suppression
   */
  @Transactional
  public void supprimerOrganisation(UUID id, String utilisateur) {
    LOG.infof("Suppression de l'organisation ID: %s", id);

    Organisation organisation = organisationRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id));

    // Vérifier qu'il n'y a pas de membres actifs
    if (organisation.getNombreMembres() > 0) {
      throw new IllegalStateException(
          "Impossible de supprimer une organisation avec des membres actifs");
    }

    // Soft delete - marquer comme inactive
    organisation.setActif(false);
    organisation.setStatut("DISSOUTE");
    organisation.marquerCommeModifie(utilisateur);

    LOG.infof("Organisation supprimée (soft delete) avec succès: ID=%s", id);
  }

  /**
   * Trouve une organisation par son ID
   *
   * @param id l'UUID de l'organisation
   * @return Optional contenant l'organisation si trouvée
   */
  public Optional<Organisation> trouverParId(UUID id) {
    return organisationRepository.findByIdOptional(id);
  }

  /**
   * Trouve une organisation par son email
   *
   * @param email l'email de l'organisation
   * @return Optional contenant l'organisation si trouvée
   */
  public Optional<Organisation> trouverParEmail(String email) {
    return organisationRepository.findByEmail(email);
  }

  /**
   * Liste les organisations auxquelles l'utilisateur connecté (membre) appartient.
   * Utilisé pour un administrateur d'organisation qui ne doit voir que son/ses organisation(s).
   *
   * @param emailUtilisateur email du principal (SecurityIdentity)
   * @return liste des organisations du membre, ou liste vide si membre non trouvé
   */
  @Transactional
  public List<Organisation> listerOrganisationsPourUtilisateur(String emailUtilisateur) {
    if (emailUtilisateur == null || emailUtilisateur.isBlank()) {
      return List.of();
    }
    return membreRepository.findByEmail(emailUtilisateur)
        .map(m -> m.getMembresOrganisations().stream()
            .map(mo -> mo.getOrganisation())
            .distinct()
            .collect(Collectors.toList()))
        .orElse(List.of());
  }

  /**
   * Associe un utilisateur (par email) à une organisation.
   * Réservé au SUPER_ADMIN. Crée un Membre minimal si aucun n'existe pour cet email,
   * puis crée le lien MembreOrganisation (idempotent si déjà associé).
   *
   * @param email           email de l'utilisateur (doit correspondre à un compte Keycloak / Membre)
   * @param organisationId  UUID de l'organisation
   * @throws NotFoundException si l'organisation n'existe pas
   */
  @Transactional
  public void associerUtilisateurAOrganisation(String email, UUID organisationId) {
    if (email == null || email.isBlank()) {
      throw new IllegalArgumentException("L'email est obligatoire");
    }
    if (organisationId == null) {
      throw new IllegalArgumentException("L'organisation est obligatoire");
    }
    Organisation organisation = organisationRepository.findByIdOptional(organisationId)
        .orElseThrow(() -> new NotFoundException("Organisation non trouvée: " + organisationId));
    String emailNorm = email.trim().toLowerCase();
    Membre membre = membreRepository.findByEmail(emailNorm).orElseGet(() -> {
      Membre nouveau = creerMembreMinimalPourEmail(emailNorm);
      membreRepository.persist(nouveau);
      LOG.infof("Membre minimal créé pour associer l'utilisateur %s à l'organisation %s", emailNorm, organisation.getNom());
      return nouveau;
    });
    if (membreOrganisationRepository.findByMembreIdAndOrganisationId(membre.getId(), organisationId).isPresent()) {
      LOG.infof("L'utilisateur %s est déjà associé à l'organisation %s", emailNorm, organisation.getNom());
      return;
    }
    MembreOrganisation mo = MembreOrganisation.builder()
        .membre(membre)
        .organisation(organisation)
        .statutMembre(StatutMembre.ACTIF)
        .dateAdhesion(LocalDate.now())
        .build();
    membreOrganisationRepository.persist(mo);
    organisation.ajouterMembre();
    organisationRepository.persist(organisation);
    LOG.infof("Utilisateur %s associé à l'organisation %s (MembreOrganisation créé)", emailNorm, organisation.getNom());
  }

  /**
   * Crée un Membre minimal à partir d'un email (pour associer un compte Keycloak sans fiche membre).
   */
  private Membre creerMembreMinimalPourEmail(String email) {
    String partieLocale = email.contains("@") ? email.substring(0, email.indexOf('@')) : email;
    String prenom = partieLocale.contains(".") ? partieLocale.substring(0, partieLocale.indexOf('.')) : "Admin";
    String nom = partieLocale.contains(".") ? partieLocale.substring(partieLocale.indexOf('.') + 1) : partieLocale;
    if (nom.isBlank()) nom = "Utilisateur";
    if (prenom.isBlank()) prenom = "Admin";
    prenom = prenom.substring(0, 1).toUpperCase() + (prenom.length() > 1 ? prenom.substring(1).toLowerCase() : "");
    nom = nom.substring(0, 1).toUpperCase() + (nom.length() > 1 ? nom.substring(1).toLowerCase() : "");
    String numeroMembre = "UF-ADM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    Membre m = Membre.builder()
        .email(email)
        .numeroMembre(numeroMembre)
        .prenom(prenom)
        .nom(nom)
        .dateNaissance(LocalDate.now().minusYears(25))
        .statutCompte("ACTIF")
        .build();
    m.setActif(Boolean.TRUE); // actif est dans BaseEntity, pas dans MembreBuilder
    return m;
  }

  /**
   * Liste toutes les organisations actives
   *
   * @return liste des organisations actives
   */
  public List<Organisation> listerOrganisationsActives() {
    return organisationRepository.findAllActives();
  }

  /**
   * Liste toutes les organisations actives avec pagination
   *
   * @param page numéro de page
   * @param size taille de la page
   * @return liste paginée des organisations actives
   */
  public List<Organisation> listerOrganisationsActives(int page, int size) {
    return organisationRepository.findAllActives(Page.of(page, size), Sort.by("nom").ascending());
  }

  /**
   * Compte le nombre d'organisations actives
   *
   * @return nombre total d'organisations actives
   */
  public long compterOrganisationsActives() {
    return organisationRepository.countActives();
  }

  /**
   * Recherche d'organisations par nom
   *
   * @param recherche terme de recherche
   * @param page      numéro de page
   * @param size      taille de la page
   * @return liste paginée des organisations correspondantes
   */
  public List<Organisation> rechercherOrganisations(String recherche, int page, int size) {
    return organisationRepository.findByNomOrNomCourt(
        recherche, Page.of(page, size), Sort.by("nom").ascending());
  }

  public long rechercherOrganisationsCount(String recherche) {
    if (recherche == null || recherche.trim().isEmpty()) {
      return organisationRepository.count();
    }
    String pattern = "%" + recherche.trim().toLowerCase() + "%";
    return organisationRepository.getEntityManager()
        .createQuery(
            "SELECT COUNT(o) FROM Organisation o WHERE LOWER(o.nom) LIKE :p OR LOWER(o.description) LIKE :p",
            Long.class)
        .setParameter("p", pattern)
        .getSingleResult();
  }

  /**
   * Recherche avancée d'organisations
   *
   * @param nom              nom (optionnel)
   * @param typeOrganisation type (optionnel)
   * @param statut           statut (optionnel)
   * @param ville            ville (optionnel)
   * @param region           région (optionnel)
   * @param pays             pays (optionnel)
   * @param page             numéro de page
   * @param size             taille de la page
   * @return liste filtrée des organisations
   */
  public List<Organisation> rechercheAvancee(
      String nom,
      String typeOrganisation,
      String statut,
      String ville,
      String region,
      String pays,
      int page,
      int size) {
    return organisationRepository.rechercheAvancee(
        nom, typeOrganisation, statut, ville, region, pays, Page.of(page, size));
  }

  /**
   * Active une organisation
   *
   * @param id          l'ID de l'organisation
   * @param utilisateur l'utilisateur effectuant l'activation
   * @return l'organisation activée
   */
  @Transactional
  public Organisation activerOrganisation(UUID id, String utilisateur) {
    LOG.infof("Activation de l'organisation ID: %s", id);

    Organisation organisation = organisationRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id));

    organisation.activer(utilisateur);

    LOG.infof("Organisation activée avec succès: ID=%s", id);
    return organisation;
  }

  /**
   * Suspend une organisation
   *
   * @param id          l'UUID de l'organisation
   * @param utilisateur l'utilisateur effectuant la suspension
   * @return l'organisation suspendue
   */
  @Transactional
  public Organisation suspendreOrganisation(UUID id, String utilisateur) {
    LOG.infof("Suspension de l'organisation ID: %s", id);

    Organisation organisation = organisationRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Organisation non trouvée avec l'ID: " + id));

    organisation.suspendre(utilisateur);

    LOG.infof("Organisation suspendue avec succès: ID=%s", id);
    return organisation;
  }

  /**
   * Obtient les statistiques des organisations (clés compatibles client DTO
   * StatistiquesAssociationDTO).
   *
   * @return map contenant les statistiques
   */
  public Map<String, Object> obtenirStatistiques() {
    LOG.info("Calcul des statistiques des organisations");

    long total = organisationRepository.count();
    long actives = organisationRepository.countActives();
    long inactives = total - actives;
    long suspendues = organisationRepository.countByStatut("SUSPENDUE");
    long dissoutes = organisationRepository.countByStatut("DISSOLUE");
    long nouvelles30Jours = organisationRepository.countNouvellesOrganisations(LocalDate.now().minusDays(30));
    double tauxActivite = total > 0 ? (actives * 100.0 / total) : 0.0;

    List<Organisation> all = organisationRepository.listAll();
    Map<String, Long> repartitionType = all.stream()
        .collect(Collectors.groupingBy(
            o -> o.getTypeOrganisation() != null ? o.getTypeOrganisation() : "NON_DEFINI",
            Collectors.counting()));
    Map<String, Long> repartitionRegion = adresseRepository
        .find("organisation IS NOT NULL AND region IS NOT NULL")
        .list()
        .stream()
        .collect(Collectors.groupingBy(
            a -> a.getRegion(),
            Collectors.counting()));

    Map<String, Object> map = new HashMap<>();
    map.put("totalAssociations", total);
    map.put("associationsActives", actives);
    map.put("associationsInactives", inactives);
    map.put("associationsSuspendues", suspendues);
    map.put("associationsDissoutes", dissoutes);
    map.put("nouvellesAssociations30Jours", nouvelles30Jours);
    map.put("tauxActivite", tauxActivite);
    map.put("repartitionParType", repartitionType);
    map.put("repartitionParRegion", repartitionRegion);
    return map;
  }

  /**
   * Obtient les statistiques scopées à une seule organisation (pour ADMIN_ORGANISATION).
   *
   * @param organisationId ID de l'organisation
   * @return map contenant les statistiques de l'organisation
   */
  public Map<String, Object> obtenirStatistiquesParOrganisation(UUID organisationId) {
    LOG.infof("Calcul des statistiques pour l'organisation %s", organisationId);

    Organisation org = organisationRepository.findById(organisationId);
    if (org == null) {
      return Map.of("error", "Organisation non trouvée");
    }

    long membresActifs = membreOrganisationRepository
        .findMembresActifsParOrganisation(organisationId).size();
    long totalMembres = membreOrganisationRepository
        .findAllByOrganisationId(organisationId).size();

    Map<String, Object> map = new HashMap<>();
    map.put("totalAssociations", 1L);
    map.put("associationsActives", org.getActif() != null && org.getActif() ? 1L : 0L);
    map.put("associationsInactives", org.getActif() != null && org.getActif() ? 0L : 1L);
    map.put("associationsSuspendues", "SUSPENDUE".equals(org.getStatut()) ? 1L : 0L);
    map.put("associationsDissoutes", "DISSOLUE".equals(org.getStatut()) ? 1L : 0L);
    map.put("nouvellesAssociations30Jours", 0L);
    map.put("tauxActivite", org.getActif() != null && org.getActif() ? 100.0 : 0.0);
    map.put("totalMembres", totalMembres);
    map.put("membresActifs", membresActifs);
    map.put("repartitionParType", Map.of(
        org.getTypeOrganisation() != null ? org.getTypeOrganisation() : "NON_DEFINI", 1L));
    map.put("repartitionParRegion", Map.of());
    return map;
  }

  /**
   * Convertit une entité Organisation en DTO complet
   */
  public OrganisationResponse convertToResponse(Organisation organisation) {
    if (organisation == null) {
      return null;
    }

    OrganisationResponse dto = new OrganisationResponse();
    dto.setId(organisation.getId());
    dto.setNom(organisation.getNom());
    dto.setNomCourt(organisation.getNomCourt());
    dto.setDescription(organisation.getDescription());
    dto.setEmail(organisation.getEmail());
    dto.setTelephone(organisation.getTelephone());
    dto.setTelephoneSecondaire(organisation.getTelephoneSecondaire());
    dto.setEmailSecondaire(organisation.getEmailSecondaire());
    dto.setAdresse(organisation.getAdresse());
    dto.setVille(organisation.getVille());
    dto.setRegion(organisation.getRegion());
    dto.setPays(organisation.getPays());
    dto.setCodePostal(organisation.getCodePostal());
    dto.setLatitude(organisation.getLatitude());
    dto.setLongitude(organisation.getLongitude());
    dto.setSiteWeb(organisation.getSiteWeb());
    dto.setLogo(organisation.getLogo());
    dto.setReseauxSociaux(organisation.getReseauxSociaux());
    dto.setObjectifs(organisation.getObjectifs());
    dto.setActivitesPrincipales(organisation.getActivitesPrincipales());
    dto.setNombreMembres(organisation.getNombreMembres());
    if (organisation.getId() != null) {
      // Compte dynamique des administrateurs (rôle ADMIN_ORGANISATION actif)
      // — le champ Organisation.nombreAdministrateurs n'est pas tenu à jour.
      long countAdmins = membreRoleRepository.countAdminsByOrganisationId(organisation.getId());
      dto.setNombreAdministrateurs((int) countAdmins);

      long countEvenements = evenementRepository.countActifsByOrganisationId(organisation.getId());
      dto.setNombreEvenements((int) countEvenements);
    } else {
      dto.setNombreAdministrateurs(0);
      dto.setNombreEvenements(0);
    }
    dto.setBudgetAnnuel(organisation.getBudgetAnnuel());
    dto.setDevise(organisation.getDevise());
    dto.setDateFondation(organisation.getDateFondation());
    dto.setNumeroEnregistrement(organisation.getNumeroEnregistrement());
    dto.setNiveauHierarchique(organisation.getNiveauHierarchique());

    if (organisation.getOrganisationParente() != null) {
      dto.setOrganisationParenteId(organisation.getOrganisationParente().getId());
      dto.setOrganisationParenteNom(organisation.getOrganisationParente().getNom());
    }

    dto.setTypeOrganisation(organisation.getTypeOrganisation());
    dto.setStatut(organisation.getStatut());

    // Résolution des libellés
    if (organisation.getTypeOrganisation() != null) {
      typeReferenceRepository.findByDomaineAndCode("TYPE_ORGANISATION", organisation.getTypeOrganisation())
          .ifPresent(ref -> dto.setTypeOrganisationLibelle(ref.getLibelle()));
      if (dto.getTypeOrganisationLibelle() == null) {
        dto.setTypeOrganisationLibelle(organisation.getTypeOrganisation());
      }
    }
    if (organisation.getStatut() != null) {
      typeReferenceRepository.findByDomaineAndCode("STATUT_ORGANISATION", organisation.getStatut())
          .ifPresent(ref -> {
            dto.setStatutLibelle(ref.getLibelle());
            dto.setStatutSeverity(ref.getCouleur()); // ou severity si dispo
          });
    }

    dto.setDateCreation(organisation.getDateCreation());
    dto.setDateModification(organisation.getDateModification());
    dto.setCreePar(organisation.getCreePar());
    dto.setModifiePar(organisation.getModifiePar());
    dto.setActif(organisation.getActif());
    dto.setVersion(organisation.getVersion());

    dto.setOrganisationPublique(organisation.getOrganisationPublique());
    dto.setAccepteNouveauxMembres(organisation.getAccepteNouveauxMembres());
    dto.setCotisationObligatoire(organisation.getCotisationObligatoire());
    dto.setMontantCotisationAnnuelle(organisation.getMontantCotisationAnnuelle());

    // Conformité (Sprint 1)
    if (organisation.getReferentielComptable() != null) {
      dto.setReferentielComptable(organisation.getReferentielComptable().name());
    }
    dto.setComplianceOfficerId(organisation.getComplianceOfficerId());

    return dto;
  }

  /**
   * Convertit une entité Organisation en Summary DTO
   */
  public OrganisationSummaryResponse convertToSummaryResponse(Organisation organisation) {
    if (organisation == null)
      return null;

    String typeLibelle = organisation.getTypeOrganisation();
    if (organisation.getTypeOrganisation() != null) {
      typeLibelle = typeReferenceRepository
          .findByDomaineAndCode("TYPE_ORGANISATION", organisation.getTypeOrganisation())
          .map(dev.lions.unionflow.server.entity.TypeReference::getLibelle)
          .orElse(organisation.getTypeOrganisation());
    }

    String statutLibelle = organisation.getStatut();
    String statutSeverity = null;
    if (organisation.getStatut() != null) {
      var refOpt = typeReferenceRepository.findByDomaineAndCode("STATUT_ORGANISATION", organisation.getStatut());
      if (refOpt.isPresent()) {
        statutLibelle = refOpt.get().getLibelle();
        statutSeverity = refOpt.get().getCouleur();
      }
    }

    return new OrganisationSummaryResponse(
        organisation.getId(),
        organisation.getNom(),
        organisation.getNomCourt(),
        organisation.getTypeOrganisation(),
        typeLibelle,
        organisation.getStatut(),
        statutLibelle,
        statutSeverity,
        organisation.getNombreMembres(),
        organisation.getActif(),
        organisation.getVille(),
        organisation.getPays());
  }

  /**
   * Crée une entité Organisation depuis CreateOrganisationRequest
   */
  public Organisation convertFromCreateRequest(CreateOrganisationRequest req) {
    if (req == null)
      return null;
    return Organisation.builder()
        .nom(req.nom())
        .nomCourt(req.nomCourt())
        .description(req.description())
        .email(req.email())
        .telephone(req.telephone())
        .telephoneSecondaire(req.telephoneSecondaire())
        .emailSecondaire(req.emailSecondaire())
        .latitude(req.latitude())
        .longitude(req.longitude())
        .siteWeb(req.siteWeb())
        .logo(req.logo())
        .reseauxSociaux(req.reseauxSociaux())
        .objectifs(req.objectifs())
        .activitesPrincipales(req.activitesPrincipales())
        .certifications(req.certifications())
        .partenaires(req.partenaires())
        .notes(req.notes())
        .dateFondation(req.dateFondation())
        .numeroEnregistrement(req.numeroEnregistrement())
        .typeOrganisation(req.typeOrganisation() != null ? req.typeOrganisation() : "ASSOCIATION")
        .statut(req.statut() != null ? req.statut() : "ACTIVE")
        .budgetAnnuel(req.budgetAnnuel())
        .devise(req.devise() != null ? req.devise() : defaultsService.getDevise())
        .cotisationObligatoire(req.cotisationObligatoire() != null ? req.cotisationObligatoire() : false)
        .montantCotisationAnnuelle(req.montantCotisationAnnuelle())
        .adresse(req.adresse())
        .ville(req.ville())
        .region(req.region())
        .pays(req.pays())
        .codePostal(req.codePostal())
        .organisationPublique(req.organisationPublique() != null ? req.organisationPublique() : true)
        .accepteNouveauxMembres(req.accepteNouveauxMembres() != null ? req.accepteNouveauxMembres() : true)
        .build();
  }

  /**
   * Crée une entité Organisation depuis UpdateOrganisationRequest
   */
  public Organisation convertFromUpdateRequest(UpdateOrganisationRequest req) {
    if (req == null)
      return null;
    return Organisation.builder()
        .nom(req.nom())
        .nomCourt(req.nomCourt())
        .description(req.description())
        .email(req.email())
        .telephone(req.telephone())
        .telephoneSecondaire(req.telephoneSecondaire())
        .emailSecondaire(req.emailSecondaire())
        .latitude(req.latitude())
        .longitude(req.longitude())
        .siteWeb(req.siteWeb())
        .logo(req.logo())
        .reseauxSociaux(req.reseauxSociaux())
        .objectifs(req.objectifs())
        .activitesPrincipales(req.activitesPrincipales())
        .certifications(req.certifications())
        .partenaires(req.partenaires())
        .notes(req.notes())
        .dateFondation(req.dateFondation())
        .numeroEnregistrement(req.numeroEnregistrement())
        .typeOrganisation(req.typeOrganisation())
        .statut(req.statut())
        .budgetAnnuel(req.budgetAnnuel())
        .devise(req.devise() != null ? req.devise() : defaultsService.getDevise())
        .cotisationObligatoire(req.cotisationObligatoire() != null ? req.cotisationObligatoire() : false)
        .montantCotisationAnnuelle(req.montantCotisationAnnuelle())
        .adresse(req.adresse())
        .ville(req.ville())
        .region(req.region())
        .pays(req.pays())
        .codePostal(req.codePostal())
        .organisationPublique(req.organisationPublique() != null ? req.organisationPublique() : true)
        .accepteNouveauxMembres(req.accepteNouveauxMembres() != null ? req.accepteNouveauxMembres() : true)
        .referentielComptable(parseReferentielComptable(req.referentielComptable(), req.typeOrganisation()))
        .complianceOfficerId(req.complianceOfficerId())
        .build();
  }

  /**
   * Parse le référentiel comptable depuis la requête. Si null/vide, applique le défaut
   * basé sur le type d'organisation (Sprint 1 — multi-référentiel).
   */
  private dev.lions.unionflow.server.entity.ReferentielComptable parseReferentielComptable(
      String input, String typeOrganisation) {
    if (input == null || input.isBlank()) {
      return dev.lions.unionflow.server.entity.ReferentielComptable.defaultFor(typeOrganisation);
    }
    try {
      return dev.lions.unionflow.server.entity.ReferentielComptable.valueOf(input);
    } catch (IllegalArgumentException e) {
      return dev.lions.unionflow.server.entity.ReferentielComptable.defaultFor(typeOrganisation);
    }
  }

  /**
   * Retourne la liste des organisations d'un membre (pour le sélecteur multi-org).
   * Inclut les infos nécessaires au sélecteur : id, nom, type, catégorie, modules, rôle du membre.
   */
  public java.util.List<java.util.Map<String, Object>> listerOrganisationsParMembre(java.util.UUID membreId) {
    java.util.List<MembreOrganisation> liens = membreOrganisationRepository.findOrganisationsActivesParMembre(membreId);
    return liens.stream().map(lien -> {
      Organisation org = lien.getOrganisation();
      java.util.Map<String, Object> entry = new java.util.LinkedHashMap<>();
      entry.put("organisationId",    org.getId());
      entry.put("nom",               org.getNom());
      entry.put("nomCourt",          org.getNomCourt());
      entry.put("typeOrganisation",  org.getTypeOrganisation());
      entry.put("categorieType",     org.getCategorieType());
      entry.put("modulesActifs",     org.getModulesActifs());
      entry.put("statut",            org.getStatut());
      entry.put("statutMembre",      lien.getStatutMembre() != null ? lien.getStatutMembre().name() : null);
      entry.put("roleOrg",           lien.getRoleOrg());
      entry.put("dateAdhesion",      lien.getDateAdhesion());
      return entry;
    }).toList();
  }
}
