package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.system.request.UpdateSystemConfigRequest;
import dev.lions.unionflow.server.api.dto.system.response.CacheStatsResponse;
import dev.lions.unionflow.server.api.dto.system.response.SystemConfigResponse;
import dev.lions.unionflow.server.api.dto.system.response.SystemTestResultResponse;
import io.quarkus.cache.CacheManager;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
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 java.util.Collection;
import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.Mockito.*;

@QuarkusTest
class SystemConfigServiceTest {

    @Inject
    SystemConfigService systemConfigService;

    @InjectMock
    CacheManager cacheManager;

    @InjectMock
    DataSource dataSource;

    // -------------------------------------------------------------------------
    // getSystemConfig
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getSystemConfig retourne un objet non-null")
    void getSystemConfig_returnsNonNullMap() {
        SystemConfigResponse config = systemConfigService.getSystemConfig();

        assertThat(config).isNotNull();
    }

    @Test
    @DisplayName("getSystemConfig contient les champs réseau, sécurité, performance et backup")
    void getSystemConfig_containsExpectedKeys() {
        SystemConfigResponse config = systemConfigService.getSystemConfig();

        assertThat(config).isNotNull();

        // Réseau
        assertThat(config.getNetworkTimeout()).isNotNull().isPositive();
        assertThat(config.getMaxRetries()).isNotNull().isPositive();
        assertThat(config.getConnectionPoolSize()).isNotNull().isPositive();

        // Sécurité
        assertThat(config.getTwoFactorAuthEnabled()).isNotNull();
        assertThat(config.getSessionTimeoutMinutes()).isNotNull().isPositive();
        assertThat(config.getAuditLoggingEnabled()).isNotNull();

        // Performance
        assertThat(config.getMetricsCollectionEnabled()).isNotNull();
        assertThat(config.getMetricsIntervalSeconds()).isNotNull().isPositive();

        // Backup
        assertThat(config.getAutoBackupEnabled()).isNotNull();
        assertThat(config.getBackupFrequency()).isNotBlank();
        assertThat(config.getBackupRetentionDays()).isNotNull().isPositive();
    }

    // -------------------------------------------------------------------------
    // updateSystemConfig
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("updateSystemConfig retourne la config système actuelle")
    void updateSystemConfig_returnsConfig() {
        UpdateSystemConfigRequest request = new UpdateSystemConfigRequest();

        SystemConfigResponse result = systemConfigService.updateSystemConfig(request);

        assertThat(result).isNotNull();
        assertThat(result.getSystemStatus()).isNotBlank();
    }

    // -------------------------------------------------------------------------
    // getCacheStats
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getCacheStats avec liste de caches vide retourne un résultat non-null")
    void getCacheStats_emptyCacheList_returnsEmptyMap() {
        // getCacheStats() dans SystemConfigService retourne des données hardcodées simulées
        // sans itérer sur CacheManager — on vérifie juste que l'appel ne plante pas
        CacheStatsResponse stats = systemConfigService.getCacheStats();

        assertThat(stats).isNotNull();
        assertThat(stats.getTotalEntries()).isGreaterThanOrEqualTo(0);
        assertThat(stats.getCaches()).isNotNull();
    }

    // -------------------------------------------------------------------------
    // clearCache
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("clearCache avec liste vide de noms de caches ne lance pas d'exception")
    void clearCache_noCaches_doesNotThrow() {
        Collection<String> emptyNames = Collections.emptyList();
        when(cacheManager.getCacheNames()).thenReturn(emptyNames);

        assertThatCode(() -> systemConfigService.clearCache()).doesNotThrowAnyException();

        verify(cacheManager).getCacheNames();
    }

    @Test
    @DisplayName("clearCache appelle invalidateAll sur chaque cache présent")
    @SuppressWarnings("unchecked")
    void clearCache_withCacheNames_callsInvalidateAll() {
        io.quarkus.cache.Cache mockCache = mock(io.quarkus.cache.Cache.class);
        io.smallrye.mutiny.Uni<Void> mockUni = mock(io.smallrye.mutiny.Uni.class);
        io.smallrye.mutiny.groups.UniAwait<Void> mockAwait = mock(io.smallrye.mutiny.groups.UniAwait.class);
        when(mockUni.await()).thenReturn(mockAwait);
        when(mockAwait.indefinitely()).thenReturn(null);
        when(mockCache.invalidateAll()).thenReturn(mockUni);

        Collection<String> names = List.of("my-cache");
        when(cacheManager.getCacheNames()).thenReturn(names);
        when(cacheManager.getCache("my-cache")).thenReturn(java.util.Optional.of(mockCache));

        assertThatCode(() -> systemConfigService.clearCache()).doesNotThrowAnyException();
    }

    @Test
    @DisplayName("clearCache avec getCache retournant Optional.empty() ignore silencieusement le cache absent (branche ifPresent non déclenchée)")
    void clearCache_cacheAbsent_ignoreSilencieusement() {
        // getCache retourne Optional.empty() → ifPresent non déclenché → pas d'appel à invalidateAll
        Collection<String> names = List.of("cache-absent");
        when(cacheManager.getCacheNames()).thenReturn(names);
        when(cacheManager.getCache("cache-absent")).thenReturn(java.util.Optional.empty());

        assertThatCode(() -> systemConfigService.clearCache()).doesNotThrowAnyException();

        verify(cacheManager).getCache("cache-absent");
    }

    @Test
    @DisplayName("clearCache lève RuntimeException si getCacheNames lève une exception (branche catch L181-184)")
    void clearCache_getCacheNamesThrows_relanceRuntimeException() {
        // getCacheNames lève une exception → le catch L181 la capture et lève RuntimeException
        when(cacheManager.getCacheNames()).thenThrow(new RuntimeException("Erreur cache simulée"));

        org.assertj.core.api.Assertions.assertThatThrownBy(() -> systemConfigService.clearCache())
                .isInstanceOf(RuntimeException.class)
                .hasMessageContaining("Erreur lors du nettoyage du cache");
    }

    @Test
    @DisplayName("clearCache avec plusieurs caches dont un absent et un présent invalide tous les présents")
    @SuppressWarnings("unchecked")
    void clearCache_mixtePresentsEtAbsents_invalideSeulementPresents() {
        io.quarkus.cache.Cache mockCache = mock(io.quarkus.cache.Cache.class);
        io.smallrye.mutiny.Uni<Void> mockUni = mock(io.smallrye.mutiny.Uni.class);
        io.smallrye.mutiny.groups.UniAwait<Void> mockAwait = mock(io.smallrye.mutiny.groups.UniAwait.class);
        when(mockUni.await()).thenReturn(mockAwait);
        when(mockAwait.indefinitely()).thenReturn(null);
        when(mockCache.invalidateAll()).thenReturn(mockUni);

        Collection<String> names = List.of("cache-present", "cache-manquant");
        when(cacheManager.getCacheNames()).thenReturn(names);
        when(cacheManager.getCache("cache-present")).thenReturn(java.util.Optional.of(mockCache));
        when(cacheManager.getCache("cache-manquant")).thenReturn(java.util.Optional.empty());

        assertThatCode(() -> systemConfigService.clearCache()).doesNotThrowAnyException();

        verify(mockCache).invalidateAll();
    }

    // -------------------------------------------------------------------------
    // testDatabaseConnection
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("testDatabaseConnection avec connexion valide retourne success=true")
    void testDatabaseConnection_successfulConnection_returnsSuccess() throws SQLException {
        Connection conn = mock(Connection.class);
        when(dataSource.getConnection()).thenReturn(conn);
        when(conn.isValid(5)).thenReturn(true);

        SystemTestResultResponse result = systemConfigService.testDatabaseConnection();

        assertThat(result).isNotNull();
        assertThat(result.getSuccess()).isTrue();
        assertThat(result.getTestType()).isEqualTo("DATABASE");
        assertThat(result.getMessage()).contains("réussie");
    }

    @Test
    @DisplayName("testDatabaseConnection avec exception retourne success=false")
    void testDatabaseConnection_failedConnection_returnsFailure() throws SQLException {
        when(dataSource.getConnection()).thenThrow(new SQLException("Connection refused"));

        SystemTestResultResponse result = systemConfigService.testDatabaseConnection();

        assertThat(result).isNotNull();
        assertThat(result.getSuccess()).isFalse();
        assertThat(result.getTestType()).isEqualTo("DATABASE");
        assertThat(result.getMessage()).contains("Connection refused");
    }

    @Test
    @DisplayName("testDatabaseConnection avec isValid=false retourne success=false")
    void testDatabaseConnection_invalidConnection_returnsFailure() throws SQLException {
        Connection conn = mock(Connection.class);
        when(dataSource.getConnection()).thenReturn(conn);
        when(conn.isValid(5)).thenReturn(false);

        SystemTestResultResponse result = systemConfigService.testDatabaseConnection();

        assertThat(result).isNotNull();
        assertThat(result.getSuccess()).isFalse();
        assertThat(result.getTestType()).isEqualTo("DATABASE");
    }

    // -------------------------------------------------------------------------
    // testEmailConfiguration
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("testEmailConfiguration retourne success=true (test simulé)")
    void testEmailConfiguration_returnsSuccess() {
        SystemTestResultResponse result = systemConfigService.testEmailConfiguration();

        assertThat(result).isNotNull();
        assertThat(result.getSuccess()).isTrue();
        assertThat(result.getTestType()).isEqualTo("EMAIL");
        assertThat(result.getMessage()).isNotBlank();
        assertThat(result.getResponseTimeMs()).isGreaterThanOrEqualTo(0L);
    }

    @Test
    @DisplayName("testEmailConfiguration avec thread interrompu retourne success=false (couvre catch block)")
    void testEmailConfiguration_interrupted_returnsFailure() {
        // Interrompre le thread → Thread.sleep(500) lève InterruptedException → couvre catch block
        Thread.currentThread().interrupt();

        SystemTestResultResponse result = systemConfigService.testEmailConfiguration();

        // Vider le flag interrupt (bonne pratique)
        Thread.interrupted();

        assertThat(result).isNotNull();
        assertThat(result.getSuccess()).isFalse();
        assertThat(result.getTestType()).isEqualTo("EMAIL");
    }
}
