package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.admin.AuditLogDTO;
import dev.lions.unionflow.server.entity.AuditLog;
import dev.lions.unionflow.server.repository.AuditLogRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
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;
    
    /**
     * Enregistre un nouveau log d'audit
     */
    @Transactional
    public AuditLogDTO enregistrerLog(AuditLogDTO dto) {
        log.debug("Enregistrement d'un log d'audit: {}", dto.getTypeAction());
        
        AuditLog auditLog = convertToEntity(dto);
        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<AuditLogDTO> 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
        var countQuery = cb.createQuery(Long.class);
        countQuery.select(cb.count(countQuery.from(AuditLog.class)));
        countQuery.where(predicates.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<AuditLogDTO> 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 AuditLogDTO convertToDTO(AuditLog auditLog) {
        AuditLogDTO dto = new AuditLogDTO();
        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(AuditLogDTO dto) {
        AuditLog auditLog = new AuditLog();
        if (dto.getId() != null) {
            auditLog.setId(dto.getId());
        }
        auditLog.setTypeAction(dto.getTypeAction());
        auditLog.setSeverite(dto.getSeverite());
        auditLog.setUtilisateur(dto.getUtilisateur());
        auditLog.setRole(dto.getRole());
        auditLog.setModule(dto.getModule());
        auditLog.setDescription(dto.getDescription());
        auditLog.setDetails(dto.getDetails());
        auditLog.setIpAddress(dto.getIpAddress());
        auditLog.setUserAgent(dto.getUserAgent());
        auditLog.setSessionId(dto.getSessionId());
        auditLog.setDateHeure(dto.getDateHeure() != null ? dto.getDateHeure() : LocalDateTime.now());
        auditLog.setDonneesAvant(dto.getDonneesAvant());
        auditLog.setDonneesApres(dto.getDonneesApres());
        auditLog.setEntiteId(dto.getEntiteId());
        auditLog.setEntiteType(dto.getEntiteType());
        return auditLog;
    }
}

