package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.adresse.AdresseDTO;
import dev.lions.unionflow.server.api.enums.adresse.TypeAdresse;
import dev.lions.unionflow.server.entity.Adresse;
import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.AdresseRepository;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
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.UUID;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;

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

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

  @Inject AdresseRepository adresseRepository;

  @Inject OrganisationRepository organisationRepository;

  @Inject MembreRepository membreRepository;

  @Inject EvenementRepository evenementRepository;

  /**
   * Crée une nouvelle adresse
   *
   * @param adresseDTO DTO de l'adresse à créer
   * @return DTO de l'adresse créée
   */
  @Transactional
  public AdresseDTO creerAdresse(AdresseDTO adresseDTO) {
    LOG.infof("Création d'une nouvelle adresse de type: %s", adresseDTO.getTypeAdresse());

    Adresse adresse = convertToEntity(adresseDTO);

    // Gestion de l'adresse principale
    if (Boolean.TRUE.equals(adresseDTO.getPrincipale())) {
      desactiverAutresPrincipales(adresseDTO);
    }

    adresseRepository.persist(adresse);
    LOG.infof("Adresse créée avec succès: ID=%s", adresse.getId());

    return convertToDTO(adresse);
  }

  /**
   * Met à jour une adresse existante
   *
   * @param id ID de l'adresse
   * @param adresseDTO DTO avec les nouvelles données
   * @return DTO de l'adresse mise à jour
   */
  @Transactional
  public AdresseDTO mettreAJourAdresse(UUID id, AdresseDTO adresseDTO) {
    LOG.infof("Mise à jour de l'adresse ID: %s", id);

    Adresse adresse =
        adresseRepository
            .findAdresseById(id)
            .orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));

    // Mise à jour des champs
    updateFromDTO(adresse, adresseDTO);

    // Gestion de l'adresse principale
    if (Boolean.TRUE.equals(adresseDTO.getPrincipale())) {
      desactiverAutresPrincipales(adresseDTO);
    }

    adresseRepository.persist(adresse);
    LOG.infof("Adresse mise à jour avec succès: ID=%s", id);

    return convertToDTO(adresse);
  }

  /**
   * Supprime une adresse
   *
   * @param id ID de l'adresse
   */
  @Transactional
  public void supprimerAdresse(UUID id) {
    LOG.infof("Suppression de l'adresse ID: %s", id);

    Adresse adresse =
        adresseRepository
            .findAdresseById(id)
            .orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));

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

  /**
   * Trouve une adresse par son ID
   *
   * @param id ID de l'adresse
   * @return DTO de l'adresse
   */
  public AdresseDTO trouverParId(UUID id) {
    return adresseRepository
        .findAdresseById(id)
        .map(this::convertToDTO)
        .orElseThrow(() -> new NotFoundException("Adresse non trouvée avec l'ID: " + id));
  }

  /**
   * Trouve toutes les adresses d'une organisation
   *
   * @param organisationId ID de l'organisation
   * @return Liste des adresses
   */
  public List<AdresseDTO> trouverParOrganisation(UUID organisationId) {
    return adresseRepository.findByOrganisationId(organisationId).stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  /**
   * Trouve toutes les adresses d'un membre
   *
   * @param membreId ID du membre
   * @return Liste des adresses
   */
  public List<AdresseDTO> trouverParMembre(UUID membreId) {
    return adresseRepository.findByMembreId(membreId).stream()
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  /**
   * Trouve l'adresse d'un événement
   *
   * @param evenementId ID de l'événement
   * @return DTO de l'adresse ou null
   */
  public AdresseDTO trouverParEvenement(UUID evenementId) {
    return adresseRepository
        .findByEvenementId(evenementId)
        .map(this::convertToDTO)
        .orElse(null);
  }

  /**
   * Trouve l'adresse principale d'une organisation
   *
   * @param organisationId ID de l'organisation
   * @return DTO de l'adresse principale ou null
   */
  public AdresseDTO trouverPrincipaleParOrganisation(UUID organisationId) {
    return adresseRepository
        .findPrincipaleByOrganisationId(organisationId)
        .map(this::convertToDTO)
        .orElse(null);
  }

  /**
   * Trouve l'adresse principale d'un membre
   *
   * @param membreId ID du membre
   * @return DTO de l'adresse principale ou null
   */
  public AdresseDTO trouverPrincipaleParMembre(UUID membreId) {
    return adresseRepository
        .findPrincipaleByMembreId(membreId)
        .map(this::convertToDTO)
        .orElse(null);
  }

  // ========================================
  // MÉTHODES PRIVÉES
  // ========================================

  /** Désactive les autres adresses principales pour la même entité */
  private void desactiverAutresPrincipales(AdresseDTO adresseDTO) {
    List<Adresse> autresPrincipales;

    if (adresseDTO.getOrganisationId() != null) {
      autresPrincipales =
          adresseRepository
              .find("organisation.id = ?1 AND principale = true", adresseDTO.getOrganisationId())
              .list();
    } else if (adresseDTO.getMembreId() != null) {
      autresPrincipales =
          adresseRepository
              .find("membre.id = ?1 AND principale = true", adresseDTO.getMembreId())
              .list();
    } else {
      return; // Pas d'entité associée
    }

    autresPrincipales.forEach(adr -> adr.setPrincipale(false));
  }

  /** Convertit une entité en DTO */
  private AdresseDTO convertToDTO(Adresse adresse) {
    if (adresse == null) {
      return null;
    }

    AdresseDTO dto = new AdresseDTO();
    dto.setId(adresse.getId());
    dto.setTypeAdresse(convertTypeAdresse(adresse.getTypeAdresse()));
    dto.setAdresse(adresse.getAdresse());
    dto.setComplementAdresse(adresse.getComplementAdresse());
    dto.setCodePostal(adresse.getCodePostal());
    dto.setVille(adresse.getVille());
    dto.setRegion(adresse.getRegion());
    dto.setPays(adresse.getPays());
    dto.setLatitude(adresse.getLatitude());
    dto.setLongitude(adresse.getLongitude());
    dto.setPrincipale(adresse.getPrincipale());
    dto.setLibelle(adresse.getLibelle());
    dto.setNotes(adresse.getNotes());

    if (adresse.getOrganisation() != null) {
      dto.setOrganisationId(adresse.getOrganisation().getId());
    }
    if (adresse.getMembre() != null) {
      dto.setMembreId(adresse.getMembre().getId());
    }
    if (adresse.getEvenement() != null) {
      dto.setEvenementId(adresse.getEvenement().getId());
    }

    dto.setAdresseComplete(adresse.getAdresseComplete());
    dto.setDateCreation(adresse.getDateCreation());
    dto.setDateModification(adresse.getDateModification());
    dto.setActif(adresse.getActif());

    return dto;
  }

  /** Convertit un DTO en entité */
  private Adresse convertToEntity(AdresseDTO dto) {
    if (dto == null) {
      return null;
    }

    Adresse adresse = new Adresse();
    adresse.setTypeAdresse(convertTypeAdresse(dto.getTypeAdresse()));
    adresse.setAdresse(dto.getAdresse());
    adresse.setComplementAdresse(dto.getComplementAdresse());
    adresse.setCodePostal(dto.getCodePostal());
    adresse.setVille(dto.getVille());
    adresse.setRegion(dto.getRegion());
    adresse.setPays(dto.getPays());
    adresse.setLatitude(dto.getLatitude());
    adresse.setLongitude(dto.getLongitude());
    adresse.setPrincipale(dto.getPrincipale() != null ? dto.getPrincipale() : false);
    adresse.setLibelle(dto.getLibelle());
    adresse.setNotes(dto.getNotes());

    // Relations
    if (dto.getOrganisationId() != null) {
      Organisation org =
          organisationRepository
              .findByIdOptional(dto.getOrganisationId())
              .orElseThrow(
                  () ->
                      new NotFoundException(
                          "Organisation non trouvée avec l'ID: " + dto.getOrganisationId()));
      adresse.setOrganisation(org);
    }

    if (dto.getMembreId() != null) {
      Membre membre =
          membreRepository
              .findByIdOptional(dto.getMembreId())
              .orElseThrow(
                  () -> new NotFoundException("Membre non trouvé avec l'ID: " + dto.getMembreId()));
      adresse.setMembre(membre);
    }

    if (dto.getEvenementId() != null) {
      Evenement evenement =
          evenementRepository
              .findByIdOptional(dto.getEvenementId())
              .orElseThrow(
                  () ->
                      new NotFoundException(
                          "Événement non trouvé avec l'ID: " + dto.getEvenementId()));
      adresse.setEvenement(evenement);
    }

    return adresse;
  }

  /** Met à jour une entité à partir d'un DTO */
  private void updateFromDTO(Adresse adresse, AdresseDTO dto) {
    if (dto.getTypeAdresse() != null) {
      adresse.setTypeAdresse(convertTypeAdresse(dto.getTypeAdresse()));
    }
    if (dto.getAdresse() != null) {
      adresse.setAdresse(dto.getAdresse());
    }
    if (dto.getComplementAdresse() != null) {
      adresse.setComplementAdresse(dto.getComplementAdresse());
    }
    if (dto.getCodePostal() != null) {
      adresse.setCodePostal(dto.getCodePostal());
    }
    if (dto.getVille() != null) {
      adresse.setVille(dto.getVille());
    }
    if (dto.getRegion() != null) {
      adresse.setRegion(dto.getRegion());
    }
    if (dto.getPays() != null) {
      adresse.setPays(dto.getPays());
    }
    if (dto.getLatitude() != null) {
      adresse.setLatitude(dto.getLatitude());
    }
    if (dto.getLongitude() != null) {
      adresse.setLongitude(dto.getLongitude());
    }
    if (dto.getPrincipale() != null) {
      adresse.setPrincipale(dto.getPrincipale());
    }
    if (dto.getLibelle() != null) {
      adresse.setLibelle(dto.getLibelle());
    }
    if (dto.getNotes() != null) {
      adresse.setNotes(dto.getNotes());
    }
  }

  /** Convertit TypeAdresse (entité) vers TypeAdresse (DTO) - même enum, pas de conversion nécessaire */
  private TypeAdresse convertTypeAdresse(TypeAdresse type) {
    return type != null ? type : TypeAdresse.AUTRE; // Même enum, valeur par défaut si null
  }
}

