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

import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.epargne.TransactionEpargneResponse;
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.wave.StatutTransactionWave;
import dev.lions.unionflow.server.api.validation.ValidationConstants;
import dev.lions.unionflow.server.entity.mutuelle.epargne.CompteEpargne;
import dev.lions.unionflow.server.entity.mutuelle.epargne.TransactionEpargne;
import dev.lions.unionflow.server.mapper.mutuelle.epargne.TransactionEpargneMapper;
import dev.lions.unionflow.server.repository.ParametresLcbFtRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.CompteEpargneRepository;
import dev.lions.unionflow.server.repository.mutuelle.epargne.TransactionEpargneRepository;
import dev.lions.unionflow.server.service.AuditService;
import dev.lions.unionflow.server.service.ComptabiliteService;
import dev.lions.unionflow.server.security.RlsEnabled;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Service métier pour les transactions sur les comptes d'épargne.
 * Applique les règles LCB-FT : origine des fonds obligatoire au-dessus du seuil configuré.
 */
@ApplicationScoped
@RlsEnabled
public class TransactionEpargneService {

    /** Seuil LCB-FT (XOF) par défaut si aucun paramètre en base. */
    private static final BigDecimal SEUIL_DEFAULT_XOF = new BigDecimal("500000");

    private static final String CODE_DEVISE_XOF = "XOF";

    @Inject
    TransactionEpargneRepository transactionEpargneRepository;

    @Inject
    CompteEpargneRepository compteEpargneRepository;

    @Inject
    TransactionEpargneMapper transactionEpargneMapper;

    @Inject
    ParametresLcbFtRepository parametresLcbFtRepository;

    @Inject
    AuditService auditService;

    @Inject
    dev.lions.unionflow.server.service.AlerteLcbFtService alerteLcbFtService;

    @Inject
    ComptabiliteService comptabiliteService;

    /**
     * Enregistre une nouvelle transaction et met à jour le solde du compte.
     *
     * @param request Le DTO de la transaction.
     * @return Le DTO de la transaction enregistrée.
     */
    @Transactional
    public TransactionEpargneResponse executerTransaction(TransactionEpargneRequest request) {
        return executerTransaction(request, false);
    }

    @Transactional
    public TransactionEpargneResponse executerTransaction(TransactionEpargneRequest request, boolean bypassSolde) {
        CompteEpargne compte = compteEpargneRepository.findByIdOptional(UUID.fromString(request.getCompteId()))
                .orElseThrow(() -> new NotFoundException("Compte non trouvé avec l'ID: " + request.getCompteId()));

        BigDecimal seuil = parametresLcbFtRepository.getSeuilJustification(
                compte.getOrganisation() != null ? compte.getOrganisation().getId() : null,
                CODE_DEVISE_XOF).orElse(SEUIL_DEFAULT_XOF);
        validerLcbFtSiSeuilAtteint(request, seuil);

        if (compte.getStatut() != StatutCompteEpargne.ACTIF) {
            throw new IllegalArgumentException("Impossible d'effectuer une transaction sur un compte non actif.");
        }

        BigDecimal soldeAvant = compte.getSoldeActuel();
        BigDecimal montant = request.getMontant();
        BigDecimal soldeApres;

        // Calculer le nouveau solde en fonction du type de transaction
        if (isTypeCredit(request.getTypeTransaction())) {
            soldeApres = soldeAvant.add(montant);
            compte.setSoldeActuel(soldeApres);
        } else if (isTypeDebit(request.getTypeTransaction())) {
            if (!bypassSolde && getSoldeDisponible(compte).compareTo(montant) < 0) {
                throw new IllegalArgumentException("Solde disponible insuffisant pour cette opération.");
            }
            soldeApres = soldeAvant.subtract(montant);
            compte.setSoldeActuel(soldeApres);
        } else if (request.getTypeTransaction() == TypeTransactionEpargne.RETENUE_GARANTIE) {
            if (!bypassSolde && getSoldeDisponible(compte).compareTo(montant) < 0) {
                throw new IllegalArgumentException("Solde disponible insuffisant pour geler ce montant.");
            }
            compte.setSoldeBloque(compte.getSoldeBloque().add(montant));
            soldeApres = soldeAvant; // Le solde total ne change pas
        } else if (request.getTypeTransaction() == TypeTransactionEpargne.LIBERATION_GARANTIE) {
            if (compte.getSoldeBloque().compareTo(montant) < 0) {
                throw new IllegalArgumentException("Le montant à libérer est supérieur au solde bloqué.");
            }
            compte.setSoldeBloque(compte.getSoldeBloque().subtract(montant));
            soldeApres = soldeAvant; // Le solde total ne change pas
        } else {
            throw new IllegalArgumentException("Type de transaction non pris en charge pour la modification de solde.");
        }

        // Mettre à jour le compte
        compte.setDateDerniereTransaction(LocalDate.now());

        // Créer la transaction
        TransactionEpargne transaction = transactionEpargneMapper.toEntity(request);
        transaction.setCompte(compte);
        transaction.setSoldeAvant(soldeAvant);
        transaction.setSoldeApres(soldeApres);
        transaction.setStatutExecution(StatutTransactionWave.REUSSIE);
        if (request.getPieceJustificativeId() != null && !request.getPieceJustificativeId().isBlank()) {
            transaction.setPieceJustificativeId(UUID.fromString(request.getPieceJustificativeId().trim()));
        }

        if (transaction.getDateTransaction() == null) {
            transaction.setDateTransaction(LocalDateTime.now());
        }

        transactionEpargneRepository.persist(transaction);

        // Génération écriture SYSCOHADA (non bloquant)
        if (compte.getOrganisation() != null) {
            try {
                if (request.getTypeTransaction() == TypeTransactionEpargne.DEPOT) {
                    comptabiliteService.enregistrerDepotEpargne(transaction, compte.getOrganisation());
                } else if (request.getTypeTransaction() == TypeTransactionEpargne.RETRAIT) {
                    comptabiliteService.enregistrerRetraitEpargne(transaction, compte.getOrganisation());
                }
            } catch (Exception e) {
                // Écriture comptable non bloquante — la transaction épargne reste valide
            }
        }

        if (request.getMontant() != null && request.getMontant().compareTo(seuil) >= 0) {
            UUID orgId = compte.getOrganisation() != null ? compte.getOrganisation().getId() : null;

            // Audit LCB-FT
            auditService.logLcbFtSeuilAtteint(orgId,
                    transaction.getOperateurId(),
                    request.getCompteId(),
                    transaction.getId() != null ? transaction.getId().toString() : null,
                    request.getMontant(),
                    request.getOrigineFonds());

            // Génération automatique d'alerte LCB-FT
            UUID membreId = compte.getMembre() != null ? compte.getMembre().getId() : null;
            alerteLcbFtService.genererAlerteSeuilDepasse(
                    orgId,
                    membreId,
                    request.getTypeTransaction().name(),
                    request.getMontant(),
                    seuil,
                    transaction.getId() != null ? transaction.getId().toString() : null,
                    request.getOrigineFonds()
            );
        }

        return transactionEpargneMapper.toDto(transaction);
    }

    /**
     * Récupère l'historique des transactions d'un compte.
     *
     * @param compteId L'ID du compte.
     * @return La liste des transactions.
     */
    public List<TransactionEpargneResponse> getTransactionsByCompte(UUID compteId) {
        return transactionEpargneRepository.find("compte.id = ?1 ORDER BY dateTransaction DESC", compteId)
                .stream()
                .map(transactionEpargneMapper::toDto)
                .collect(Collectors.toList());
    }

    /**
     * Effectue un transfert entre deux comptes d'épargne.
     *
     * @param request Le DTO du transfert.
     * @return La transaction de débit du compte source.
     */
    @Transactional
    public TransactionEpargneResponse transferer(TransactionEpargneRequest request) {
        if (request.getCompteDestinationId() == null || request.getCompteDestinationId().isEmpty()) {
            throw new IllegalArgumentException("L'ID du compte de destination est obligatoire pour un transfert.");
        }

        if (request.getCompteId().equals(request.getCompteDestinationId())) {
            throw new IllegalArgumentException("Le compte source et destination doivent être différents.");
        }

        // 1. Débit du compte source (LCB-FT : origine des fonds sur le débit si seuil atteint)
        TransactionEpargneRequest debitReq = TransactionEpargneRequest.builder()
                .compteId(request.getCompteId())
                .typeTransaction(TypeTransactionEpargne.TRANSFERT_SORTANT)
                .montant(request.getMontant())
                .motif(request.getMotif())
                .compteDestinationId(request.getCompteDestinationId())
                .origineFonds(request.getOrigineFonds())
                .pieceJustificativeId(request.getPieceJustificativeId())
                .build();
        TransactionEpargneResponse debitRes = executerTransaction(debitReq);

        // 2. Crédit du compte destination
        TransactionEpargneRequest creditReq = TransactionEpargneRequest.builder()
                .compteId(request.getCompteDestinationId())
                .typeTransaction(TypeTransactionEpargne.TRANSFERT_ENTRANT)
                .montant(request.getMontant())
                .motif(request.getMotif())
                .build();
        executerTransaction(creditReq);

        return debitRes;
    }

    private BigDecimal getSoldeDisponible(CompteEpargne compte) {
        return compte.getSoldeActuel().subtract(compte.getSoldeBloque());
    }

    private boolean isTypeCredit(TypeTransactionEpargne type) {
        return type == TypeTransactionEpargne.DEPOT ||
                type == TypeTransactionEpargne.PAIEMENT_INTERETS ||
                type == TypeTransactionEpargne.TRANSFERT_ENTRANT;
    }

    private boolean isTypeDebit(TypeTransactionEpargne type) {
        return type == TypeTransactionEpargne.RETRAIT ||
                type == TypeTransactionEpargne.PRELEVEMENT_FRAIS ||
                type == TypeTransactionEpargne.TRANSFERT_SORTANT ||
                type == TypeTransactionEpargne.REMBOURSEMENT_CREDIT;
    }

    /**
     * Vérifie les règles LCB-FT : au-dessus du seuil, l'origine des fonds est obligatoire.
     */
    private void validerLcbFtSiSeuilAtteint(TransactionEpargneRequest request, BigDecimal seuil) {
        if (request.getMontant() == null || seuil == null) {
            return;
        }
        if (request.getMontant().compareTo(seuil) >= 0) {
            if (request.getOrigineFonds() == null || request.getOrigineFonds().isBlank()) {
                throw new IllegalArgumentException(ValidationConstants.ORIGINE_FONDS_OBLIGATOIRE_SEUIL_MESSAGE);
            }
            if (request.getOrigineFonds().length() > ValidationConstants.ORIGINE_FONDS_MAX_LENGTH) {
                throw new IllegalArgumentException(ValidationConstants.ORIGINE_FONDS_SIZE_MESSAGE);
            }
        }
    }
}
