package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.membre.CompteAdherentResponse;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.repository.CotisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.mutuelle.credit.DemandeCreditRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import dev.lions.unionflow.server.service.support.SecuriteHelper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import org.jboss.logging.Logger;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.UUID;

/**
 * Service qui agrège les données financières d'un membre en un "compte adhérent" unifié.
 *
 * <p>Ce compte n'est pas persisté en base — il est calculé à la volée depuis :
 * <ul>
 *   <li>La table {@code cotisations} (historique des paiements)</li>
 *   <li>La table {@code comptes_epargne} (soldes épargne)</li>
 *   <li>La table {@code utilisateurs} (infos membre)</li>
 * </ul>
 *
 * <p>Règles métier mutuelle appliquées :
 * <ul>
 *   <li>Capacité d'emprunt = 3 × solde épargne disponible (règle classique)</li>
 *   <li>Solde total disponible = cotisations tout temps + épargne disponible - épargne bloquée</li>
 *   <li>Taux d'engagement = (cotisationsPayées / cotisationsTotal) × 100</li>
 * </ul>
 */
@ApplicationScoped
public class CompteAdherentService {

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

    /** Multiplicateur capacité d'emprunt = 3 × épargne (règle mutuelle standard) */
    private static final BigDecimal MULTIPLICATEUR_EMPRUNT = new BigDecimal("3");

    @Inject
    SecuriteHelper securiteHelper;

    @Inject
    MembreRepository membreRepository;

    @Inject
    CotisationRepository cotisationRepository;

    @Inject
    CompteEpargneRepository compteEpargneRepository;
    
    @Inject
    DemandeCreditRepository demandeCreditRepository;

    // ── Point d'entrée principal ───────────────────────────────────────────

    /**
     * Calcule et retourne le compte adhérent du membre connecté.
     *
     * @return {@link CompteAdherentResponse} avec toutes les informations financières agrégées
     * @throws NotFoundException si le membre n'est pas trouvé ou inactif
     */
    public CompteAdherentResponse getMonCompte() {
        String email = securiteHelper.resolveEmail();
        if (email == null || email.isBlank()) {
            throw new NotFoundException("Identité non disponible pour le compte adhérent.");
        }

        LOG.infof("Calcul du compte adhérent pour: %s", email);

        Membre membre = membreRepository.findByEmail(email.trim())
                .or(() -> membreRepository.findByEmail(email.trim().toLowerCase()))
                .filter(m -> m.getActif() == null || m.getActif())
                .orElseThrow(() -> new NotFoundException("Membre non trouvé: " + email));

        return buildCompteAdherent(membre);
    }

    // ── Construction du compte agrégé ─────────────────────────────────────

    private CompteAdherentResponse buildCompteAdherent(Membre membre) {
        UUID membreId = membre.getId();
        LocalDate today = LocalDate.now();

        // ── Identité ──────────────────────────────────────────────────────
        String nomComplet = (membre.getPrenom() != null ? membre.getPrenom() : "")
                + " " + (membre.getNom() != null ? membre.getNom() : "");
        nomComplet = nomComplet.trim();

        // Organisation principale (la plus récente adhesion active)
        String orgNom = membre.getMembresOrganisations() != null
                ? membre.getMembresOrganisations().stream()
                        .filter(mo -> mo != null && mo.getOrganisation() != null)
                        .filter(MembreOrganisation::isActif)
                        .max(Comparator.comparing(mo -> mo.getDateAdhesion() != null ? mo.getDateAdhesion() : LocalDate.MIN))
                        .map(mo -> mo.getOrganisation().getNom())
                        .orElse(null)
                : null;


        LocalDate dateAdhesion = membre.getDateCreation() != null
                ? membre.getDateCreation().toLocalDate()
                : null;

        // ── Cotisations ───────────────────────────────────────────────────
        BigDecimal soldeCotisations = nvl(cotisationRepository.calculerTotalCotisationsPayeesToutTemps(membreId));
        long nbPayees  = cotisationRepository.countPayeesByMembreId(membreId);
        long nbTotal   = cotisationRepository.countByMembreId(membreId);
        long nbRetard  = cotisationRepository.countRetardByMembreId(membreId);

        Integer tauxEngagement = null;
        if (nbTotal > 0) {
            tauxEngagement = (int) (nbPayees * 100L / nbTotal);
        }

        // ── Épargne ───────────────────────────────────────────────────────
        BigDecimal soldeEpargne  = nvl(compteEpargneRepository.sumSoldeActuelByMembreId(membreId));
        BigDecimal soldeBloque   = nvl(compteEpargneRepository.sumSoldeBloqueByMembreId(membreId));
        long       nbComptesEp   = compteEpargneRepository.countActifsByMembreId(membreId);

        // ── Crédit ────────────────────────────────────────────────────────
        BigDecimal encoursCreditTotal = nvl(demandeCreditRepository.calculerTotalEncoursParMembre(membreId));

        // ── Calculs dérivés ───────────────────────────────────────────────
        // Solde total disponible = cotisations tout temps + épargne disponible
        // (L'épargne bloquée reste dans le compte mais n'est pas « disponible »)
        BigDecimal epargneDisponible = soldeEpargne.subtract(soldeBloque);
        BigDecimal soldeTotalDisponible = soldeCotisations.add(epargneDisponible.max(BigDecimal.ZERO));

        // Capacité d'emprunt = 3 × épargne disponible (règle mutuelle classique)
        BigDecimal capaciteEmprunt = epargneDisponible.max(BigDecimal.ZERO)
                .multiply(MULTIPLICATEUR_EMPRUNT)
                .setScale(0, RoundingMode.DOWN);

        return new CompteAdherentResponse(
                // Identité
                membre.getNumeroMembre(),
                nomComplet,
                orgNom,
                dateAdhesion,
                membre.getStatutCompte(),

                // Soldes
                soldeCotisations,
                soldeEpargne,
                soldeBloque,
                soldeTotalDisponible,
                encoursCreditTotal,
                capaciteEmprunt,

                // Cotisations
                (int) nbPayees,
                (int) nbTotal,
                (int) nbRetard,
                tauxEngagement,

                // Épargne
                (int) nbComptesEp,

                // Méta
                today
        );
    }

    // ── Utilitaires ───────────────────────────────────────────────────────

    private static BigDecimal nvl(BigDecimal v) {
        return v != null ? v : BigDecimal.ZERO;
    }
}

