package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.organisation.OrganisationDTO;
import dev.lions.unionflow.server.api.enums.organisation.StatutOrganisation;
import dev.lions.unionflow.server.api.enums.organisation.TypeOrganisation;
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.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
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;

  /**
   * Crée une nouvelle organisation
   *
   * @param organisation l'organisation à créer
   * @return l'organisation créée
   */
  @Transactional
  public Organisation creerOrganisation(Organisation organisation) {
    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 IllegalArgumentException("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");
    }

    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 IllegalArgumentException("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 les autres champs
    organisation.setNomCourt(organisationMiseAJour.getNomCourt());
    organisation.setDescription(organisationMiseAJour.getDescription());
    organisation.setTelephone(organisationMiseAJour.getTelephone());
    organisation.setAdresse(organisationMiseAJour.getAdresse());
    organisation.setVille(organisationMiseAJour.getVille());
    organisation.setCodePostal(organisationMiseAJour.getCodePostal());
    organisation.setRegion(organisationMiseAJour.getRegion());
    organisation.setPays(organisationMiseAJour.getPays());
    organisation.setSiteWeb(organisationMiseAJour.getSiteWeb());
    organisation.setObjectifs(organisationMiseAJour.getObjectifs());
    organisation.setActivitesPrincipales(organisationMiseAJour.getActivitesPrincipales());

    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 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());
  }

  /**
   * 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());
  }

  /**
   * 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
   *
   * @return map contenant les statistiques
   */
  public Map<String, Object> obtenirStatistiques() {
    LOG.info("Calcul des statistiques des organisations");

    long totalOrganisations = organisationRepository.count();
    long organisationsActives = organisationRepository.countActives();
    long organisationsInactives = totalOrganisations - organisationsActives;
    long nouvellesOrganisations30Jours =
        organisationRepository.countNouvellesOrganisations(LocalDate.now().minusDays(30));

    return Map.of(
        "totalOrganisations", totalOrganisations,
        "organisationsActives", organisationsActives,
        "organisationsInactives", organisationsInactives,
        "nouvellesOrganisations30Jours", nouvellesOrganisations30Jours,
        "tauxActivite",
            totalOrganisations > 0 ? (organisationsActives * 100.0 / totalOrganisations) : 0.0,
        "timestamp", LocalDateTime.now());
  }

  /**
   * Convertit une entité Organisation en DTO
   *
   * @param organisation l'entité à convertir
   * @return le DTO correspondant
   */
  public OrganisationDTO convertToDTO(Organisation organisation) {
    if (organisation == null) {
      return null;
    }

    OrganisationDTO dto = new OrganisationDTO();

    // Conversion de l'ID UUID vers UUID (pas de conversion nécessaire maintenant)
    dto.setId(organisation.getId());

    // Informations de base
    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.setCodePostal(organisation.getCodePostal());
    dto.setRegion(organisation.getRegion());
    dto.setPays(organisation.getPays());
    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());
    dto.setNombreAdministrateurs(organisation.getNombreAdministrateurs());
    dto.setBudgetAnnuel(organisation.getBudgetAnnuel());
    dto.setDevise(organisation.getDevise());
    dto.setDateFondation(organisation.getDateFondation());
    dto.setNumeroEnregistrement(organisation.getNumeroEnregistrement());
    dto.setNiveauHierarchique(organisation.getNiveauHierarchique());

    // Conversion de l'organisation parente (UUID → UUID, pas de conversion nécessaire)
    if (organisation.getOrganisationParenteId() != null) {
      dto.setOrganisationParenteId(organisation.getOrganisationParenteId());
    }

    // Conversion du type d'organisation (String → Enum)
    if (organisation.getTypeOrganisation() != null) {
      try {
        dto.setTypeOrganisation(
            TypeOrganisation.valueOf(organisation.getTypeOrganisation().toUpperCase()));
      } catch (IllegalArgumentException e) {
        // Valeur par défaut si la conversion échoue
        LOG.warnf(
            "Type d'organisation inconnu: %s, utilisation de ASSOCIATION par défaut",
            organisation.getTypeOrganisation());
        dto.setTypeOrganisation(TypeOrganisation.ASSOCIATION);
      }
    } else {
      dto.setTypeOrganisation(TypeOrganisation.ASSOCIATION);
    }

    // Conversion du statut (String → Enum)
    if (organisation.getStatut() != null) {
      try {
        dto.setStatut(
            StatutOrganisation.valueOf(organisation.getStatut().toUpperCase()));
      } catch (IllegalArgumentException e) {
        // Valeur par défaut si la conversion échoue
        LOG.warnf(
            "Statut d'organisation inconnu: %s, utilisation de ACTIVE par défaut",
            organisation.getStatut());
        dto.setStatut(StatutOrganisation.ACTIVE);
      }
    } else {
      dto.setStatut(StatutOrganisation.ACTIVE);
    }

    // Champs de base DTO
    dto.setDateCreation(organisation.getDateCreation());
    dto.setDateModification(organisation.getDateModification());
    dto.setActif(organisation.getActif());
    dto.setVersion(organisation.getVersion() != null ? organisation.getVersion() : 0L);

    // Champs par défaut
    dto.setOrganisationPublique(
        organisation.getOrganisationPublique() != null
            ? organisation.getOrganisationPublique()
            : true);
    dto.setAccepteNouveauxMembres(
        organisation.getAccepteNouveauxMembres() != null
            ? organisation.getAccepteNouveauxMembres()
            : true);
    dto.setCotisationObligatoire(
        organisation.getCotisationObligatoire() != null
            ? organisation.getCotisationObligatoire()
            : false);
    dto.setMontantCotisationAnnuelle(organisation.getMontantCotisationAnnuelle());

    return dto;
  }

  /**
   * Convertit un DTO en entité Organisation
   *
   * @param dto le DTO à convertir
   * @return l'entité correspondante
   */
  public Organisation convertFromDTO(OrganisationDTO dto) {
    if (dto == null) {
      return null;
    }

    return Organisation.builder()
        .nom(dto.getNom())
        .nomCourt(dto.getNomCourt())
        .description(dto.getDescription())
        .email(dto.getEmail())
        .telephone(dto.getTelephone())
        .adresse(dto.getAdresse())
        .ville(dto.getVille())
        .codePostal(dto.getCodePostal())
        .region(dto.getRegion())
        .pays(dto.getPays())
        .siteWeb(dto.getSiteWeb())
        .objectifs(dto.getObjectifs())
        .activitesPrincipales(dto.getActivitesPrincipales())
        .build();
  }
}
