package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.entity.BaremeCotisationRole;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.ParametresCotisationOrganisation;
import dev.lions.unionflow.server.repository.BaremeCotisationRoleRepository;
import dev.lions.unionflow.server.repository.CotisationRepository;
import dev.lions.unionflow.server.repository.MembreOrganisationRepository;
import dev.lions.unionflow.server.repository.ParametresCotisationOrganisationRepository;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;

/**
 * Génération automatique mensuelle des cotisations pour les organisations
 * ayant activé {@code generationAutomatiqueActivee} dans leurs paramètres.
 *
 * <p>Le job s'exécute le 1er de chaque mois à 1h00. Pour chaque membre actif,
 * il détermine le montant selon le barème du rôle fonctionnel de ce membre dans
 * l'organisation, ou utilise le montant par défaut si aucun barème n'est configuré.
 *
 * <p>Les cotisations déjà générées pour le même mois/année sont ignorées (idempotence).
 */
@ApplicationScoped
@Slf4j
public class CotisationAutoGenerationService {

    @Inject
    ParametresCotisationOrganisationRepository parametresRepository;

    @Inject
    MembreOrganisationRepository membreOrganisationRepository;

    @Inject
    BaremeCotisationRoleRepository baremeRepository;

    @Inject
    CotisationRepository cotisationRepository;

    /**
     * Point d'entrée du job planifié : 1er de chaque mois à 01:00.
     */
    @Scheduled(cron = "0 0 1 1 * ?")
    @Transactional
    public void genererCotisationsMensuelles() {
        LocalDate aujourd_hui = LocalDate.now();
        int annee = aujourd_hui.getYear();
        int mois = aujourd_hui.getMonthValue();
        log.info("=== Génération automatique cotisations {}/{} ===", mois, annee);

        List<ParametresCotisationOrganisation> paramsList =
            parametresRepository.findAvecGenerationAutomatiqueActivee();

        if (paramsList.isEmpty()) {
            log.info("Aucune organisation avec génération automatique activée.");
            return;
        }

        int totalCrees = 0;
        int totalIgnores = 0;

        for (ParametresCotisationOrganisation params : paramsList) {
            Organisation org = params.getOrganisation();
            int[] result = genererPourOrganisation(org, params, annee, mois);
            totalCrees   += result[0];
            totalIgnores += result[1];
        }

        log.info("=== Génération terminée : {} créées, {} ignorées (déjà existantes) ===",
            totalCrees, totalIgnores);
    }

    /**
     * Génère les cotisations pour une organisation donnée.
     *
     * @return tableau [nbCreees, nbIgnorees]
     */
    int[] genererPourOrganisation(Organisation org,
                                   ParametresCotisationOrganisation params,
                                   int annee, int mois) {
        List<MembreOrganisation> membresActifs =
            membreOrganisationRepository.findMembresActifsParOrganisation(org.getId());

        int crees   = 0;
        int ignores = 0;
        String devise = params.getDevise() != null ? params.getDevise() : "XOF";

        for (MembreOrganisation mo : membresActifs) {
            if (mo.getMembre() == null) continue;

            // Idempotence : ne pas recréer si déjà générée ce mois
            if (cotisationRepository.existsByMembreOrganisationAnneeAndMois(
                    mo.getMembre().getId(), org.getId(), annee, mois)) {
                ignores++;
                continue;
            }

            BigDecimal montant = resoudreMontantMensuel(org.getId(), mo.getRoleOrg(), params);
            if (montant == null || montant.compareTo(BigDecimal.ZERO) == 0) {
                log.debug("Org {} membre {} : montant 0, cotisation ignorée", org.getNom(), mo.getMembre().getId());
                ignores++;
                continue;
            }

            LocalDate echeance = LocalDate.of(annee, mois, 28);
            Cotisation cotisation = Cotisation.builder()
                .typeCotisation("MENSUELLE")
                .libelle("Cotisation mensuelle " + String.format("%02d/%d", mois, annee))
                .description("Génération automatique — " + org.getNom())
                .montantDu(montant)
                .montantPaye(BigDecimal.ZERO)
                .codeDevise(devise)
                .statut("EN_ATTENTE")
                .dateEcheance(echeance)
                .annee(annee)
                .mois(mois)
                .recurrente(true)
                .membre(mo.getMembre())
                .organisation(org)
                .build();
            cotisation.setNumeroReference(Cotisation.genererNumeroReference());
            cotisationRepository.persist(cotisation);
            crees++;
        }

        log.info("Org '{}' [{}/{}] : {} cotisation(s) créée(s), {} ignorée(s)",
            org.getNom(), mois, annee, crees, ignores);
        return new int[]{crees, ignores};
    }

    /**
     * Détermine le montant mensuel applicable à ce membre selon son rôle.
     * Priorité : barème du rôle → montant par défaut de l'organisation.
     */
    private BigDecimal resoudreMontantMensuel(java.util.UUID orgId, String roleOrg,
                                               ParametresCotisationOrganisation params) {
        if (roleOrg != null && !roleOrg.isBlank()) {
            Optional<BaremeCotisationRole> bareme =
                baremeRepository.findByOrganisationIdAndRoleOrg(orgId, roleOrg);
            if (bareme.isPresent() && bareme.get().getMontantMensuel() != null) {
                return bareme.get().getMontantMensuel();
            }
        }
        return params.getMontantCotisationMensuelle();
    }
}
