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

import dev.lions.unionflow.server.api.dto.mutuelle.parts.ComptePartsSocialesRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.parts.ComptePartsSocialesResponse;
import dev.lions.unionflow.server.api.dto.mutuelle.parts.TransactionPartsSocialesRequest;
import dev.lions.unionflow.server.api.dto.mutuelle.parts.TransactionPartsSocialesResponse;
import dev.lions.unionflow.server.api.enums.mutuelle.parts.StatutComptePartsSociales;
import dev.lions.unionflow.server.api.enums.mutuelle.parts.TypeTransactionPartsSociales;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.mutuelle.ParametresFinanciersMutuelle;
import dev.lions.unionflow.server.entity.mutuelle.parts.ComptePartsSociales;
import dev.lions.unionflow.server.entity.mutuelle.parts.TransactionPartsSociales;
import dev.lions.unionflow.server.mapper.mutuelle.parts.ComptePartsSocialesMapper;
import dev.lions.unionflow.server.mapper.mutuelle.parts.TransactionPartsSocialesMapper;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.mutuelle.ParametresFinanciersMutuellRepository;
import dev.lions.unionflow.server.repository.mutuelle.parts.ComptePartsSocialesRepository;
import dev.lions.unionflow.server.repository.mutuelle.parts.TransactionPartsSocialesRepository;
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.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@ApplicationScoped
public class ComptePartsSocialesService {

    private static final BigDecimal VALEUR_NOMINALE_DEFAUT = new BigDecimal("5000");

    @Inject ComptePartsSocialesRepository compteRepo;
    @Inject TransactionPartsSocialesRepository txRepo;
    @Inject MembreRepository membreRepository;
    @Inject OrganisationRepository organisationRepository;
    @Inject ParametresFinanciersMutuellRepository parametresRepo;
    @Inject ComptePartsSocialesMapper compteMapper;
    @Inject TransactionPartsSocialesMapper txMapper;

    @Transactional
    public ComptePartsSocialesResponse ouvrirCompte(ComptePartsSocialesRequest req) {
        Membre membre = membreRepository.findByIdOptional(UUID.fromString(req.getMembreId()))
                .orElseThrow(() -> new NotFoundException("Membre introuvable: " + req.getMembreId()));
        Organisation org = organisationRepository.findByIdOptional(UUID.fromString(req.getOrganisationId()))
                .orElseThrow(() -> new NotFoundException("Organisation introuvable: " + req.getOrganisationId()));

        // Reject duplicate (one compte per member per org)
        compteRepo.findByMembreAndOrg(membre.getId(), org.getId()).ifPresent(c -> {
            throw new IllegalStateException("Un compte de parts sociales existe déjà pour ce membre dans cette organisation.");
        });

        BigDecimal valeurNominale = resolveValeurNominale(req.getValeurNominale(), org.getId());

        ComptePartsSociales compte = ComptePartsSociales.builder()
                .membre(membre)
                .organisation(org)
                .numeroCompte(genererNumeroCompte(org))
                .nombreParts(0)
                .valeurNominale(valeurNominale)
                .montantTotal(BigDecimal.ZERO)
                .totalDividendesRecus(BigDecimal.ZERO)
                .statut(StatutComptePartsSociales.ACTIF)
                .dateOuverture(LocalDate.now())
                .notes(req.getNotes())
                .build();
        compteRepo.persist(compte);

        // Initial souscription si nombreParts > 0
        if (req.getNombreParts() != null && req.getNombreParts() > 0) {
            enregistrerTransaction(compte, TypeTransactionPartsSociales.SOUSCRIPTION,
                    req.getNombreParts(), null, "Souscription initiale à l'ouverture", null);
        }

        return compteMapper.toDto(compte);
    }

    @Transactional
    public TransactionPartsSocialesResponse enregistrerSouscription(TransactionPartsSocialesRequest req) {
        ComptePartsSociales compte = findCompteActif(req.getCompteId());
        return txMapper.toDto(
                enregistrerTransaction(compte, req.getTypeTransaction(),
                        req.getNombreParts(), req.getMontant(), req.getMotif(), req.getReferenceExterne()));
    }

    public ComptePartsSocialesResponse getById(UUID id) {
        return compteMapper.toDto(compteRepo.findByIdOptional(id)
                .orElseThrow(() -> new NotFoundException("Compte parts sociales introuvable: " + id)));
    }

    public List<ComptePartsSocialesResponse> getByMembre(UUID membreId) {
        return compteRepo.findByMembre(membreId).stream().map(compteMapper::toDto).collect(Collectors.toList());
    }

    public List<ComptePartsSocialesResponse> getByOrganisation(UUID orgId) {
        return compteRepo.findByOrganisation(orgId).stream().map(compteMapper::toDto).collect(Collectors.toList());
    }

    public List<TransactionPartsSocialesResponse> getTransactions(UUID compteId) {
        return txRepo.findByCompte(compteId).stream().map(txMapper::toDto).collect(Collectors.toList());
    }

    // ─── Internal helpers ──────────────────────────────────────────────────────

    TransactionPartsSociales enregistrerTransaction(
            ComptePartsSociales compte,
            TypeTransactionPartsSociales type,
            int nombreParts,
            BigDecimal montantOverride,
            String motif,
            String referenceExterne) {

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

        int soldeAvant = compte.getNombreParts();
        int soldeApres;
        BigDecimal montant = montantOverride != null
                ? montantOverride
                : compte.getValeurNominale().multiply(BigDecimal.valueOf(nombreParts));

        switch (type) {
            case SOUSCRIPTION, SOUSCRIPTION_IMPORT, PAIEMENT_DIVIDENDE -> {
                soldeApres = soldeAvant + nombreParts;
                compte.setNombreParts(soldeApres);
                compte.setMontantTotal(compte.getValeurNominale().multiply(BigDecimal.valueOf(soldeApres)));
                if (type == TypeTransactionPartsSociales.PAIEMENT_DIVIDENDE) {
                    compte.setTotalDividendesRecus(compte.getTotalDividendesRecus().add(montant));
                }
            }
            case CESSION_PARTIELLE -> {
                if (nombreParts > soldeAvant) {
                    throw new IllegalArgumentException(
                            "Nombre de parts à céder (" + nombreParts + ") supérieur au solde (" + soldeAvant + ").");
                }
                soldeApres = soldeAvant - nombreParts;
                compte.setNombreParts(soldeApres);
                compte.setMontantTotal(compte.getValeurNominale().multiply(BigDecimal.valueOf(soldeApres)));
            }
            case RACHAT_TOTAL -> {
                soldeApres = 0;
                compte.setNombreParts(0);
                compte.setMontantTotal(BigDecimal.ZERO);
                compte.setStatut(StatutComptePartsSociales.CLOS);
            }
            case CORRECTION -> {
                // Admin correction: use nombreParts as the new absolute balance
                soldeApres = nombreParts;
                compte.setNombreParts(soldeApres);
                compte.setMontantTotal(compte.getValeurNominale().multiply(BigDecimal.valueOf(soldeApres)));
            }
            default -> throw new IllegalArgumentException("Type de transaction non pris en charge: " + type);
        }

        compte.setDateDerniereOperation(LocalDate.now());

        TransactionPartsSociales tx = TransactionPartsSociales.builder()
                .compte(compte)
                .typeTransaction(type)
                .nombreParts(nombreParts)
                .montant(montant)
                .soldePartsAvant(soldeAvant)
                .soldePartsApres(soldeApres)
                .motif(motif)
                .referenceExterne(referenceExterne)
                .dateTransaction(LocalDateTime.now())
                .build();
        txRepo.persist(tx);
        return tx;
    }

    private ComptePartsSociales findCompteActif(String compteId) {
        return compteRepo.findByIdOptional(UUID.fromString(compteId))
                .orElseThrow(() -> new NotFoundException("Compte parts sociales introuvable: " + compteId));
    }

    private BigDecimal resolveValeurNominale(BigDecimal fromRequest, UUID orgId) {
        if (fromRequest != null && fromRequest.compareTo(BigDecimal.ZERO) > 0) {
            return fromRequest;
        }
        return parametresRepo.findByOrganisation(orgId)
                .map(ParametresFinanciersMutuelle::getValeurNominaleParDefaut)
                .orElse(VALEUR_NOMINALE_DEFAUT);
    }

    private static final AtomicInteger COUNTER = new AtomicInteger(0);

    private String genererNumeroCompte(Organisation org) {
        String prefix = "PS-" + (org.getNomCourt() != null ? org.getNomCourt().toUpperCase() : "ORG") + "-";
        long count = compteRepo.count("organisation.id", org.getId());
        return prefix + String.format("%05d", count + 1);
    }
}
