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.mapper.AuditLogMapper; // DELETE - Wrong package
import dev.lions.user.manager.server.impl.mapper.AuditLogMapper; // ADD - Correct package
import dev.lions.user.manager.server.impl.entity.AuditLogEntity;
import dev.lions.user.manager.server.impl.repository.AuditLogRepository;
import dev.lions.user.manager.service.AuditService;
import io.quarkus.hibernate.orm.panache.PanacheQuery;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@ApplicationScoped
@Slf4j
public class AuditServiceImpl implements AuditService {

    @Inject
    AuditLogRepository auditLogRepository;

    @Inject
    AuditLogMapper auditLogMapper;

    @Inject
    EntityManager entityManager;

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

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

    @Override
    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public AuditLogDTO logAction(@Valid @NotNull AuditLogDTO auditLog) {
        if (!auditEnabled) {
            log.debug("Audit désactivé, action ignorée: {}", auditLog.getTypeAction());
            return auditLog;
        }

        log.info("AUDIT: [{}] {} - user:{} - ressource:{}/{} - status:{}",
                auditLog.getRealmName(),
                auditLog.getTypeAction(),
                auditLog.getActeurUsername(), // ou getActeurUserId()
                auditLog.getRessourceType(),
                auditLog.getRessourceId(),
                auditLog.getSuccess() != null && auditLog.getSuccess() ? "SUCCESS" : "FAILURE");

        if (logToDatabase) {
            try {
                // Ensure dateAction is set
                if (auditLog.getDateAction() == null) {
                    auditLog.setDateAction(LocalDateTime.now());
                }

                AuditLogEntity entity = auditLogMapper.toEntity(auditLog);
                auditLogRepository.persist(entity);

                // Mettre à jour l'ID du DTO avec l'ID généré par la base
                if (entity.id != null) {
                    auditLog.setId(entity.id.toString());
                }
            } catch (Exception e) {
                log.error("Erreur lors de la persistance du log d'audit", e);
                // On ne bloque pas l'action métier si l'audit échoue (sauf exigence contraire)
            }
        }

        return auditLog;
    }

    @Override
    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public void logSuccess(@NotNull TypeActionAudit typeAction,
            @NotBlank String ressourceType,
            String ressourceId,
            String ressourceName,
            @NotBlank String realmName,
            @NotBlank String acteurUserId,
            String description) {

        AuditLogDTO log = AuditLogDTO.builder()
                .typeAction(typeAction)
                .ressourceType(ressourceType)
                .ressourceId(ressourceId)
                .ressourceName(ressourceName)
                .realmName(realmName)
                .acteurUserId(acteurUserId)
                .acteurUsername(acteurUserId) // On map aussi le username pour la persistence Entity
                .description(description)
                .dateAction(LocalDateTime.now())
                .success(true)
                .build();

        logAction(log);
    }

    @Override
    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public void logFailure(@NotNull TypeActionAudit typeAction,
            @NotBlank String ressourceType,
            String ressourceId,
            String ressourceName,
            @NotBlank String realmName,
            @NotBlank String acteurUserId,
            String errorCode,
            String errorMessage) {

        AuditLogDTO log = AuditLogDTO.builder()
                .typeAction(typeAction)
                .ressourceType(ressourceType)
                .ressourceId(ressourceId)
                .ressourceName(ressourceName)
                .realmName(realmName)
                .acteurUserId(acteurUserId)
                .acteurUsername(acteurUserId)
                .description("Echec: " + errorCode)
                .errorMessage(errorMessage)
                .dateAction(LocalDateTime.now())
                .success(false)
                .build();

        logAction(log);
    }

    @Override
    public List<AuditLogDTO> findByActeur(@NotBlank String acteurUserId,
            LocalDateTime dateDebut,
            LocalDateTime dateFin,
            int page,
            int pageSize) {
        // Le repository cherche par auteurAction, qui est mappé sur acteurUsername dans
        // le DTO
        List<AuditLogEntity> entities = auditLogRepository.search(null, acteurUserId, dateDebut, dateFin, null, null,
                page,
                pageSize);
        return auditLogMapper.toDTOList(entities);
    }

    @Override
    public List<AuditLogDTO> findByRessource(@NotBlank String ressourceType,
            @NotBlank String ressourceId,
            LocalDateTime dateDebut,
            LocalDateTime dateFin,
            int page,
            int pageSize) {

        // Utilisation de Panache query directe car le repo search générique est limité
        // On cherche dans 'details' (description) ou 'userId' (ressourceId)
        String filter = "%" + ressourceId + "%";
        // Correction: userId est le nom du champ dans l'entité qui mappe ressourceId
        PanacheQuery<AuditLogEntity> q = auditLogRepository.find("userId = ?1 or details like ?2", ressourceId, filter);

        return auditLogMapper.toDTOList(q.page(page, pageSize).list());
    }

    @Override
    public List<AuditLogDTO> findByTypeAction(@NotNull TypeActionAudit typeAction,
            @NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin,
            int page,
            int pageSize) {
        List<AuditLogEntity> entities = auditLogRepository.search(realmName, null, dateDebut, dateFin,
                typeAction.name(), null, page,
                pageSize);
        return auditLogMapper.toDTOList(entities);
    }

    @Override
    public List<AuditLogDTO> findByRealm(@NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin,
            int page,
            int pageSize) {
        List<AuditLogEntity> entities = auditLogRepository.search(realmName, null, dateDebut, dateFin, null, null, page,
                pageSize);
        return auditLogMapper.toDTOList(entities);
    }

    @Override
    public List<AuditLogDTO> findFailures(@NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin,
            int page,
            int pageSize) {
        List<AuditLogEntity> entities = auditLogRepository.search(realmName, null, dateDebut, dateFin, null, false,
                page,
                pageSize);
        return auditLogMapper.toDTOList(entities);
    }

    @Override
    public List<AuditLogDTO> findCriticalActions(@NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin,
            int page,
            int pageSize) {
        List<AuditLogEntity> entities = auditLogRepository.search(realmName, null, dateDebut, dateFin, null, false,
                page, pageSize);
        return auditLogMapper.toDTOList(entities);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map<TypeActionAudit, Long> countByActionType(@NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin) {
        StringBuilder sql = new StringBuilder("SELECT action, COUNT(*) AS cnt FROM audit_logs WHERE realm_name = :realmName");
        if (dateDebut != null) sql.append(" AND timestamp >= :dateDebut");
        if (dateFin != null) sql.append(" AND timestamp <= :dateFin");
        sql.append(" GROUP BY action");
        var query = entityManager.createNativeQuery(sql.toString())
                .setParameter("realmName", realmName);
        if (dateDebut != null) query.setParameter("dateDebut", dateDebut);
        if (dateFin != null) query.setParameter("dateFin", dateFin);
        List<Object[]> rows = query.getResultList();
        Map<TypeActionAudit, Long> result = new HashMap<>();
        for (Object[] row : rows) {
            String actionStr = (String) row[0];
            Long count = ((Number) row[1]).longValue();
            try {
                result.put(TypeActionAudit.valueOf(actionStr), count);
            } catch (IllegalArgumentException e) {
                log.debug("TypeActionAudit inconnu ignoré: {}", actionStr);
            }
        }
        return result;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map<String, Long> countByActeur(@NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin) {
        StringBuilder sql = new StringBuilder("SELECT auteur_action, COUNT(*) AS cnt FROM audit_logs WHERE realm_name = :realmName");
        if (dateDebut != null) sql.append(" AND timestamp >= :dateDebut");
        if (dateFin != null) sql.append(" AND timestamp <= :dateFin");
        sql.append(" GROUP BY auteur_action ORDER BY COUNT(*) DESC LIMIT 10");
        var query = entityManager.createNativeQuery(sql.toString())
                .setParameter("realmName", realmName);
        if (dateDebut != null) query.setParameter("dateDebut", dateDebut);
        if (dateFin != null) query.setParameter("dateFin", dateFin);
        List<Object[]> rows = query.getResultList();
        Map<String, Long> result = new HashMap<>();
        for (Object[] row : rows) {
            result.put((String) row[0], ((Number) row[1]).longValue());
        }
        return result;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map<String, Long> countSuccessVsFailure(@NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin) {
        StringBuilder sql = new StringBuilder("SELECT success, COUNT(*) AS cnt FROM audit_logs WHERE realm_name = :realmName");
        if (dateDebut != null) sql.append(" AND timestamp >= :dateDebut");
        if (dateFin != null) sql.append(" AND timestamp <= :dateFin");
        sql.append(" GROUP BY success");
        var query = entityManager.createNativeQuery(sql.toString())
                .setParameter("realmName", realmName);
        if (dateDebut != null) query.setParameter("dateDebut", dateDebut);
        if (dateFin != null) query.setParameter("dateFin", dateFin);
        List<Object[]> rows = query.getResultList();
        Map<String, Long> result = new HashMap<>();
        result.put("success", 0L);
        result.put("failure", 0L);
        for (Object[] row : rows) {
            Boolean success = (Boolean) row[0];
            Long count = ((Number) row[1]).longValue();
            result.put(Boolean.TRUE.equals(success) ? "success" : "failure", count);
        }
        return result;
    }

    @Override
    public String exportToCSV(@NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin) {
        List<AuditLogEntity> entities = auditLogRepository.search(realmName, null, dateDebut, dateFin, null, null, 0, Integer.MAX_VALUE);
        List<AuditLogDTO> logs = auditLogMapper.toDTOList(entities);
        StringBuilder csv = new StringBuilder();
        csv.append("id;typeAction;acteur;realmName;ressourceType;ressourceId;succes;dateAction;message\n");
        for (AuditLogDTO dto : logs) {
            csv.append(escapeCsv(dto.getId()));
            csv.append(";");
            csv.append(escapeCsv(dto.getTypeAction() != null ? dto.getTypeAction().name() : ""));
            csv.append(";");
            csv.append(escapeCsv(dto.getActeurUsername()));
            csv.append(";");
            csv.append(escapeCsv(dto.getRealmName()));
            csv.append(";");
            csv.append(escapeCsv(dto.getRessourceType()));
            csv.append(";");
            csv.append(escapeCsv(dto.getRessourceId()));
            csv.append(";");
            csv.append(dto.getSuccess() != null && dto.getSuccess() ? "true" : "false");
            csv.append(";");
            csv.append(dto.getDateAction() != null ? dto.getDateAction().toString() : "");
            csv.append(";");
            csv.append(escapeCsv(dto.getErrorMessage() != null ? dto.getErrorMessage() : (dto.getDescription() != null ? dto.getDescription() : "")));
            csv.append("\n");
        }
        return csv.toString();
    }

    private static String escapeCsv(String value) {
        if (value == null) return "";
        if (value.contains(";") || value.contains("\"") || value.contains("\n")) {
            return "\"" + value.replace("\"", "\"\"") + "\"";
        }
        return value;
    }

    @Override
    @Transactional
    public long purgeOldLogs(@NotNull LocalDateTime dateLimite) {
        return auditLogRepository.delete("timestamp < ?1", dateLimite);
    }

    @Override
    public Map<String, Object> getAuditStatistics(@NotBlank String realmName,
            LocalDateTime dateDebut,
            LocalDateTime dateFin) {
        Map<String, Object> stats = new java.util.HashMap<>();
        stats.put("total", auditLogRepository.count("realmName", realmName));
        return stats;
    }

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

    /**
     * Retourne le nombre total de logs (Utilisé par les tests)
     */
    public long getTotalCount() {
        return auditLogRepository.count();
    }

    /**
     * Vide tous les logs (Utilisé par les tests)
     */
    @Transactional
    public void clearAll() {
        log.warn("ATTENTION: Suppression de tous les logs d'audit en base");
        auditLogRepository.deleteAll();
    }
}
