package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.admin.request.CreateAuditLogRequest;
import dev.lions.unionflow.server.api.dto.admin.response.AuditLogResponse;
import dev.lions.unionflow.server.api.enums.audit.PorteeAudit;
import dev.lions.unionflow.server.entity.AuditLog;
import dev.lions.unionflow.server.repository.AuditLogRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;

/**
 * Service pour la gestion des logs d'audit
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-17
 */
@ApplicationScoped
@Slf4j
public class AuditService {

    @Inject
    AuditLogRepository auditLogRepository;

    @Inject
    OrganisationRepository organisationRepository;

    /**
     * Enregistre un audit de désactivation de membre (soft delete).
     * Portée GLOBALE (trace centrale de toute action admin sur un compte).
     */
    @Transactional
    public void logMembreDesactive(UUID membreId, String membreEmail, String adminOperateur,
            int nbAdhesionsSuspendues, int nbRolesDesactives) {
        AuditLog auditLog = new AuditLog();
        auditLog.setTypeAction("MEMBRE_DESACTIVE");
        auditLog.setSeverite("WARN");
        auditLog.setUtilisateur(adminOperateur != null ? adminOperateur : "system");
        auditLog.setModule("MEMBRES");
        auditLog.setDescription("Désactivation (soft delete) d'un compte membre");
        auditLog.setDetails(String.format(
            "membreId=%s, email=%s, adhesionsSuspendues=%d, rolesDesactives=%d",
            membreId, membreEmail != null ? membreEmail : "", nbAdhesionsSuspendues, nbRolesDesactives));
        auditLog.setEntiteType("Membre");
        auditLog.setEntiteId(membreId != null ? membreId.toString() : null);
        auditLog.setDateHeure(LocalDateTime.now());
        auditLog.setPortee(PorteeAudit.PLATEFORME);
        try {
            auditLogRepository.persist(auditLog);
        } catch (Exception e) {
            log.warn("Persist audit MEMBRE_DESACTIVE échoué pour membreId={} : {}", membreId, e.getMessage());
        }
    }

    /**
     * Enregistre un log d'audit LCB-FT lorsqu'une transaction épargne dépasse le seuil.
     * Portée ORGANISATION pour traçabilité anti-blanchiment.
     */
    @Transactional
    public void logLcbFtSeuilAtteint(UUID organisationId, String operateurId, String compteId,
            String transactionId, BigDecimal montant, String origineFonds) {
        AuditLog log = new AuditLog();
        log.setTypeAction("TRANSACTION_EPARGNE_SEUIL_LCB_FT");
        log.setSeverite("INFO");
        log.setUtilisateur(operateurId);
        log.setModule("MUTUELLE_EPARGNE");
        log.setDescription("Transaction épargne au-dessus du seuil LCB-FT");
        log.setDetails(String.format("compteId=%s, transactionId=%s, montant=%s, origineFonds=%s",
                compteId, transactionId, montant != null ? montant.toPlainString() : "", origineFonds != null ? origineFonds : ""));
        log.setEntiteType("TransactionEpargne");
        log.setEntiteId(transactionId);
        log.setDateHeure(LocalDateTime.now());
        log.setPortee(PorteeAudit.ORGANISATION);
        if (organisationId != null) {
            organisationRepository.findByIdOptional(organisationId).ifPresent(log::setOrganisation);
        }
        auditLogRepository.persist(log);
    }

    /**
     * Enregistre un log d'audit KYC/AML quand un score de risque élevé est détecté.
     */
    @Transactional
    public void logKycRisqueEleve(UUID membreId, int scoreRisque, String niveauRisque) {
        AuditLog log = new AuditLog();
        log.setTypeAction("KYC_RISQUE_ELEVE");
        log.setSeverite("WARNING");
        log.setUtilisateur(membreId != null ? membreId.toString() : null);
        log.setModule("KYC_AML");
        log.setDescription("Score de risque KYC/AML élevé détecté");
        log.setDetails(String.format("membreId=%s, score=%d, niveau=%s", membreId, scoreRisque, niveauRisque));
        log.setEntiteType("KycDossier");
        log.setEntiteId(membreId != null ? membreId.toString() : null);
        log.setDateHeure(LocalDateTime.now());
        log.setPortee(PorteeAudit.PLATEFORME);
        auditLogRepository.persist(log);
    }

    /**
     * Enregistre un nouveau log d'audit
     */
    @Transactional
    public AuditLogResponse enregistrerLog(CreateAuditLogRequest request) {
        log.debug("Enregistrement d'un log d'audit: {}", request.typeAction());

        AuditLog auditLog = convertToEntity(request);
        auditLogRepository.persist(auditLog);

        return convertToDTO(auditLog);
    }

    /**
     * Récupère tous les logs avec pagination
     */
    public Map<String, Object> listerTous(int page, int size, String sortBy, String sortOrder) {
        log.debug("Récupération des logs d'audit - page: {}, size: {}", page, size);

        String orderBy = sortBy != null ? sortBy : "dateHeure";
        String order = "desc".equalsIgnoreCase(sortOrder) ? "DESC" : "ASC";

        var entityManager = auditLogRepository.getEntityManager();

        // Compter le total
        long total = auditLogRepository.count();

        // Récupérer les logs avec pagination
        var query = entityManager.createQuery(
                "SELECT a FROM AuditLog a ORDER BY a." + orderBy + " " + order, AuditLog.class);
        query.setFirstResult(page * size);
        query.setMaxResults(size);

        List<AuditLog> logs = query.getResultList();
        List<AuditLogResponse> dtos = logs.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());

        return Map.of(
                "data", dtos,
                "total", total,
                "page", page,
                "size", size,
                "totalPages", (int) Math.ceil((double) total / size));
    }

    /**
     * Recherche les logs avec filtres
     */
    public Map<String, Object> rechercher(
            LocalDateTime dateDebut, LocalDateTime dateFin,
            String typeAction, String severite, String utilisateur,
            String module, String ipAddress,
            int page, int size) {

        log.debug("Recherche de logs d'audit avec filtres");

        // Construire la requête dynamique avec Criteria API
        var entityManager = auditLogRepository.getEntityManager();
        var cb = entityManager.getCriteriaBuilder();
        var query = cb.createQuery(AuditLog.class);
        var root = query.from(AuditLog.class);

        var predicates = new ArrayList<jakarta.persistence.criteria.Predicate>();

        if (dateDebut != null) {
            predicates.add(cb.greaterThanOrEqualTo(root.get("dateHeure"), dateDebut));
        }
        if (dateFin != null) {
            predicates.add(cb.lessThanOrEqualTo(root.get("dateHeure"), dateFin));
        }
        if (typeAction != null && !typeAction.isEmpty()) {
            predicates.add(cb.equal(root.get("typeAction"), typeAction));
        }
        if (severite != null && !severite.isEmpty()) {
            predicates.add(cb.equal(root.get("severite"), severite));
        }
        if (utilisateur != null && !utilisateur.isEmpty()) {
            predicates.add(cb.like(cb.lower(root.get("utilisateur")),
                    "%" + utilisateur.toLowerCase() + "%"));
        }
        if (module != null && !module.isEmpty()) {
            predicates.add(cb.equal(root.get("module"), module));
        }
        if (ipAddress != null && !ipAddress.isEmpty()) {
            predicates.add(cb.like(root.get("ipAddress"), "%" + ipAddress + "%"));
        }

        query.where(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
        query.orderBy(cb.desc(root.get("dateHeure")));

        // Compter le total (avec son propre root pour éviter le partage de prédicats)
        var countQuery = cb.createQuery(Long.class);
        var countRoot = countQuery.from(AuditLog.class);
        countQuery.select(cb.count(countRoot));
        var countPredicates = new ArrayList<jakarta.persistence.criteria.Predicate>();
        if (dateDebut != null) countPredicates.add(cb.greaterThanOrEqualTo(countRoot.get("dateHeure"), dateDebut));
        if (dateFin != null) countPredicates.add(cb.lessThanOrEqualTo(countRoot.get("dateHeure"), dateFin));
        if (typeAction != null && !typeAction.isEmpty()) countPredicates.add(cb.equal(countRoot.get("typeAction"), typeAction));
        if (severite != null && !severite.isEmpty()) countPredicates.add(cb.equal(countRoot.get("severite"), severite));
        if (utilisateur != null && !utilisateur.isEmpty()) countPredicates.add(cb.like(cb.lower(countRoot.get("utilisateur")), "%" + utilisateur.toLowerCase() + "%"));
        if (module != null && !module.isEmpty()) countPredicates.add(cb.equal(countRoot.get("module"), module));
        if (ipAddress != null && !ipAddress.isEmpty()) countPredicates.add(cb.like(countRoot.get("ipAddress"), "%" + ipAddress + "%"));
        countQuery.where(countPredicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
        long total = entityManager.createQuery(countQuery).getSingleResult();

        // Récupérer les résultats avec pagination
        var typedQuery = entityManager.createQuery(query);
        typedQuery.setFirstResult(page * size);
        typedQuery.setMaxResults(size);

        List<AuditLog> logs = typedQuery.getResultList();
        List<AuditLogResponse> dtos = logs.stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());

        return Map.of(
                "data", dtos,
                "total", total,
                "page", page,
                "size", size,
                "totalPages", (int) Math.ceil((double) total / size));
    }

    /**
     * Récupère les statistiques d'audit
     */
    public Map<String, Object> getStatistiques() {
        long total = auditLogRepository.count();

        var entityManager = auditLogRepository.getEntityManager();

        long success = entityManager.createQuery(
                "SELECT COUNT(a) FROM AuditLog a WHERE a.severite = :severite", Long.class)
                .setParameter("severite", "SUCCESS")
                .getSingleResult();

        long errors = entityManager.createQuery(
                "SELECT COUNT(a) FROM AuditLog a WHERE a.severite IN :severites", Long.class)
                .setParameter("severites", List.of("ERROR", "CRITICAL"))
                .getSingleResult();

        long warnings = entityManager.createQuery(
                "SELECT COUNT(a) FROM AuditLog a WHERE a.severite = :severite", Long.class)
                .setParameter("severite", "WARNING")
                .getSingleResult();

        return Map.of(
                "total", total,
                "success", success,
                "errors", errors,
                "warnings", warnings);
    }

    /**
     * Convertit une entité en DTO
     */
    private AuditLogResponse convertToDTO(AuditLog auditLog) {
        AuditLogResponse dto = new AuditLogResponse();
        dto.setId(auditLog.getId());
        dto.setTypeAction(auditLog.getTypeAction());
        dto.setSeverite(auditLog.getSeverite());
        dto.setUtilisateur(auditLog.getUtilisateur());
        dto.setRole(auditLog.getRole());
        dto.setModule(auditLog.getModule());
        dto.setDescription(auditLog.getDescription());
        dto.setDetails(auditLog.getDetails());
        dto.setIpAddress(auditLog.getIpAddress());
        dto.setUserAgent(auditLog.getUserAgent());
        dto.setSessionId(auditLog.getSessionId());
        dto.setDateHeure(auditLog.getDateHeure());
        dto.setDonneesAvant(auditLog.getDonneesAvant());
        dto.setDonneesApres(auditLog.getDonneesApres());
        dto.setEntiteId(auditLog.getEntiteId());
        dto.setEntiteType(auditLog.getEntiteType());
        return dto;
    }

    /**
     * Convertit un DTO en entité
     */
    private AuditLog convertToEntity(CreateAuditLogRequest request) {
        AuditLog auditLog = new AuditLog();
        auditLog.setTypeAction(request.typeAction());
        auditLog.setSeverite(request.severite());
        auditLog.setUtilisateur(request.utilisateur());
        auditLog.setRole(request.role());
        auditLog.setModule(request.module());
        auditLog.setDescription(request.description());
        auditLog.setDetails(request.details());
        auditLog.setIpAddress(request.ipAddress());
        auditLog.setUserAgent(request.userAgent());
        auditLog.setSessionId(request.sessionId());
        auditLog.setDateHeure(request.dateHeure() != null ? request.dateHeure() : LocalDateTime.now());
        auditLog.setDonneesAvant(request.donneesAvant());
        auditLog.setDonneesApres(request.donneesApres());
        auditLog.setEntiteId(request.entiteId());
        auditLog.setEntiteType(request.entiteType());
        return auditLog;
    }
}
