package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.kyc.KycDossierRequest;
import dev.lions.unionflow.server.api.dto.kyc.KycDossierResponse;
import dev.lions.unionflow.server.api.enums.membre.NiveauRisqueKyc;
import dev.lions.unionflow.server.api.enums.membre.StatutKyc;
import dev.lions.unionflow.server.entity.KycDossier;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.KycDossierRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
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 lombok.extern.slf4j.Slf4j;

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

/**
 * Service KYC/AML (Know Your Customer / Anti-Money Laundering).
 *
 * <p>Implémente la due diligence requise par le GIABA (Groupe Intergouvernemental
 * d'Action contre le Blanchiment d'Argent en Afrique de l'Ouest) et les
 * instructions BCEAO sur la LCB-FT (Lutte Contre le Blanchiment et le Financement
 * du Terrorisme).
 *
 * <p>Algorithme de score de risque :
 * <ul>
 *   <li>PEP (Personne Exposée Politiquement) : +40 points</li>
 *   <li>Pièce expirée : +20 points</li>
 *   <li>Aucun justificatif de domicile : +15 points</li>
 *   <li>Pièce manquante (recto/verso) : +15 points</li>
 *   <li>Nationalité hors UEMOA (facteur risque géographique) : +10 points</li>
 * </ul>
 */
@Slf4j
@ApplicationScoped
@RlsEnabled
public class KycAmlService {

    private static final List<String> PAYS_UEMOA = List.of(
            "BJ", "BF", "CI", "GW", "ML", "NE", "SN", "TG"
    );

    @Inject
    KycDossierRepository kycDossierRepository;

    @Inject
    MembreRepository membreRepository;

    @Inject
    AuditService auditService;

    /**
     * Crée ou met à jour le dossier KYC d'un membre.
     * Si un dossier actif existe déjà, il est archivé et remplacé.
     */
    @Transactional
    public KycDossierResponse soumettreOuMettreAJour(KycDossierRequest request, String operateur) {
        UUID membreId = UUID.fromString(request.getMembreId());
        Membre membre = membreRepository.findByIdOptional(membreId)
                .orElseThrow(() -> new NotFoundException("Membre introuvable : " + request.getMembreId()));

        // Archiver l'ancien dossier actif si présent
        kycDossierRepository.findDossierActifByMembre(membreId).ifPresent(ancien -> {
            ancien.setActif(false);
            ancien.setModifiePar(operateur);
            kycDossierRepository.persist(ancien);
        });

        KycDossier dossier = KycDossier.builder()
                .membre(membre)
                .typePiece(request.getTypePiece())
                .numeroPiece(request.getNumeroPiece())
                .dateExpirationPiece(request.getDateExpirationPiece())
                .pieceIdentiteRectoFileId(request.getPieceIdentiteRectoFileId())
                .pieceIdentiteVersoFileId(request.getPieceIdentiteVersoFileId())
                .justifDomicileFileId(request.getJustifDomicileFileId())
                .estPep(Boolean.TRUE.equals(request.getEstPep()))
                .nationalite(request.getNationalite())
                .notesValidateur(request.getNotesValidateur())
                .statut(StatutKyc.EN_COURS)
                .anneeReference(LocalDate.now().getYear())
                .build();

        dossier.setCreePar(operateur);
        kycDossierRepository.persist(dossier);

        log.info("Dossier KYC soumis pour membre {} par {}", membreId, operateur);
        return toDto(dossier);
    }

    /**
     * Évalue le score de risque LCB-FT du membre et met à jour son dossier.
     *
     * @param membreId l'UUID du membre
     * @return le dossier KYC mis à jour avec le score et niveau de risque
     */
    @Transactional
    public KycDossierResponse evaluerRisque(UUID membreId) {
        KycDossier dossier = kycDossierRepository.findDossierActifByMembre(membreId)
                .orElseThrow(() -> new NotFoundException("Aucun dossier KYC actif pour le membre : " + membreId));

        int score = calculerScore(dossier);
        NiveauRisqueKyc niveau = NiveauRisqueKyc.fromScore(score);

        dossier.setScoreRisque(score);
        dossier.setNiveauRisque(niveau);
        kycDossierRepository.persist(dossier);

        if (niveau == NiveauRisqueKyc.CRITIQUE || niveau == NiveauRisqueKyc.ELEVE) {
            log.warn("Membre {} : niveau risque KYC {} (score {})", membreId, niveau, score);
            auditService.logKycRisqueEleve(membreId, score, niveau.name());
        }

        return toDto(dossier);
    }

    /**
     * Valide manuellement un dossier KYC (approbation par un agent habilité).
     */
    @Transactional
    public KycDossierResponse valider(UUID dossierId, UUID validateurId, String notes, String operateur) {
        KycDossier dossier = kycDossierRepository.findByIdOptional(dossierId)
                .orElseThrow(() -> new NotFoundException("Dossier KYC introuvable : " + dossierId));

        int score = calculerScore(dossier);
        dossier.setScoreRisque(score);
        dossier.setNiveauRisque(NiveauRisqueKyc.fromScore(score));
        dossier.setStatut(StatutKyc.VERIFIE);
        dossier.setDateVerification(LocalDateTime.now());
        dossier.setValidateurId(validateurId);
        dossier.setNotesValidateur(notes);
        dossier.setModifiePar(operateur);

        kycDossierRepository.persist(dossier);
        log.info("Dossier KYC {} validé par {} (score={})", dossierId, validateurId, score);
        return toDto(dossier);
    }

    /**
     * Refuse un dossier KYC avec motif.
     */
    @Transactional
    public KycDossierResponse refuser(UUID dossierId, UUID validateurId, String motif, String operateur) {
        KycDossier dossier = kycDossierRepository.findByIdOptional(dossierId)
                .orElseThrow(() -> new NotFoundException("Dossier KYC introuvable : " + dossierId));

        dossier.setStatut(StatutKyc.REFUSE);
        dossier.setDateVerification(LocalDateTime.now());
        dossier.setValidateurId(validateurId);
        dossier.setNotesValidateur(motif);
        dossier.setModifiePar(operateur);

        kycDossierRepository.persist(dossier);
        log.info("Dossier KYC {} refusé par {}: {}", dossierId, validateurId, motif);
        return toDto(dossier);
    }

    public Optional<KycDossierResponse> getDossierActif(UUID membreId) {
        return kycDossierRepository.findDossierActifByMembre(membreId).map(this::toDto);
    }

    public List<KycDossierResponse> getDossiersEnAttente() {
        return kycDossierRepository.findByStatut(StatutKyc.EN_COURS)
                .stream().map(this::toDto).collect(Collectors.toList());
    }

    public List<KycDossierResponse> getDossiersPep() {
        return kycDossierRepository.findPep()
                .stream().map(this::toDto).collect(Collectors.toList());
    }

    public List<KycDossierResponse> getPiecesExpirantDansLes30Jours() {
        LocalDate limite = LocalDate.now().plusDays(30);
        return kycDossierRepository.findPiecesExpirantsAvant(limite)
                .stream().map(this::toDto).collect(Collectors.toList());
    }

    // ── Calcul score ────────────────────────────────────────────────────────────

    int calculerScore(KycDossier dossier) {
        int score = 0;

        if (dossier.isEstPep()) {
            score += 40;
        }

        if (dossier.isPieceExpiree()) {
            score += 20;
        }

        if (dossier.getJustifDomicileFileId() == null || dossier.getJustifDomicileFileId().isBlank()) {
            score += 15;
        }

        boolean rectoManquant = dossier.getPieceIdentiteRectoFileId() == null
                || dossier.getPieceIdentiteRectoFileId().isBlank();
        boolean versoManquant = dossier.getPieceIdentiteVersoFileId() == null
                || dossier.getPieceIdentiteVersoFileId().isBlank();
        if (rectoManquant || versoManquant) {
            score += 15;
        }

        if (dossier.getNationalite() != null && !PAYS_UEMOA.contains(dossier.getNationalite().toUpperCase())) {
            score += 10;
        }

        return Math.min(score, 100);
    }

    // ── Mapping ─────────────────────────────────────────────────────────────────

    private KycDossierResponse toDto(KycDossier d) {
        KycDossierResponse dto = new KycDossierResponse();
        dto.setId(d.getId());
        dto.setDateCreation(d.getDateCreation());
        dto.setDateModification(d.getDateModification());
        dto.setCreePar(d.getCreePar());
        dto.setModifiePar(d.getModifiePar());
        dto.setVersion(d.getVersion());
        dto.setActif(d.getActif());

        if (d.getMembre() != null) {
            dto.setMembreId(d.getMembre().getId());
            dto.setMembreNomComplet(d.getMembre().getPrenom() + " " + d.getMembre().getNom());
            dto.setMembreEmail(d.getMembre().getEmail());
        }

        dto.setTypePiece(d.getTypePiece());
        dto.setNumeroPiece(d.getNumeroPiece());
        dto.setDateExpirationPiece(d.getDateExpirationPiece());
        dto.setPieceIdentiteRectoFileId(d.getPieceIdentiteRectoFileId());
        dto.setPieceIdentiteVersoFileId(d.getPieceIdentiteVersoFileId());
        dto.setJustifDomicileFileId(d.getJustifDomicileFileId());
        dto.setStatut(d.getStatut());
        dto.setNiveauRisque(d.getNiveauRisque());
        dto.setScoreRisque(d.getScoreRisque());
        dto.setEstPep(d.isEstPep());
        dto.setNationalite(d.getNationalite());
        dto.setDateVerification(d.getDateVerification());
        dto.setValidateurId(d.getValidateurId());
        dto.setNotesValidateur(d.getNotesValidateur());
        dto.setAnneeReference(d.getAnneeReference());
        return dto;
    }
}
