package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.entity.AlertConfiguration;
import dev.lions.unionflow.server.entity.SystemAlert;
import dev.lions.unionflow.server.repository.AlertConfigurationRepository;
import dev.lions.unionflow.server.repository.SystemAlertRepository;
import dev.lions.unionflow.server.repository.SystemLogRepository;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

/**
 * Tests unitaires purs (sans Quarkus) pour {@link AlertMonitoringService}.
 *
 * <p>Utilise {@code mockStatic(ManagementFactory.class)} pour injecter des beans MXBean
 * contrôlés et couvrir les branches inaccessibles sur certains systèmes (notamment Windows
 * où {@code getSystemLoadAverage()} retourne toujours -1).
 *
 * <p>Note : utilise {@code mock-maker-inline} (configuré via
 * {@code src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker}).
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("AlertMonitoringService — branches via mockStatic ManagementFactory")
class AlertMonitoringServiceMockStaticCoverageTest {

    private AlertMonitoringService service;

    private AlertConfigurationRepository alertConfigurationRepository;
    private SystemAlertRepository systemAlertRepository;
    private SystemLogRepository systemLogRepository;

    @BeforeEach
    void setUp() throws Exception {
        service = new AlertMonitoringService();

        alertConfigurationRepository = mock(AlertConfigurationRepository.class);
        systemAlertRepository = mock(SystemAlertRepository.class);
        systemLogRepository = mock(SystemLogRepository.class);

        injectField(service, "alertConfigurationRepository", alertConfigurationRepository);
        injectField(service, "systemAlertRepository", systemAlertRepository);
        injectField(service, "systemLogRepository", systemLogRepository);

        // Initialiser le champ privé lastCpuHighTime à null
        injectField(service, "lastCpuHighTime", null);
        injectField(service, "lastCpuUsage", 0.0);
    }

    // =========================================================================
    // L85 : branche ternaire loadAvg >= 0 → Math.min(100.0, (loadAvg / processors) * 100.0)
    // =========================================================================

    /**
     * Couvre la branche {@code loadAvg >= 0} à la ligne 85 :
     * {@code Math.min(100.0, (loadAvg / processors) * 100.0)}.
     *
     * <p>On injecte un {@code OperatingSystemMXBean} mock qui retourne
     * {@code getSystemLoadAverage() = 0.5} (valeur positive ≥ 0) et
     * {@code getAvailableProcessors() = 4}.
     * → {@code cpuUsage = Math.min(100.0, (0.5/4)*100.0) = Math.min(100.0, 12.5) = 12.5}.
     *
     * <p>Avec {@code threshold = 0}, {@code 12.5 > 0} → branche cpuUsage > threshold → true
     * → {@code lastCpuHighTime} est mis à null initialement → il est settté à now().
     * Aucune alerte n'est créée (première itération).
     */
    @Test
    @DisplayName("checkCpuThreshold : loadAvg >= 0 → Math.min branch (L85) via OperatingSystemMXBean mock")
    void checkCpuThreshold_loadAvgPositive_coversL85MathMinBranch() {
        AlertConfiguration config = buildConfig(true, 0, 60, false, 100, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class);
        when(osMock.getSystemLoadAverage()).thenReturn(0.5);   // >= 0 → branche Math.min à L85
        when(osMock.getAvailableProcessors()).thenReturn(4);

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);
            // Laisser getMemoryMXBean() retourner le vrai bean (checkMemory désactivée)

            assertThatCode(() -> service.monitorSystemMetrics())
                    .doesNotThrowAnyException();
        }

        // Première itération : lastCpuHighTime était null → settée à now(), pas d'alerte
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    /**
     * Couvre la branche {@code loadAvg >= 0} à L85 avec une valeur élevée déclenchant une alerte.
     *
     * <p>On simule un CPU à 200% (loadAvg=8, processors=4 → cpuUsage=200, cappé à 100%).
     * Après deux appels avec {@code durationMinutes=0}, l'alerte est créée.
     */
    @Test
    @DisplayName("checkCpuThreshold : loadAvg élevé → cpuUsage 100% → alerte créée en 2 appels (L85 Math.min branch)")
    void checkCpuThreshold_loadAvgHighPositive_triggersAlert_coversL85() {
        // Premier appel : reset lastCpuHighTime via threshold élevé
        AlertConfiguration resetConfig = buildConfig(true, 100, 0, false, 100, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);

        OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class);
        when(osMock.getSystemLoadAverage()).thenReturn(2.0);  // >= 0 → Math.min branch
        when(osMock.getAvailableProcessors()).thenReturn(4);

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);
            service.monitorSystemMetrics(); // cpuUsage=50%, threshold=100 → below → lastCpuHighTime=null
        }

        // Deuxième et troisième appels avec threshold=-1 (0.0 > -1 always), durationMinutes=0
        AlertConfiguration triggerConfig = buildConfig(true, -1, 0, false, 100, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(triggerConfig);
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Collections.emptyList());

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);
            doNothing().when(systemAlertRepository).persist(any(SystemAlert.class));

            // Appel 1 : lastCpuHighTime null → le setter
            service.monitorSystemMetrics();
            // Appel 2 : lastCpuHighTime != null, duration=0 >= 0 → create alert si pas récente
            service.monitorSystemMetrics();
        }

        verify(systemAlertRepository, times(1)).persist(any(SystemAlert.class));
    }

    // =========================================================================
    // L113-114 : catch(Exception e) dans checkCpuThreshold
    // =========================================================================

    /**
     * Couvre les lignes 113-114 ({@code catch(Exception e)} et {@code log.error(...)}) dans
     * {@code checkCpuThreshold()}.
     *
     * <p>On force {@code ManagementFactory.getOperatingSystemMXBean()} à lancer une
     * {@code RuntimeException}. Cette exception est capturée par le catch à L113,
     * loggée à L114, et la méthode retourne silencieusement.
     */
    @Test
    @DisplayName("checkCpuThreshold : ManagementFactory.getOperatingSystemMXBean() throw → catch L113-114 couvert")
    void checkCpuThreshold_managementFactoryThrows_coversCatchL113to114() {
        AlertConfiguration config = buildConfig(true, 80, 5, false, 100, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getOperatingSystemMXBean)
                    .thenThrow(new RuntimeException("Simulated JMX failure — covers catch L113-114"));

            // L113-114 : l'exception est catchée → monitorSystemMetrics ne propage pas
            assertThatCode(() -> service.monitorSystemMetrics())
                    .doesNotThrowAnyException();
        }

        // Aucune alerte créée car l'exception a court-circuité checkCpuThreshold
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    /**
     * Couvre L113-114 via une exception lancée pendant le calcul (NullPointerException
     * depuis le mock MemoryMXBean non configuré pour OperatingSystemMXBean).
     */
    @Test
    @DisplayName("checkCpuThreshold : OperatingSystemMXBean.getAvailableProcessors() throw → catch L113-114")
    void checkCpuThreshold_availableProcessorsThrows_coversCatchL113to114() {
        AlertConfiguration config = buildConfig(true, 80, 5, false, 100, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class);
        when(osMock.getSystemLoadAverage()).thenReturn(0.5);
        when(osMock.getAvailableProcessors())
                .thenThrow(new RuntimeException("Simulated getAvailableProcessors failure"));

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);

            assertThatCode(() -> service.monitorSystemMetrics())
                    .doesNotThrowAnyException();
        }

        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    // =========================================================================
    // L127 : branche ternaire maxMemory <= 0 → 0.0
    // =========================================================================

    /**
     * Couvre la branche {@code maxMemory <= 0} à la ligne 127 :
     * {@code double memoryUsage = maxMemory > 0 ? ... : 0.0}.
     *
     * <p>On injecte un {@code MemoryMXBean} mock dont {@code getHeapMemoryUsage().getMax()}
     * retourne -1 (valeur légale selon le contrat JMX : -1 signifie "not defined").
     * → {@code maxMemory = -1 <= 0} → branche false → {@code memoryUsage = 0.0}.
     *
     * <p>Avec threshold=0 : {@code 0.0 > 0} est false → pas d'alerte.
     */
    @Test
    @DisplayName("checkMemoryThreshold : maxMemory = -1 (non défini) → memoryUsage = 0.0 → branche false L127")
    void checkMemoryThreshold_maxMemoryMinusOne_coversL127FalseBranch() {
        AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        MemoryMXBean memMock = mock(MemoryMXBean.class);
        MemoryUsage heapUsageMock = mock(MemoryUsage.class);
        when(heapUsageMock.getMax()).thenReturn(-1L);    // maxMemory = -1 → branche false à L127
        when(heapUsageMock.getUsed()).thenReturn(100L);
        when(memMock.getHeapMemoryUsage()).thenReturn(heapUsageMock);

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getMemoryMXBean).thenReturn(memMock);

            assertThatCode(() -> service.monitorSystemMetrics())
                    .doesNotThrowAnyException();
        }

        // memoryUsage = 0.0, threshold = 0 → 0.0 > 0 est false → pas d'alerte
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    /**
     * Couvre L127 branche false avec maxMemory = 0.
     *
     * <p>La condition {@code maxMemory > 0} est false pour maxMemory = 0 aussi.
     */
    @Test
    @DisplayName("checkMemoryThreshold : maxMemory = 0 → memoryUsage = 0.0 → branche false L127")
    void checkMemoryThreshold_maxMemoryZero_coversL127FalseBranch() {
        AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        MemoryMXBean memMock = mock(MemoryMXBean.class);
        MemoryUsage heapUsageMock = mock(MemoryUsage.class);
        when(heapUsageMock.getMax()).thenReturn(0L);     // maxMemory = 0 → branche false à L127
        when(heapUsageMock.getUsed()).thenReturn(50L);
        when(memMock.getHeapMemoryUsage()).thenReturn(heapUsageMock);

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getMemoryMXBean).thenReturn(memMock);

            assertThatCode(() -> service.monitorSystemMetrics())
                    .doesNotThrowAnyException();
        }

        // memoryUsage = 0.0 → pas d'alerte
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    /**
     * Couvre L127 branche true (maxMemory > 0) + alerte mémoire créée.
     * Vérifie que le mock MemoryMXBean avec une valeur positive couvre la branche vraie.
     */
    @Test
    @DisplayName("checkMemoryThreshold : maxMemory > 0 + usage > threshold → alerte créée (L127 branche true)")
    void checkMemoryThreshold_maxMemoryPositive_usageAboveThreshold_createsAlert() {
        AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Collections.emptyList());
        doNothing().when(systemAlertRepository).persist(any(SystemAlert.class));

        MemoryMXBean memMock = mock(MemoryMXBean.class);
        MemoryUsage heapUsageMock = mock(MemoryUsage.class);
        when(heapUsageMock.getMax()).thenReturn(1000L);   // maxMemory = 1000 > 0 → branche true
        when(heapUsageMock.getUsed()).thenReturn(900L);   // usage = 90% > threshold 0% → alerte
        when(memMock.getHeapMemoryUsage()).thenReturn(heapUsageMock);

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getMemoryMXBean).thenReturn(memMock);

            assertThatCode(() -> service.monitorSystemMetrics())
                    .doesNotThrowAnyException();
        }

        verify(systemAlertRepository, times(1)).persist(any(SystemAlert.class));
    }

    // =========================================================================
    // L85 : combinaison de loadAvg >= 0 avec threshold dépassé (couvre Math.min)
    // =========================================================================

    /**
     * Couvre explicitement la branche {@code loadAvg < 0} (L85, branche true du ternaire)
     * pour compléter la couverture des deux branches du ternaire à L85.
     *
     * <p>On injecte {@code getSystemLoadAverage() = -1.0} (loadAvg < 0 → cpuUsage = 0.0).
     * Cela simule le comportement Windows où loadAvg = -1.
     */
    @Test
    @DisplayName("checkCpuThreshold : loadAvg = -1 (Windows) → cpuUsage = 0.0 → branche true L85 ternaire")
    void checkCpuThreshold_loadAvgNegative_coversL85TrueBranch() {
        AlertConfiguration config = buildConfig(true, 100, 5, false, 100, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class);
        when(osMock.getSystemLoadAverage()).thenReturn(-1.0);  // < 0 → cpuUsage = 0.0
        when(osMock.getAvailableProcessors()).thenReturn(4);

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);

            assertThatCode(() -> service.monitorSystemMetrics())
                    .doesNotThrowAnyException();
        }

        // cpuUsage = 0.0, threshold = 100 → 0.0 > 100 false → lastCpuHighTime = null → pas d'alerte
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    // =========================================================================
    // L229 : lambda anyMatch dans hasRecentCpuAlert
    // Couverture supplémentaire des branches de la lambda
    // =========================================================================

    /**
     * Couvre la branche où la liste des alertes récentes contient une alerte avec
     * {@code source = "CPU"} et {@code acknowledged = false} → {@code anyMatch} retourne true
     * → {@code hasRecentCpuAlert()} = true → aucune nouvelle alerte créée.
     *
     * <p>La lambda à L229 : {@code "CPU".equals(alert.getSource()) && !alert.getAcknowledged()}
     * — branche : source="CPU" true ET acknowledged=false → true → anyMatch=true.
     */
    @Test
    @DisplayName("hasRecentCpuAlert lambda L229 : source=CPU + acknowledged=false → anyMatch true → pas d'alerte")
    void hasRecentCpuAlert_lambda_sourceCpu_acknowledgedFalse_anyMatchTrue_noNewAlert() throws Exception {
        // Préparer : reset lastCpuHighTime à null d'abord
        injectField(service, "lastCpuHighTime", null);

        AlertConfiguration config = buildConfig(true, -1, 0, false, 100, false);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // Alerte existante non acquittée de source CPU
        SystemAlert existingCpuAlert = new SystemAlert();
        existingCpuAlert.setId(UUID.randomUUID());
        existingCpuAlert.setSource("CPU");
        existingCpuAlert.setAcknowledged(false);
        existingCpuAlert.setTimestamp(LocalDateTime.now().minusMinutes(10));

        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(existingCpuAlert));

        OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class);
        when(osMock.getSystemLoadAverage()).thenReturn(2.0);  // loadAvg >= 0 → Math.min branch L85
        when(osMock.getAvailableProcessors()).thenReturn(4);  // cpuUsage = 50% > -1

        try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
            mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);

            // Premier appel : lastCpuHighTime = null → setter
            service.monitorSystemMetrics();
            // Deuxième appel : duration 0 >= 0 → hasRecentCpuAlert() → true → pas d'alerte
            service.monitorSystemMetrics();
        }

        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    // =========================================================================
    // Helpers
    // =========================================================================

    private AlertConfiguration buildConfig(
            boolean cpuEnabled, int cpuThreshold, int cpuDuration,
            boolean memEnabled, int memThreshold,
            boolean critEnabled) {
        AlertConfiguration config = new AlertConfiguration();
        config.setId(UUID.randomUUID());
        config.setCpuHighAlertEnabled(cpuEnabled);
        config.setCpuThresholdPercent(cpuThreshold);
        config.setCpuDurationMinutes(cpuDuration);
        config.setMemoryLowAlertEnabled(memEnabled);
        config.setMemoryThresholdPercent(memThreshold);
        config.setCriticalErrorAlertEnabled(critEnabled);
        config.setErrorAlertEnabled(false);
        config.setConnectionFailureAlertEnabled(false);
        config.setConnectionFailureThreshold(100);
        config.setConnectionFailureWindowMinutes(5);
        config.setEmailNotificationsEnabled(false);
        config.setPushNotificationsEnabled(false);
        config.setSmsNotificationsEnabled(false);
        config.setAlertEmailRecipients("test@test.com");
        return config;
    }

    private void injectField(Object target, String fieldName, Object value) throws Exception {
        Class<?> clazz = target.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(target, value);
                return;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException("Field not found: " + fieldName + " in " + target.getClass());
    }
}
