package dev.lions.unionflow.server.service.mutuelle;

import dev.lions.unionflow.server.api.enums.mutuelle.epargne.StatutCompteEpargne;
import dev.lions.unionflow.server.api.enums.mutuelle.epargne.TypeTransactionEpargne;
import dev.lions.unionflow.server.api.enums.mutuelle.parts.TypeTransactionPartsSociales;
import dev.lions.unionflow.server.api.enums.wave.StatutTransactionWave;
import dev.lions.unionflow.server.entity.mutuelle.ParametresFinanciersMutuelle;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import dev.lions.unionflow.server.entity.mutuelle.epargne.TransactionEpargne;
import dev.lions.unionflow.server.entity.mutuelle.parts.ComptePartsSociales;
import dev.lions.unionflow.server.entity.mutuelle.parts.TransactionPartsSociales;
import dev.lions.unionflow.server.repository.mutuelle.ParametresFinanciersMutuellRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.TransactionEpargneRepository;
import dev.lions.unionflow.server.repository.mutuelle.parts.ComptePartsSocialesRepository;
import dev.lions.unionflow.server.repository.mutuelle.parts.TransactionPartsSocialesRepository;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.jboss.logging.Logger;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * Calcul automatique des intérêts sur épargne et des dividendes sur parts sociales.
 *
 * <p>Le scheduler tourne chaque jour à 02:00 et vérifie si un calcul est dû
 * selon la périodicité configurée par organisation.
 */
@ApplicationScoped
public class InteretsEpargneService {

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

    @Inject ParametresFinanciersMutuellRepository parametresRepo;
    @Inject CompteEpargneRepository compteEpargneRepository;
    @Inject TransactionEpargneRepository transactionEpargneRepository;
    @Inject ComptePartsSocialesRepository comptePartsSocialesRepository;
    @Inject TransactionPartsSocialesRepository transactionPartsSocialesRepository;

    /**
     * Scheduler quotidien — calcule les intérêts pour toutes les organisations dont
     * la date prochaine_calcul_interets est aujourd'hui ou dans le passé.
     */
    @Scheduled(cron = "0 0 2 * * ?")
    @Transactional
    public void calculerInteretsScheduled() {
        LocalDate aujourd_hui = LocalDate.now();
        List<ParametresFinanciersMutuelle> tous = parametresRepo.listAll();
        for (ParametresFinanciersMutuelle params : tous) {
            if (params.getProchaineCalculInterets() != null
                    && !params.getProchaineCalculInterets().isAfter(aujourd_hui)) {
                try {
                    calculerInteretsPourOrg(params);
                } catch (Exception e) {
                    LOG.errorf("Erreur calcul intérêts org %s: %s",
                            params.getOrganisation().getId(), e.getMessage());
                }
            }
        }
    }

    /**
     * Déclenchement manuel par un admin pour une organisation donnée.
     * @return résumé : nombre de comptes traités, montant total crédité
     */
    @Transactional
    public Map<String, Object> calculerManuellement(UUID orgId) {
        ParametresFinanciersMutuelle params = parametresRepo.findByOrganisation(orgId)
                .orElseThrow(() -> new IllegalArgumentException(
                        "Aucun paramètre financier configuré pour cette organisation. "
                        + "Créez d'abord les paramètres via POST /api/v1/mutuelle/parametres-financiers."));

        return calculerInteretsPourOrg(params);
    }

    // ─── Internal ─────────────────────────────────────────────────────────────

    Map<String, Object> calculerInteretsPourOrg(ParametresFinanciersMutuelle params) {
        UUID orgId = params.getOrganisation().getId();
        LOG.infof("Calcul intérêts org %s — taux épargne=%.4f, taux parts=%.4f",
                orgId, params.getTauxInteretAnnuelEpargne(), params.getTauxDividendePartsAnnuel());

        int nbEpargne = calculerInteretsEpargne(params, orgId);
        int nbParts = calculerDividendesParts(params, orgId);

        // Mise à jour des dates
        params.setDernierCalculInterets(LocalDate.now());
        params.setDernierNbComptesTraites(nbEpargne + nbParts);
        params.setProchaineCalculInterets(prochaineDateCalcul(params));

        LOG.infof("Calcul terminé org %s — %d comptes épargne, %d comptes parts", orgId, nbEpargne, nbParts);
        return Map.of(
                "organisationId", orgId.toString(),
                "comptesEpargneTraites", nbEpargne,
                "comptesPartsTraites", nbParts,
                "dateCalcul", LocalDate.now().toString(),
                "prochaineCalcul", params.getProchaineCalculInterets().toString()
        );
    }

    private int calculerInteretsEpargne(ParametresFinanciersMutuelle params, UUID orgId) {
        if (params.getTauxInteretAnnuelEpargne().compareTo(BigDecimal.ZERO) == 0) return 0;

        List<CompteEpargne> comptes = compteEpargneRepository
                .find("organisation.id = ?1 AND statut = ?2 AND actif = true",
                        orgId, StatutCompteEpargne.ACTIF)
                .list();

        BigDecimal tauxPeriodique = calculerTauxPeriodique(
                params.getTauxInteretAnnuelEpargne(), params.getPeriodiciteCalcul());
        BigDecimal seuil = params.getSeuilMinEpargneInterets() != null
                ? params.getSeuilMinEpargneInterets() : BigDecimal.ZERO;

        int count = 0;
        for (CompteEpargne compte : comptes) {
            BigDecimal solde = compte.getSoldeActuel().subtract(compte.getSoldeBloque());
            if (solde.compareTo(seuil) <= 0) continue;

            BigDecimal interets = solde.multiply(tauxPeriodique).setScale(0, RoundingMode.HALF_UP);
            if (interets.compareTo(BigDecimal.ZERO) <= 0) continue;

            compte.setSoldeActuel(compte.getSoldeActuel().add(interets));
            compte.setDateDerniereTransaction(LocalDate.now());

            TransactionEpargne tx = TransactionEpargne.builder()
                    .compte(compte)
                    .type(TypeTransactionEpargne.PAIEMENT_INTERETS)
                    .montant(interets)
                    .soldeAvant(solde)
                    .soldeApres(compte.getSoldeActuel())
                    .motif("Intérêts " + params.getPeriodiciteCalcul().toLowerCase()
                            + " — taux " + params.getTauxInteretAnnuelEpargne()
                                    .multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP) + "%/an")
                    .dateTransaction(LocalDateTime.now())
                    .statutExecution(StatutTransactionWave.REUSSIE)
                    .origineFonds("Calcul automatique intérêts")
                    .build();
            transactionEpargneRepository.persist(tx);
            count++;
        }
        return count;
    }

    private int calculerDividendesParts(ParametresFinanciersMutuelle params, UUID orgId) {
        if (params.getTauxDividendePartsAnnuel().compareTo(BigDecimal.ZERO) == 0) return 0;

        List<ComptePartsSociales> comptes = comptePartsSocialesRepository.findByOrganisation(orgId);
        BigDecimal tauxPeriodique = calculerTauxPeriodique(
                params.getTauxDividendePartsAnnuel(), params.getPeriodiciteCalcul());

        int count = 0;
        for (ComptePartsSociales compte : comptes) {
            if (compte.getMontantTotal().compareTo(BigDecimal.ZERO) <= 0) continue;

            BigDecimal dividende = compte.getMontantTotal()
                    .multiply(tauxPeriodique).setScale(0, RoundingMode.HALF_UP);
            if (dividende.compareTo(BigDecimal.ZERO) <= 0) continue;

            // Dividende: enregistré comme transaction (NB: ne modifie pas le nombre de parts)
            int partsRef = 1; // transaction symbolique — montant est le vrai indicateur
            TransactionPartsSociales tx = TransactionPartsSociales.builder()
                    .compte(compte)
                    .typeTransaction(TypeTransactionPartsSociales.PAIEMENT_DIVIDENDE)
                    .nombreParts(partsRef)
                    .montant(dividende)
                    .soldePartsAvant(compte.getNombreParts())
                    .soldePartsApres(compte.getNombreParts())
                    .motif("Dividende " + params.getPeriodiciteCalcul().toLowerCase()
                            + " — taux " + params.getTauxDividendePartsAnnuel()
                                    .multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP) + "%/an")
                    .dateTransaction(LocalDateTime.now())
                    .build();
            compte.setTotalDividendesRecus(compte.getTotalDividendesRecus().add(dividende));
            compte.setDateDerniereOperation(LocalDate.now());
            transactionPartsSocialesRepository.persist(tx);
            count++;
        }
        return count;
    }

    /** Taux périodique = taux annuel / nombre de périodes par an */
    private BigDecimal calculerTauxPeriodique(BigDecimal tauxAnnuel, String periodicite) {
        int diviseur = switch (periodicite.toUpperCase()) {
            case "MENSUEL" -> 12;
            case "TRIMESTRIEL" -> 4;
            default -> 1; // ANNUEL
        };
        return tauxAnnuel.divide(BigDecimal.valueOf(diviseur), 8, RoundingMode.HALF_UP);
    }

    private LocalDate prochaineDateCalcul(ParametresFinanciersMutuelle params) {
        LocalDate base = LocalDate.now();
        return switch (params.getPeriodiciteCalcul().toUpperCase()) {
            case "MENSUEL" -> base.plusMonths(1);
            case "TRIMESTRIEL" -> base.plusMonths(3);
            default -> base.plusYears(1);
        };
    }
}
