package dev.lions.user.manager.service.impl;

import dev.lions.user.manager.dto.audit.AuditLogDTO;
import dev.lions.user.manager.enums.audit.TypeActionAudit;
import dev.lions.user.manager.service.AuditService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * Implémentation du service d'audit
 *
 * NOTES:
 * - Cette implémentation utilise un stockage en mémoire pour le développement
 * - En production, il faudrait utiliser une base de données (PostgreSQL avec Panache)
 * - Les logs sont également écrits via SLF4J pour être capturés par les systèmes de logging centralisés
 */
@ApplicationScoped
@Slf4j
public class AuditServiceImpl implements AuditService {

    // Stockage en mémoire (à remplacer par une DB en production)
    private final Map<String, AuditLogDTO> auditLogs = new ConcurrentHashMap<>();

    @ConfigProperty(name = "lions.audit.enabled", defaultValue = "true")
    boolean auditEnabled;

    @ConfigProperty(name = "lions.audit.log-to-database", defaultValue = "false")
    boolean logToDatabase;

    @Override
    public AuditLogDTO logAction(@Valid @NotNull AuditLogDTO auditLog) {
        if (!auditEnabled) {
            log.debug("Audit désactivé, log ignoré");
            return auditLog;
        }

        // Générer un ID si nécessaire
        if (auditLog.getId() == null) {
            auditLog.setId(UUID.randomUUID().toString());
        }

        // Ajouter le timestamp si nécessaire
        if (auditLog.getDateAction() == null) {
            auditLog.setDateAction(LocalDateTime.now());
        }

        // Log structuré pour les systèmes de logging (Graylog, Elasticsearch, etc.)
        log.info("AUDIT | Type: {} | Acteur: {} | Ressource: {} | Succès: {} | IP: {} | Détails: {}",
                 auditLog.getTypeAction(),
                 auditLog.getActeurUsername(),
                 auditLog.getRessourceType() + ":" + auditLog.getRessourceId(),
                 auditLog.isSuccessful(),
                 auditLog.getIpAddress(),
                 auditLog.getDescription());

        // Stocker en mémoire
        auditLogs.put(auditLog.getId(), auditLog);

        // TODO: Si logToDatabase = true, persister dans PostgreSQL via Panache
        // Exemple:
        // if (logToDatabase) {
        //     AuditLogEntity entity = AuditLogMapper.toEntity(auditLog);
        //     entity.persist();
        // }

        return auditLog;
    }

    @Override
    public void logSuccess(@NotNull TypeActionAudit typeAction,
                           @NotBlank String ressourceType,
                           String ressourceId,
                           String ressourceName,
                           @NotBlank String realmName,
                           @NotBlank String acteurUserId,
                           String description) {
        AuditLogDTO auditLog = AuditLogDTO.builder()
            .acteurUserId(acteurUserId)
            .acteurUsername(acteurUserId) // Utiliser acteurUserId comme username pour l'instant
            .typeAction(typeAction)
            .ressourceType(ressourceType)
            .ressourceId(ressourceId != null ? ressourceId : "")
            .success(true)
            .description(description)
            .dateAction(LocalDateTime.now())
            .build();

        logAction(auditLog);
    }

    @Override
    public void logFailure(@NotNull TypeActionAudit typeAction,
                           @NotBlank String ressourceType,
                           String ressourceId,
                           String ressourceName,
                           @NotBlank String realmName,
                           @NotBlank String acteurUserId,
                           String errorCode,
                           String errorMessage) {
        AuditLogDTO auditLog = AuditLogDTO.builder()
            .acteurUserId(acteurUserId)
            .acteurUsername(acteurUserId) // Utiliser acteurUserId comme username pour l'instant
            .typeAction(typeAction)
            .ressourceType(ressourceType)
            .ressourceId(ressourceId != null ? ressourceId : "")
            .success(false)
            .errorMessage(errorMessage)
            .dateAction(LocalDateTime.now())
            .build();

        logAction(auditLog);
    }

    @Override
    public List<AuditLogDTO> findByActeur(@NotBlank String acteurUserId,
                                          LocalDateTime dateDebut,
                                          LocalDateTime dateFin,
                                          int page,
                                          int pageSize) {
        return searchLogs(acteurUserId, dateDebut, dateFin, null, null, null, page, pageSize);
    }

    @Override
    public List<AuditLogDTO> findByRessource(@NotBlank String ressourceType,
                                            @NotBlank String ressourceId,
                                            LocalDateTime dateDebut,
                                            LocalDateTime dateFin,
                                            int page,
                                            int pageSize) {
        return searchLogs(null, dateDebut, dateFin, null, ressourceType, null, page, pageSize)
            .stream()
            .filter(log -> ressourceId.equals(log.getRessourceId()))
            .collect(Collectors.toList());
    }

    @Override
    public List<AuditLogDTO> findByTypeAction(@NotNull TypeActionAudit typeAction,
                                             @NotBlank String realmName,
                                             LocalDateTime dateDebut,
                                             LocalDateTime dateFin,
                                             int page,
                                             int pageSize) {
        return searchLogs(null, dateDebut, dateFin, typeAction, null, null, page, pageSize);
    }

    @Override
    public List<AuditLogDTO> findByRealm(@NotBlank String realmName,
                                        LocalDateTime dateDebut,
                                        LocalDateTime dateFin,
                                        int page,
                                        int pageSize) {
        // Pour l'instant, on retourne tous les logs car on n'a pas de champ realmName dans AuditLogDTO
        return searchLogs(null, dateDebut, dateFin, null, null, null, page, pageSize);
    }

    @Override
    public List<AuditLogDTO> findFailures(@NotBlank String realmName,
                                         LocalDateTime dateDebut,
                                         LocalDateTime dateFin,
                                         int page,
                                         int pageSize) {
        return searchLogs(null, dateDebut, dateFin, null, null, false, page, pageSize);
    }

    @Override
    public List<AuditLogDTO> findCriticalActions(@NotBlank String realmName,
                                                  LocalDateTime dateDebut,
                                                  LocalDateTime dateFin,
                                                  int page,
                                                  int pageSize) {
        // Les actions critiques sont USER_DELETE, ROLE_DELETE, etc.
        return auditLogs.values().stream()
            .filter(log -> {
                TypeActionAudit type = log.getTypeAction();
                return type == TypeActionAudit.USER_DELETE || 
                       type == TypeActionAudit.ROLE_DELETE ||
                       type == TypeActionAudit.SESSION_REVOKE_ALL;
            })
            .filter(log -> {
                if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
                    return false;
                }
                if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
                    return false;
                }
                return true;
            })
            .sorted((a, b) -> b.getDateAction().compareTo(a.getDateAction()))
            .skip((long) page * pageSize)
            .limit(pageSize)
            .collect(Collectors.toList());
    }

    @Override
    public Map<TypeActionAudit, Long> countByActionType(@NotBlank String realmName,
                                                         LocalDateTime dateDebut,
                                                         LocalDateTime dateFin) {
        return getActionStatistics(dateDebut, dateFin);
    }

    @Override
    public Map<String, Long> countByActeur(@NotBlank String realmName,
                                           LocalDateTime dateDebut,
                                           LocalDateTime dateFin) {
        return getUserActivityStatistics(dateDebut, dateFin);
    }

    @Override
    public Map<String, Long> countSuccessVsFailure(@NotBlank String realmName,
                                                    LocalDateTime dateDebut,
                                                    LocalDateTime dateFin) {
        long successCount = getSuccessCount(dateDebut, dateFin);
        long failureCount = getFailureCount(dateDebut, dateFin);
        
        Map<String, Long> result = new java.util.HashMap<>();
        result.put("success", successCount);
        result.put("failure", failureCount);
        return result;
    }

    @Override
    public String exportToCSV(@NotBlank String realmName,
                              LocalDateTime dateDebut,
                              LocalDateTime dateFin) {
        List<String> csvLines = exportLogsToCSV(dateDebut, dateFin);
        return String.join("\n", csvLines);
    }

    @Override
    public long purgeOldLogs(@NotNull LocalDateTime dateLimite) {
        long beforeCount = auditLogs.size();
        auditLogs.entrySet().removeIf(entry ->
            entry.getValue().getDateAction().isBefore(dateLimite)
        );
        long afterCount = auditLogs.size();
        return beforeCount - afterCount;
    }

    @Override
    public Map<String, Object> getAuditStatistics(@NotBlank String realmName,
                                                  LocalDateTime dateDebut,
                                                  LocalDateTime dateFin) {
        Map<String, Object> stats = new java.util.HashMap<>();
        stats.put("total", auditLogs.values().stream()
            .filter(log -> {
                if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
                    return false;
                }
                if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
                    return false;
                }
                return true;
            })
            .count());
        stats.put("success", getSuccessCount(dateDebut, dateFin));
        stats.put("failure", getFailureCount(dateDebut, dateFin));
        stats.put("byActionType", countByActionType(realmName, dateDebut, dateFin));
        stats.put("byActeur", countByActeur(realmName, dateDebut, dateFin));
        return stats;
    }

    // Méthode privée helper pour la recherche
    private List<AuditLogDTO> searchLogs(String acteurUsername, LocalDateTime dateDebut,
                                        LocalDateTime dateFin, TypeActionAudit typeAction,
                                        String ressourceType, Boolean succes,
                                        int page, int pageSize) {
        log.debug("Recherche de logs d'audit: acteur={}, dateDebut={}, dateFin={}, typeAction={}, succes={}",
                  acteurUsername, dateDebut, dateFin, typeAction, succes);

        return auditLogs.values().stream()
            .filter(log -> {
                // Filtre par acteur (si spécifié et non "*")
                if (acteurUsername != null && !"*".equals(acteurUsername) &&
                    !acteurUsername.equals(log.getActeurUsername())) {
                    return false;
                }

                // Filtre par date début
                if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
                    return false;
                }

                // Filtre par date fin
                if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
                    return false;
                }

                // Filtre par type d'action
                if (typeAction != null && !typeAction.equals(log.getTypeAction())) {
                    return false;
                }

                // Filtre par type de ressource
                if (ressourceType != null && !ressourceType.equals(log.getRessourceType())) {
                    return false;
                }

                // Filtre par succès/échec
                if (succes != null && succes != log.isSuccessful()) {
                    return false;
                }

                return true;
            })
            .sorted((a, b) -> b.getDateAction().compareTo(a.getDateAction())) // Tri décroissant par date
            .skip((long) page * pageSize)
            .limit(pageSize)
            .collect(Collectors.toList());
    }

    // Méthodes privées helper
    private Map<TypeActionAudit, Long> getActionStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
        log.debug("Calcul des statistiques d'actions entre {} et {}", dateDebut, dateFin);

        return auditLogs.values().stream()
            .filter(log -> {
                if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
                    return false;
                }
                if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
                    return false;
                }
                return true;
            })
            .collect(Collectors.groupingBy(
                AuditLogDTO::getTypeAction,
                Collectors.counting()
            ));
    }

    private Map<String, Long> getUserActivityStatistics(LocalDateTime dateDebut, LocalDateTime dateFin) {
        log.debug("Calcul des statistiques d'activité utilisateurs entre {} et {}", dateDebut, dateFin);

        return auditLogs.values().stream()
            .filter(log -> {
                if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
                    return false;
                }
                if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
                    return false;
                }
                return true;
            })
            .collect(Collectors.groupingBy(
                AuditLogDTO::getActeurUsername,
                Collectors.counting()
            ));
    }

    private long getFailureCount(LocalDateTime dateDebut, LocalDateTime dateFin) {
        log.debug("Comptage des échecs entre {} et {}", dateDebut, dateFin);

        return auditLogs.values().stream()
            .filter(log -> {
                if (log.isSuccessful()) {
                    return false; // On ne compte que les échecs
                }
                if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
                    return false;
                }
                if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
                    return false;
                }
                return true;
            })
            .count();
    }

    private long getSuccessCount(LocalDateTime dateDebut, LocalDateTime dateFin) {
        log.debug("Comptage des succès entre {} et {}", dateDebut, dateFin);

        return auditLogs.values().stream()
            .filter(log -> {
                if (!log.isSuccessful()) {
                    return false; // On ne compte que les succès
                }
                if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
                    return false;
                }
                if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
                    return false;
                }
                return true;
            })
            .count();
    }

    private List<String> exportLogsToCSV(LocalDateTime dateDebut, LocalDateTime dateFin) {
        log.info("Export CSV des logs d'audit entre {} et {}", dateDebut, dateFin);

        List<String> csvLines = new ArrayList<>();

        // En-tête CSV
        csvLines.add("ID,Date Action,Acteur,Type Action,Ressource Type,Ressource ID,Succès,Adresse IP,Détails,Message Erreur");

        // Données
        auditLogs.values().stream()
            .filter(log -> {
                if (dateDebut != null && log.getDateAction().isBefore(dateDebut)) {
                    return false;
                }
                if (dateFin != null && log.getDateAction().isAfter(dateFin)) {
                    return false;
                }
                return true;
            })
            .sorted((a, b) -> a.getDateAction().compareTo(b.getDateAction()))
            .forEach(log -> {
                String csvLine = String.format("%s,%s,%s,%s,%s,%s,%s,%s,\"%s\",\"%s\"",
                    log.getId(),
                    log.getDateAction(),
                    log.getActeurUsername(),
                    log.getTypeAction(),
                    log.getRessourceType(),
                    log.getRessourceId(),
                    log.isSuccessful(),
                    log.getIpAddress() != null ? log.getIpAddress() : "",
                    log.getDescription() != null ? log.getDescription().replace("\"", "\"\"") : "",
                    log.getErrorMessage() != null ? log.getErrorMessage().replace("\"", "\"\"") : ""
                );
                csvLines.add(csvLine);
            });

        log.info("Export CSV terminé: {} lignes", csvLines.size() - 1);
        return csvLines;
    }

    // ==================== Méthodes utilitaires ====================

    /**
     * Retourne le nombre total de logs en mémoire
     */
    public long getTotalCount() {
        return auditLogs.size();
    }

    /**
     * Vide tous les logs (ATTENTION: à utiliser uniquement en développement)
     */
    public void clearAll() {
        log.warn("ATTENTION: Suppression de tous les logs d'audit en mémoire");
        auditLogs.clear();
    }
}
