package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.SystemLogRepository;
import io.agroal.api.AgroalDataSource;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import javax.sql.DataSource;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.OperatingSystemMXBean;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Service pour récupérer les métriques système réelles
 */
@Slf4j
@ApplicationScoped
public class SystemMetricsService {

    @Inject
    MembreRepository membreRepository;

    @Inject
    SystemLogRepository systemLogRepository;

    @Inject
    DataSource dataSource;

    @ConfigProperty(name = "quarkus.application.name")
    String applicationName;

    @ConfigProperty(name = "quarkus.application.version")
    String applicationVersion;

    @ConfigProperty(name = "quarkus.oidc.auth-server-url", defaultValue = "http://localhost:8180/realms/unionflow")
    String authServerUrl;

    @ConfigProperty(name = "quarkus.http.host", defaultValue = "localhost")
    String httpHost;

    @ConfigProperty(name = "quarkus.http.port", defaultValue = "8085")
    int httpPort;

    // Compteurs pour les métriques
    private final AtomicLong apiRequestsCount = new AtomicLong(0);
    private final AtomicLong apiRequestsLastHour = new AtomicLong(0);
    private final AtomicLong apiRequestsToday = new AtomicLong(0);
    private final AtomicLong requestCount = new AtomicLong(0);
    private final AtomicLong totalResponseTime = new AtomicLong(0);
    private long startTimeMillis;
    private LocalDateTime startTime;

    /**
     * Initialisation au démarrage
     */
    void onStart(@Observes StartupEvent event) {
        startTimeMillis = System.currentTimeMillis();
        startTime = LocalDateTime.now();
        log.info("SystemMetricsService initialized at {}", startTime);
    }

    /**
     * Récupérer toutes les métriques système
     */
    public SystemMetricsResponse getSystemMetrics() {
        log.debug("Collecting system metrics...");

        return SystemMetricsResponse.builder()
                // Métriques CPU
                .cpuUsagePercent(getCpuUsage())
                .availableProcessors(Runtime.getRuntime().availableProcessors())
                .systemLoadAverage(getSystemLoadAverage())

                // Métriques mémoire
                .totalMemoryBytes(getTotalMemory())
                .usedMemoryBytes(getUsedMemory())
                .freeMemoryBytes(getFreeMemory())
                .maxMemoryBytes(getMaxMemory())
                .memoryUsagePercent(getMemoryUsagePercent())
                .totalMemoryFormatted(SystemMetricsResponse.formatBytes(getTotalMemory()))
                .usedMemoryFormatted(SystemMetricsResponse.formatBytes(getUsedMemory()))
                .freeMemoryFormatted(SystemMetricsResponse.formatBytes(getFreeMemory()))

                // Métriques disque
                .totalDiskBytes(getTotalDiskSpace())
                .usedDiskBytes(getUsedDiskSpace())
                .freeDiskBytes(getFreeDiskSpace())
                .diskUsagePercent(getDiskUsagePercent())
                .totalDiskFormatted(SystemMetricsResponse.formatBytes(getTotalDiskSpace()))
                .usedDiskFormatted(SystemMetricsResponse.formatBytes(getUsedDiskSpace()))
                .freeDiskFormatted(SystemMetricsResponse.formatBytes(getFreeDiskSpace()))

                // Métriques utilisateurs
                .activeUsersCount(getActiveUsersCount())
                .totalUsersCount(getTotalUsersCount())
                .activeSessionsCount(getActiveSessionsCount())
                .failedLoginAttempts24h(getFailedLoginAttempts())

                // Métriques API
                .apiRequestsLastHour(apiRequestsLastHour.get())
                .apiRequestsToday(apiRequestsToday.get())
                .averageResponseTimeMs(getAverageResponseTime())
                .totalRequestsCount(apiRequestsCount.get())

                // Métriques base de données
                .dbConnectionPoolSize(getDbConnectionPoolSize())
                .dbActiveConnections(getDbActiveConnections())
                .dbIdleConnections(getDbIdleConnections())
                .dbHealthy(isDatabaseHealthy())

                // Métriques erreurs et logs (simulées pour l'instant, à implémenter avec vrai système de logs)
                .criticalErrorsCount(0)
                .warningsCount(0)
                .infoLogsCount(0)
                .debugLogsCount(0)
                .totalLogsCount(0L)

                // Métriques réseau (simulées, nécessiterait monitoring avancé)
                .networkBytesReceivedPerSec(0.0)
                .networkBytesSentPerSec(0.0)
                .networkInFormatted("0 B/s")
                .networkOutFormatted("0 B/s")

                // Métriques système
                .systemStatus(getSystemStatus())
                .uptimeMillis(getUptimeMillis())
                .uptimeFormatted(SystemMetricsResponse.formatUptime(getUptimeMillis()))
                .startTime(startTime)
                .currentTime(LocalDateTime.now())
                .javaVersion(System.getProperty("java.version"))
                .quarkusVersion(getQuarkusVersion())
                .applicationVersion(applicationVersion)

                // Métriques maintenance (à implémenter avec vrai système de backup)
                .lastBackup(null)
                .nextScheduledMaintenance(null)
                .lastMaintenance(null)

                // URLs
                .apiBaseUrl(getApiBaseUrl())
                .authServerUrl(authServerUrl)
                .cdnUrl(null)

                // Cache (à implémenter)
                .totalCacheSizeBytes(0L)
                .totalCacheSizeFormatted("0 B")
                .totalCacheEntries(0)

                .build();
    }

    // ==================== MÉTHODES DE CALCUL DES MÉTRIQUES ====================

    /**
     * CPU Usage (estimation basée sur la charge système)
     */
    private Double getCpuUsage() {
        OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
        double loadAvg = osBean.getSystemLoadAverage();
        int processors = osBean.getAvailableProcessors();

        if (loadAvg < 0) {
            return 0.0; // Non disponible sur certains OS
        }

        // Calcul approximatif : (load average / nb processeurs) * 100
        return Math.min(100.0, (loadAvg / processors) * 100.0);
    }

    /**
     * System Load Average
     */
    private Double getSystemLoadAverage() {
        return ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
    }

    /**
     * Mémoire totale
     */
    private Long getTotalMemory() {
        return Runtime.getRuntime().totalMemory();
    }

    /**
     * Mémoire utilisée
     */
    private Long getUsedMemory() {
        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();
    }

    /**
     * Mémoire libre
     */
    private Long getFreeMemory() {
        return Runtime.getRuntime().freeMemory();
    }

    /**
     * Mémoire maximale
     */
    private Long getMaxMemory() {
        return Runtime.getRuntime().maxMemory();
    }

    /**
     * Pourcentage mémoire utilisée
     */
    private Double getMemoryUsagePercent() {
        Runtime runtime = Runtime.getRuntime();
        long used = runtime.totalMemory() - runtime.freeMemory();
        long max = runtime.maxMemory();
        return (used * 100.0) / max;
    }

    /**
     * Espace disque total
     */
    private Long getTotalDiskSpace() {
        File root = new File("/");
        return root.getTotalSpace();
    }

    /**
     * Espace disque utilisé
     */
    private Long getUsedDiskSpace() {
        File root = new File("/");
        return root.getTotalSpace() - root.getFreeSpace();
    }

    /**
     * Espace disque libre
     */
    private Long getFreeDiskSpace() {
        File root = new File("/");
        return root.getFreeSpace();
    }

    /**
     * Pourcentage disque utilisé
     */
    private Double getDiskUsagePercent() {
        File root = new File("/");
        long total = root.getTotalSpace();
        long free = root.getFreeSpace();
        return ((total - free) * 100.0) / total;
    }

    /**
     * Nombre d'utilisateurs actifs (avec sessions actives)
     */
    private Integer getActiveUsersCount() {
        try {
            return (int) membreRepository.count("actif = true");
        } catch (Exception e) {
            log.error("Error getting active users count", e);
            return 0;
        }
    }

    /**
     * Nombre total d'utilisateurs
     */
    private Integer getTotalUsersCount() {
        try {
            return (int) membreRepository.count();
        } catch (Exception e) {
            log.error("Error getting total users count", e);
            return 0;
        }
    }

    /**
     * Nombre de sessions actives (proxy : membres avec compte ACTIF)
     */
    private Integer getActiveSessionsCount() {
        try {
            return (int) membreRepository.count("statutCompte = 'ACTIF'");
        } catch (Exception e) {
            log.warn("Impossible de compter les membres actifs", e);
            return 0;
        }
    }

    /**
     * Tentatives de login échouées (24h)
     */
    private Integer getFailedLoginAttempts() {
        try {
            return (int) systemLogRepository.countByLevelLast24h("ERROR");
        } catch (Exception e) {
            log.error("Error getting failed login attempts count", e);
            return 0;
        }
    }

    /**
     * Temps de réponse moyen API (basé sur les appels enregistrés via recordRequest)
     */
    private Double getAverageResponseTime() {
        long count = requestCount.get();
        if (count == 0) return 0.0;
        return (double) totalResponseTime.get() / count;
    }

    /**
     * Taille du pool de connexions DB
     */
    private Integer getDbConnectionPoolSize() {
        if (dataSource instanceof AgroalDataSource agroalDataSource) {
            try {
                var config = agroalDataSource.getConfiguration();
                if (config == null) return 0;
                var poolConfig = config.connectionPoolConfiguration();
                if (poolConfig == null) return 0;
                return poolConfig.maxSize();
            } catch (Exception e) {
                return 0;
            }
        }
        return 0;
    }

    /**
     * Connexions DB actives
     */
    private Integer getDbActiveConnections() {
        if (dataSource instanceof AgroalDataSource agroalDataSource) {
            try {
                var metrics = agroalDataSource.getMetrics();
                if (metrics == null) return 0;
                return (int) metrics.activeCount();
            } catch (Exception e) {
                return 0;
            }
        }
        return 0;
    }

    /**
     * Connexions DB en attente
     */
    private Integer getDbIdleConnections() {
        if (dataSource instanceof AgroalDataSource agroalDataSource) {
            try {
                var metrics = agroalDataSource.getMetrics();
                if (metrics == null) return 0;
                return (int) metrics.availableCount();
            } catch (Exception e) {
                return 0;
            }
        }
        return 0;
    }

    /**
     * État santé base de données
     */
    private Boolean isDatabaseHealthy() {
        try (Connection conn = dataSource.getConnection()) {
            return conn.isValid(5); // 5 secondes timeout
        } catch (SQLException e) {
            log.error("Database health check failed", e);
            return false;
        }
    }

    /**
     * Statut système
     */
    private String getSystemStatus() {
        if (!isDatabaseHealthy()) {
            return "DEGRADED";
        }
        OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
        if (osBean instanceof com.sun.management.OperatingSystemMXBean sunOs) {
            double cpuLoad = sunOs.getCpuLoad() * 100;
            if (cpuLoad > 90) return "DEGRADED";
        }
        MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
        if (memBean != null && memBean.getHeapMemoryUsage() != null) {
            long maxMem = memBean.getHeapMemoryUsage().getMax();
            long usedMem = memBean.getHeapMemoryUsage().getUsed();
            if (maxMem > 0 && (double) usedMem / maxMem > 0.95) return "DEGRADED";
        }
        return "OPERATIONAL";
    }

    /**
     * Uptime en millisecondes
     */
    private Long getUptimeMillis() {
        return System.currentTimeMillis() - startTimeMillis;
    }

    /**
     * Version Quarkus
     */
    private String getQuarkusVersion() {
        return io.quarkus.runtime.annotations.QuarkusMain.class.getPackage().getImplementationVersion();
    }

    /**
     * URL base API
     */
    private String getApiBaseUrl() {
        return "http://" + httpHost + ":" + httpPort;
    }

    /**
     * Incrémenter le compteur de requêtes API
     */
    public void incrementApiRequestCount() {
        apiRequestsCount.incrementAndGet();
        apiRequestsLastHour.incrementAndGet();
        apiRequestsToday.incrementAndGet();
    }

    /**
     * Enregistrer une requête avec son temps de réponse (en ms)
     * Permet le calcul du temps de réponse moyen via getAverageResponseTime()
     */
    public void recordRequest(long responseTimeMs) {
        requestCount.incrementAndGet();
        totalResponseTime.addAndGet(responseTimeMs);
        incrementApiRequestCount();
    }
}
