package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
import dev.lions.unionflow.server.repository.MembreRepository;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * Tests unitaires pour {@link SystemMetricsService}.
 *
 * Couverture : getSystemMetrics(), incrementApiRequestCount(),
 * isDatabaseHealthy() (succès + SQLException), getActiveUsersCount() (succès + exception),
 * getTotalUsersCount() (succès + exception), et les branches AgroalDataSource (branche false).
 */
@QuarkusTest
class SystemMetricsServiceTest {

    @Inject
    SystemMetricsService systemMetricsService;

    @InjectMock
    MembreRepository membreRepository;

    @InjectMock
    DataSource dataSource;

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    /** Crée un mock Connection dont isValid(5) retourne true. */
    private Connection validConnection() throws SQLException {
        Connection conn = mock(Connection.class);
        when(conn.isValid(5)).thenReturn(true);
        return conn;
    }

    // -------------------------------------------------------------------------
    // Setup par défaut commun à la plupart des tests
    // -------------------------------------------------------------------------

    @BeforeEach
    void setupDefaultMocks() throws SQLException {
        // Par défaut : comptes membres retournent 5 actifs et 10 au total
        when(membreRepository.count(anyString(), (Object[]) any())).thenReturn(5L);
        when(membreRepository.count()).thenReturn(10L);
        // Par défaut : la connexion DB est valide — créer le mock AVANT de l'utiliser dans when()
        Connection conn = mock(Connection.class);
        when(conn.isValid(5)).thenReturn(true);
        when(dataSource.getConnection()).thenReturn(conn);
    }

    // -------------------------------------------------------------------------
    // getSystemMetrics — chemin principal
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getSystemMetrics retourne une réponse non nulle avec les champs principaux renseignés")
    void getSystemMetrics_returnsNonNullResponse() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response).isNotNull();
    }

    @Test
    @DisplayName("getSystemMetrics — champs mémoire non nuls")
    void getSystemMetrics_memoryFieldsNonNull() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getTotalMemoryBytes()).isNotNull().isPositive();
        assertThat(response.getUsedMemoryBytes()).isNotNull().isNotNegative();
        assertThat(response.getFreeMemoryBytes()).isNotNull().isNotNegative();
        assertThat(response.getMaxMemoryBytes()).isNotNull().isPositive();
        assertThat(response.getMemoryUsagePercent()).isNotNull().isBetween(0.0, 100.0);
        assertThat(response.getTotalMemoryFormatted()).isNotNull().isNotBlank();
        assertThat(response.getUsedMemoryFormatted()).isNotNull().isNotBlank();
        assertThat(response.getFreeMemoryFormatted()).isNotNull().isNotBlank();
    }

    @Test
    @DisplayName("getSystemMetrics — champs CPU non nuls")
    void getSystemMetrics_cpuFieldsNonNull() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getCpuUsagePercent()).isNotNull().isNotNegative();
        assertThat(response.getAvailableProcessors()).isNotNull().isPositive();
        // systemLoadAverage peut être -1 sur Windows — on vérifie juste la présence
        assertThat(response.getSystemLoadAverage()).isNotNull();
    }

    @Test
    @DisplayName("getSystemMetrics — champs disque non nuls")
    void getSystemMetrics_diskFieldsNonNull() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getTotalDiskBytes()).isNotNull().isNotNegative();
        assertThat(response.getFreeDiskBytes()).isNotNull().isNotNegative();
        assertThat(response.getUsedDiskBytes()).isNotNull().isNotNegative();
        assertThat(response.getDiskUsagePercent()).isNotNull().isNotNegative();
        assertThat(response.getTotalDiskFormatted()).isNotNull();
        assertThat(response.getUsedDiskFormatted()).isNotNull();
        assertThat(response.getFreeDiskFormatted()).isNotNull();
    }

    @Test
    @DisplayName("getSystemMetrics — métriques utilisateurs reflètent les mocks du repository")
    void getSystemMetrics_userMetricsFromRepository() {
        when(membreRepository.count(anyString(), (Object[]) any())).thenReturn(7L);
        when(membreRepository.count()).thenReturn(42L);

        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getActiveUsersCount()).isEqualTo(7);
        assertThat(response.getTotalUsersCount()).isEqualTo(42);
        assertThat(response.getActiveSessionsCount()).isEqualTo(0);
        assertThat(response.getFailedLoginAttempts24h()).isEqualTo(0);
    }

    @Test
    @DisplayName("getSystemMetrics — champs API initiaux à zéro")
    void getSystemMetrics_apiCountersInitiallyZero() {
        // NOTE : d'autres tests peuvent avoir incrémenté le compteur dans le même contexte Quarkus.
        // On vérifie uniquement que le champ est non-négatif.
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getTotalRequestsCount()).isNotNegative();
        assertThat(response.getApiRequestsLastHour()).isNotNegative();
        assertThat(response.getApiRequestsToday()).isNotNegative();
        assertThat(response.getAverageResponseTimeMs()).isEqualTo(0.0);
    }

    @Test
    @DisplayName("getSystemMetrics — DB healthy=true quand la connexion est valide")
    void getSystemMetrics_dbHealthy_whenConnectionValid() throws SQLException {
        // Créer le mock de connexion AVANT l'appel when() pour éviter UnfinishedStubbingException
        Connection conn = validConnection();
        when(dataSource.getConnection()).thenReturn(conn);

        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getDbHealthy()).isTrue();
    }

    @Test
    @DisplayName("getSystemMetrics — champs DB pool à 0 (DataSource n'est pas AgroalDataSource)")
    void getSystemMetrics_dbPoolFields_zeroWhenNotAgroal() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        // Le DataSource mocké n'est pas un AgroalDataSource → branches if = false → retournent 0
        assertThat(response.getDbConnectionPoolSize()).isEqualTo(0);
        assertThat(response.getDbActiveConnections()).isEqualTo(0);
        assertThat(response.getDbIdleConnections()).isEqualTo(0);
    }

    @Test
    @DisplayName("getSystemMetrics — champs système (status, uptime, version)")
    void getSystemMetrics_systemFields() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getSystemStatus()).isIn("OPERATIONAL", "OK", "WARNING", "CRITICAL");
        assertThat(response.getUptimeMillis()).isNotNull().isNotNegative();
        assertThat(response.getUptimeFormatted()).isNotNull();
        assertThat(response.getStartTime()).isNotNull();
        assertThat(response.getCurrentTime()).isNotNull();
        assertThat(response.getJavaVersion()).isNotNull().isNotBlank();
        assertThat(response.getApplicationVersion()).isNotNull();
        assertThat(response.getApiBaseUrl()).isEqualTo("http://localhost:8085");
        assertThat(response.getAuthServerUrl()).isNotNull();
    }

    @Test
    @DisplayName("getSystemMetrics — champs réseaux et cache simulés à zéro")
    void getSystemMetrics_networkAndCacheFieldsZero() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getNetworkBytesReceivedPerSec()).isEqualTo(0.0);
        assertThat(response.getNetworkBytesSentPerSec()).isEqualTo(0.0);
        assertThat(response.getNetworkInFormatted()).isEqualTo("0 B/s");
        assertThat(response.getNetworkOutFormatted()).isEqualTo("0 B/s");
        assertThat(response.getTotalCacheSizeBytes()).isEqualTo(0L);
        assertThat(response.getTotalCacheSizeFormatted()).isEqualTo("0 B");
        assertThat(response.getTotalCacheEntries()).isEqualTo(0);
    }

    @Test
    @DisplayName("getSystemMetrics — champs logs simulés à zéro")
    void getSystemMetrics_logCountsZero() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getCriticalErrorsCount()).isEqualTo(0);
        assertThat(response.getWarningsCount()).isEqualTo(0);
        assertThat(response.getInfoLogsCount()).isEqualTo(0);
        assertThat(response.getDebugLogsCount()).isEqualTo(0);
        assertThat(response.getTotalLogsCount()).isEqualTo(0L);
    }

    @Test
    @DisplayName("getSystemMetrics — champs maintenance et CDN nulls")
    void getSystemMetrics_maintenanceFieldsNull() {
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getLastBackup()).isNull();
        assertThat(response.getNextScheduledMaintenance()).isNull();
        assertThat(response.getLastMaintenance()).isNull();
        assertThat(response.getCdnUrl()).isNull();
    }

    // -------------------------------------------------------------------------
    // isDatabaseHealthy — branche catch (SQLException)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getSystemMetrics — dbHealthy=false quand getConnection() lève une SQLException")
    void getSystemMetrics_dbHealthy_falseOnSqlException() throws SQLException {
        when(dataSource.getConnection()).thenThrow(new SQLException("Connection refused"));

        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getDbHealthy()).isFalse();
    }

    @Test
    @DisplayName("getSystemMetrics — dbHealthy=false quand isValid() retourne false")
    void getSystemMetrics_dbHealthy_falseWhenConnectionInvalid() throws SQLException {
        Connection conn = mock(Connection.class);
        when(conn.isValid(5)).thenReturn(false);
        when(dataSource.getConnection()).thenReturn(conn);

        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getDbHealthy()).isFalse();
    }

    // -------------------------------------------------------------------------
    // getActiveUsersCount — branche catch
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getSystemMetrics — activeUsersCount=0 quand membreRepository.count(query) lève une exception")
    void getSystemMetrics_activeUsersCount_zeroOnRepositoryException() throws SQLException {
        when(membreRepository.count(anyString(), (Object[]) any())).thenThrow(new RuntimeException("DB error"));
        Connection conn = validConnection();
        when(dataSource.getConnection()).thenReturn(conn);

        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getActiveUsersCount()).isEqualTo(0);
    }

    // -------------------------------------------------------------------------
    // getTotalUsersCount — branche catch
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getSystemMetrics — totalUsersCount=0 quand membreRepository.count() lève une exception")
    void getSystemMetrics_totalUsersCount_zeroOnRepositoryException() throws SQLException {
        when(membreRepository.count()).thenThrow(new RuntimeException("DB error"));
        Connection conn = validConnection();
        when(dataSource.getConnection()).thenReturn(conn);

        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getTotalUsersCount()).isEqualTo(0);
    }

    // -------------------------------------------------------------------------
    // incrementApiRequestCount
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("incrementApiRequestCount incrémente le compteur totalRequestsCount")
    void incrementApiRequestCount_incrementsTotalCount() throws SQLException {
        Connection conn = validConnection();
        when(dataSource.getConnection()).thenReturn(conn);

        long before = systemMetricsService.getSystemMetrics().getTotalRequestsCount();

        systemMetricsService.incrementApiRequestCount();
        systemMetricsService.incrementApiRequestCount();
        systemMetricsService.incrementApiRequestCount();

        long after = systemMetricsService.getSystemMetrics().getTotalRequestsCount();

        assertThat(after).isEqualTo(before + 3);
    }

    @Test
    @DisplayName("incrementApiRequestCount incrémente apiRequestsLastHour et apiRequestsToday")
    void incrementApiRequestCount_incrementsHourlyAndDailyCounters() throws SQLException {
        Connection conn = validConnection();
        when(dataSource.getConnection()).thenReturn(conn);

        long beforeLastHour = systemMetricsService.getSystemMetrics().getApiRequestsLastHour();
        long beforeToday = systemMetricsService.getSystemMetrics().getApiRequestsToday();

        systemMetricsService.incrementApiRequestCount();

        long afterLastHour = systemMetricsService.getSystemMetrics().getApiRequestsLastHour();
        long afterToday = systemMetricsService.getSystemMetrics().getApiRequestsToday();

        assertThat(afterLastHour).isEqualTo(beforeLastHour + 1);
        assertThat(afterToday).isEqualTo(beforeToday + 1);
    }

    // -------------------------------------------------------------------------
    // getCpuUsage — branche loadAvg < 0 (Windows peut retourner -1)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getSystemMetrics — cpuUsagePercent est >= 0 même quand loadAvg est indisponible")
    void getSystemMetrics_cpuUsage_nonNegativeEvenWhenUnavailable() {
        // Sur Windows, getSystemLoadAverage() retourne -1 → la branche `if (loadAvg < 0)` retourne 0.0
        // Sur Linux, elle retourne une valeur >= 0. Dans les deux cas la valeur doit être >= 0.
        SystemMetricsResponse response = systemMetricsService.getSystemMetrics();

        assertThat(response.getCpuUsagePercent()).isNotNull().isGreaterThanOrEqualTo(0.0);
    }
}
