package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.finance.request.CreateAdhesionRequest;
import dev.lions.unionflow.server.api.dto.finance.request.UpdateAdhesionRequest;
import dev.lions.unionflow.server.api.dto.finance.response.AdhesionResponse;

import dev.lions.unionflow.server.entity.DemandeAdhesion;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.AdhesionRepository;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.jwt.JsonWebToken;

/**
 * Service métier pour la gestion des demandes d'adhésion.
 *
 * @author UnionFlow Team
 * @version 2.0
 * @since 2025-02-18
 */
@ApplicationScoped
@Slf4j
public class AdhesionService {

  @Inject
  AdhesionRepository adhesionRepository;
  @Inject
  MembreRepository membreRepository;
  @Inject
  OrganisationRepository organisationRepository;
  @Inject
  MembreOrganisationRepository membreOrganisationRepository;
  @Inject
  MembreKeycloakSyncService keycloakSyncService;
  @Inject
  DefaultsService defaultsService;
  @Inject
  SecurityIdentity securityIdentity;
  @Inject
  JsonWebToken jwt;

  public List<AdhesionResponse> getAllAdhesions(int page, int size) {
    log.debug("Récupération des adhésions - page: {}, size: {}", page, size);
    jakarta.persistence.TypedQuery<DemandeAdhesion> query = adhesionRepository
        .getEntityManager()
        .createQuery(
            "SELECT a FROM DemandeAdhesion a ORDER BY a.dateDemande DESC",
            DemandeAdhesion.class);
    query.setFirstResult(page * size);
    query.setMaxResults(size);
    return query.getResultList().stream().map(this::convertToDTO).collect(Collectors.toList());
  }

  public AdhesionResponse getAdhesionById(@NotNull UUID id) {
    log.debug("Récupération de l'adhésion avec ID: {}", id);
    DemandeAdhesion adhesion = adhesionRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
    return convertToDTO(adhesion);
  }

  public AdhesionResponse getAdhesionByReference(@NotNull String numeroReference) {
    log.debug("Récupération de l'adhésion avec référence: {}", numeroReference);
    DemandeAdhesion adhesion = adhesionRepository
        .findByNumeroReference(numeroReference)
        .orElseThrow(
            () -> new NotFoundException(
                "Adhésion non trouvée avec la référence: " + numeroReference));
    return convertToDTO(adhesion);
  }

  @Transactional
  public AdhesionResponse createAdhesion(@Valid CreateAdhesionRequest request) {
    log.info(
        "Création d'une nouvelle adhésion pour le membre: {} et l'organisation: {}",
        request.membreId(),
        request.organisationId());

    Membre membre = membreRepository
        .findByIdOptional(request.membreId())
        .orElseThrow(
            () -> new NotFoundException(
                "Membre non trouvé avec l'ID: " + request.membreId()));

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

    DemandeAdhesion adhesion = convertToEntity(request);
    adhesion.setUtilisateur(membre);
    adhesion.setOrganisation(organisation);

    adhesionRepository.persist(adhesion);

    log.info(
        "Adhésion créée avec succès - ID: {}, Référence: {}",
        adhesion.getId(),
        adhesion.getNumeroReference());

    return convertToDTO(adhesion);
  }

  @Transactional
  public AdhesionResponse updateAdhesion(@NotNull UUID id, @Valid UpdateAdhesionRequest request) {
    log.info("Mise à jour de l'adhésion avec ID: {}", id);
    DemandeAdhesion adhesionExistante = adhesionRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));
    updateAdhesionFields(adhesionExistante, request);
    log.info("Adhésion mise à jour avec succès - ID: {}", id);
    return convertToDTO(adhesionExistante);
  }

  @Transactional
  public void deleteAdhesion(@NotNull UUID id) {
    log.info("Suppression de l'adhésion avec ID: {}", id);
    DemandeAdhesion adhesion = adhesionRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));

    if ("APPROUVEE".equals(adhesion.getStatut()) && adhesion.isPayeeIntegralement()) {
      throw new IllegalStateException("Impossible de supprimer une adhésion déjà payée intégralement");
    }

    adhesion.setStatut("ANNULEE");
    log.info("Adhésion annulée avec succès - ID: {}", id);
  }

  @Transactional
  public AdhesionResponse approuverAdhesion(@NotNull UUID id, String approuvePar) {
    log.info("Approbation de l'adhésion avec ID: {}", id);
    DemandeAdhesion adhesion = adhesionRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));

    verifierAccesOrganisation(adhesion);

    if (!adhesion.isEnAttente()) {
      throw new IllegalStateException("Seules les adhésions en attente peuvent être approuvées");
    }

    adhesion.setStatut("APPROUVEE");
    adhesion.setDateTraitement(LocalDateTime.now());
    adhesion.setObservations(
        approuvePar != null ? "Approuvée par : " + approuvePar : adhesion.getObservations());

    // Activer le compte membre et provisionner son accès Keycloak
    Membre membre = adhesion.getUtilisateur();
    if (membre != null) {
      membre.setStatutCompte("ACTIF");
      membre.setActif(true);
      try {
        keycloakSyncService.provisionKeycloakUser(membre.getId());
        log.info("Compte Keycloak provisionné pour le membre: {}", membre.getEmail());
      } catch (Exception e) {
        log.warn("Provisionnement Keycloak non bloquant pour {} : {}", membre.getEmail(), e.getMessage());
      }
    }

    log.info("Adhésion approuvée avec succès - ID: {}", id);
    return convertToDTO(adhesion);
  }

  @Transactional
  public AdhesionResponse rejeterAdhesion(@NotNull UUID id, String motifRejet) {
    log.info("Rejet de l'adhésion avec ID: {}", id);
    DemandeAdhesion adhesion = adhesionRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));

    verifierAccesOrganisation(adhesion);

    if (!adhesion.isEnAttente()) {
      throw new IllegalStateException("Seules les adhésions en attente peuvent être rejetées");
    }

    adhesion.setStatut("REJETEE");
    adhesion.setMotifRejet(motifRejet);
    adhesion.setDateTraitement(LocalDateTime.now());

    // Désactiver le compte membre
    Membre membre = adhesion.getUtilisateur();
    if (membre != null) {
      membre.setStatutCompte("DESACTIVE");
      membre.setActif(false);
    }

    log.info("Adhésion rejetée avec succès - ID: {}", id);
    return convertToDTO(adhesion);
  }

  @Transactional
  public AdhesionResponse enregistrerPaiement(
      @NotNull UUID id,
      BigDecimal montantPaye,
      String methodePaiement,
      String referencePaiement) {
    log.info("Enregistrement du paiement pour l'adhésion avec ID: {}", id);
    DemandeAdhesion adhesion = adhesionRepository
        .findByIdOptional(id)
        .orElseThrow(() -> new NotFoundException("Adhésion non trouvée avec l'ID: " + id));

    if (!"APPROUVEE".equals(adhesion.getStatut()) && !"EN_PAIEMENT".equals(adhesion.getStatut())) {
      throw new IllegalStateException(
          "Seules les adhésions approuvées peuvent receive un paiement");
    }

    adhesion.setMontantPaye(adhesion.getMontantPaye().add(montantPaye));

    if (adhesion.isPayeeIntegralement()) {
      adhesion.setStatut("APPROUVEE");
    }

    log.info("Paiement enregistré avec succès pour l'adhésion - ID: {}", id);
    return convertToDTO(adhesion);
  }

  public List<AdhesionResponse> getAdhesionsByMembre(@NotNull UUID membreId, int page, int size) {
    log.debug("Récupération des adhésions du membre: {}", membreId);
    if (!membreRepository.findByIdOptional(membreId).isPresent()) {
      throw new NotFoundException("Membre non trouvé avec l'ID: " + membreId);
    }
    return adhesionRepository.findByMembreId(membreId).stream()
        .skip((long) page * size)
        .limit(size)
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  public List<AdhesionResponse> getAdhesionsByOrganisation(
      @NotNull UUID organisationId, int page, int size) {
    log.debug("Récupération des adhésions de l'organisation: {}", organisationId);
    if (!organisationRepository.findByIdOptional(organisationId).isPresent()) {
      throw new NotFoundException("Organisation non trouvée avec l'ID: " + organisationId);
    }
    return adhesionRepository.findByOrganisationId(organisationId).stream()
        .skip((long) page * size)
        .limit(size)
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  public List<AdhesionResponse> getAdhesionsByStatut(@NotNull String statut, int page, int size) {
    log.debug("Récupération des adhésions avec statut: {}", statut);
    return adhesionRepository.findByStatut(statut).stream()
        .skip((long) page * size)
        .limit(size)
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  public List<AdhesionResponse> getAdhesionsEnAttente(int page, int size) {
    log.debug("Récupération des adhésions en attente");
    return adhesionRepository.findEnAttente().stream()
        .skip((long) page * size)
        .limit(size)
        .map(this::convertToDTO)
        .collect(Collectors.toList());
  }

  public Map<String, Object> getStatistiquesAdhesions() {
    log.debug("Calcul des statistiques des adhésions");
    long total = adhesionRepository.count();
    long approuvees = adhesionRepository.findByStatut("APPROUVEE").size();
    long enAttente = adhesionRepository.findEnAttente().size();
    long rejetees = adhesionRepository.findByStatut("REJETEE").size();

    return Map.of(
        "totalAdhesions", total,
        "adhesionsApprouvees", approuvees,
        "adhesionsEnAttente", enAttente,
        "adhesionsRejetees", rejetees,
        "tauxApprobation", total > 0 ? (approuvees * 100.0 / total) : 0.0,
        "tauxRejet", total > 0 ? (rejetees * 100.0 / total) : 0.0);
  }

  /**
   * Vérifie que l'ADMIN_ORGANISATION n'agit que sur les adhésions de sa propre organisation.
   * Les rôles SUPER_ADMIN et ADMIN ont accès sans restriction.
   */
  private void verifierAccesOrganisation(DemandeAdhesion adhesion) {
    if (!securityIdentity.hasRole("ADMIN_ORGANISATION")) {
      return; // SUPER_ADMIN / ADMIN : accès libre
    }

    UUID adhesionOrgId = adhesion.getOrganisation() != null ? adhesion.getOrganisation().getId() : null;
    if (adhesionOrgId == null) {
      throw new ForbiddenException("L'adhésion n'est rattachée à aucune organisation");
    }

    String keycloakSubject = jwt.getSubject();
    Membre adminMembre = membreRepository.findByKeycloakUserId(keycloakSubject)
        .orElseThrow(() -> new ForbiddenException("Compte admin introuvable pour le sujet JWT: " + keycloakSubject));

    boolean appartient = membreOrganisationRepository
        .findByMembreIdAndOrganisationId(adminMembre.getId(), adhesionOrgId)
        .isPresent();

    if (!appartient) {
      log.warn("ADMIN_ORGANISATION {} tente d'agir sur une adhésion de l'organisation {} qui n'est pas la sienne",
          keycloakSubject, adhesionOrgId);
      throw new ForbiddenException("Vous ne pouvez gérer que les adhésions de votre organisation");
    }
  }

  private AdhesionResponse convertToDTO(DemandeAdhesion adhesion) {
    AdhesionResponse response = new AdhesionResponse();
    response.setId(adhesion.getId());
    response.setNumeroReference(adhesion.getNumeroReference());

    if (adhesion.getUtilisateur() != null) {
      response.setMembreId(adhesion.getUtilisateur().getId());
      response.setNomMembre(adhesion.getUtilisateur().getNomComplet());
      response.setNumeroMembre(adhesion.getUtilisateur().getNumeroMembre());
      response.setEmailMembre(adhesion.getUtilisateur().getEmail());
    }

    if (adhesion.getOrganisation() != null) {
      response.setOrganisationId(adhesion.getOrganisation().getId());
      response.setNomOrganisation(adhesion.getOrganisation().getNom());
    }

    response.setDateDemande(
        adhesion.getDateDemande() != null ? adhesion.getDateDemande().toLocalDate() : null);
    response.setFraisAdhesion(adhesion.getFraisAdhesion());
    response.setMontantPaye(adhesion.getMontantPaye());
    response.setCodeDevise(adhesion.getCodeDevise());
    response.setStatut(adhesion.getStatut());
    response.setMotifRejet(adhesion.getMotifRejet());
    response.setObservations(adhesion.getObservations());

    if (adhesion.getDateTraitement() != null) {
      response.setDateApprobation(adhesion.getDateTraitement().toLocalDate());
    }
    if (adhesion.getTraitePar() != null) {
      response.setApprouvePar(adhesion.getTraitePar().getNomComplet());
    }

    response.setDateCreation(adhesion.getDateCreation());
    response.setDateModification(adhesion.getDateModification());
    response.setCreePar(adhesion.getCreePar());
    response.setModifiePar(adhesion.getModifiePar());
    response.setActif(adhesion.getActif());

    return response;
  }

  private DemandeAdhesion convertToEntity(CreateAdhesionRequest request) {
    return DemandeAdhesion.builder()
        .numeroReference(request.numeroReference())
        .dateDemande(request.dateDemande().atStartOfDay())
        .fraisAdhesion(request.fraisAdhesion())
        .montantPaye(BigDecimal.ZERO)
        .codeDevise(request.codeDevise())
        .statut("EN_ATTENTE")
        .observations(request.observations())
        .build();
  }

  private void updateAdhesionFields(DemandeAdhesion adhesion, UpdateAdhesionRequest request) {
    if (request.statut() != null)
      adhesion.setStatut(request.statut());
    if (request.montantPaye() != null)
      adhesion.setMontantPaye(request.montantPaye());
    if (request.motifRejet() != null)
      adhesion.setMotifRejet(request.motifRejet());
    if (request.observations() != null)
      adhesion.setObservations(request.observations());
    if (request.dateApprobation() != null) {
      adhesion.setDateTraitement(request.dateApprobation().atStartOfDay());
    }
    if (request.dateValidation() != null) {
      // Logic for validation date if needed
    }
  }
}
