package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
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 io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests unitaires pour AlertMonitoringService.
 *
 * <p>Le scheduler @Scheduled est appelé directement en tant que méthode publique pour tester
 * toutes les branches internes (checkCpuThreshold, checkMemoryThreshold, checkCriticalErrors)
 * via leurs effets de bord (persist, logs).
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-20
 */
@QuarkusTest
class AlertMonitoringServiceTest {

    @Inject
    AlertMonitoringService alertMonitoringService;

    @InjectMock
    AlertConfigurationRepository alertConfigurationRepository;

    @InjectMock
    SystemAlertRepository systemAlertRepository;

    @InjectMock
    SystemLogRepository systemLogRepository;

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

    /**
     * Builds a minimal AlertConfiguration with all checks disabled.
     */
    private AlertConfiguration buildDisabledConfig() {
        AlertConfiguration config = new AlertConfiguration();
        config.setId(UUID.randomUUID());
        config.setCpuHighAlertEnabled(false);
        config.setCpuThresholdPercent(80);
        config.setCpuDurationMinutes(5);
        config.setMemoryLowAlertEnabled(false);
        config.setMemoryThresholdPercent(85);
        config.setCriticalErrorAlertEnabled(false);
        config.setErrorAlertEnabled(false);
        config.setConnectionFailureAlertEnabled(false);
        config.setConnectionFailureThreshold(100);
        config.setConnectionFailureWindowMinutes(5);
        config.setEmailNotificationsEnabled(true);
        config.setPushNotificationsEnabled(false);
        config.setSmsNotificationsEnabled(false);
        config.setAlertEmailRecipients("admin@unionflow.test");
        return config;
    }

    /**
     * Builds a config with all checks enabled and very low thresholds to easily trigger alerts.
     * CPU threshold = 0 (so any CPU usage > 0 triggers), but since lastCpuHighTime logic requires
     * a second call to elapse durationMinutes, we set cpuDurationMinutes = 0 which won't trigger
     * during a single call (use cpuHighAlertEnabled independently).
     */
    private AlertConfiguration buildEnabledConfig() {
        AlertConfiguration config = new AlertConfiguration();
        config.setId(UUID.randomUUID());
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(0);    // any CPU > 0 sets lastCpuHighTime first call
        config.setCpuDurationMinutes(1000);  // long enough so alert won't actually fire in single call
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(0); // any memory usage > 0 triggers memory check
        config.setCriticalErrorAlertEnabled(true);
        config.setErrorAlertEnabled(true);
        config.setConnectionFailureAlertEnabled(true);
        config.setConnectionFailureThreshold(100);
        config.setConnectionFailureWindowMinutes(5);
        config.setEmailNotificationsEnabled(true);
        config.setPushNotificationsEnabled(false);
        config.setSmsNotificationsEnabled(false);
        config.setAlertEmailRecipients("admin@unionflow.test");
        return config;
    }

    private SystemAlert buildCpuAlert() {
        SystemAlert alert = new SystemAlert();
        alert.setId(UUID.randomUUID());
        alert.setSource("CPU");
        alert.setAlertType("THRESHOLD");
        alert.setAcknowledged(false);
        alert.setTimestamp(LocalDateTime.now().minusMinutes(5));
        return alert;
    }

    private SystemAlert buildMemoryAlert() {
        SystemAlert alert = new SystemAlert();
        alert.setId(UUID.randomUUID());
        alert.setSource("MEMORY");
        alert.setAlertType("THRESHOLD");
        alert.setAcknowledged(false);
        alert.setTimestamp(LocalDateTime.now().minusMinutes(5));
        return alert;
    }

    private SystemAlert buildCriticalErrorsAlert() {
        SystemAlert alert = new SystemAlert();
        alert.setId(UUID.randomUUID());
        alert.setSource("System");
        alert.setAlertType("ERROR");
        alert.setAcknowledged(false);
        alert.setTimestamp(LocalDateTime.now().minusMinutes(15));
        return alert;
    }

    // ==================== Tests: all checks DISABLED ====================

    @Test
    @DisplayName("monitorSystemMetrics - all checks disabled => no repository calls beyond getConfiguration")
    void monitorSystemMetrics_allChecksDisabled_noAlerts() {
        AlertConfiguration config = buildDisabledConfig();
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        alertMonitoringService.monitorSystemMetrics();

        verify(alertConfigurationRepository).getConfiguration();
        // Since all flags are false, none of the check methods runs, so:
        verify(systemLogRepository, never()).countByLevelLast24h(any());
        verify(systemAlertRepository, never()).findByTimestampBetween(any(), any());
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    // ==================== Tests: CPU check enabled ====================

    @Test
    @DisplayName("monitorSystemMetrics - CPU enabled, no recent CPU alert => lastCpuHighTime set on first call when CPU > threshold")
    void monitorSystemMetrics_cpuEnabled_firstCallSetsLastCpuHighTime() {
        // threshold=0 so any positive CPU usage > 0% is above threshold.
        AlertConfiguration config = buildDisabledConfig();
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(0);    // all CPU > 0 triggers
        config.setCpuDurationMinutes(60);    // duration long enough that alert won't fire
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // On first call: lastCpuHighTime is null, so it gets set but no alert is created yet.
        // findByTimestampBetween is NOT called because the duration check is not reached on first hit.
        assertThatCode(() -> alertMonitoringService.monitorSystemMetrics())
                .doesNotThrowAnyException();

        // persist should not have been called — no alert created yet
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    @Test
    @DisplayName("monitorSystemMetrics - CPU enabled but CPU below threshold => lastCpuHighTime reset to null")
    void monitorSystemMetrics_cpuEnabled_cpuBelowThreshold_resetsTimer() {
        // threshold=100 means cpuUsage (0-100) is never > 100, so always below threshold.
        AlertConfiguration config = buildDisabledConfig();
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(100);  // cpuUsage can't exceed 100
        config.setCpuDurationMinutes(5);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        assertThatCode(() -> alertMonitoringService.monitorSystemMetrics())
                .doesNotThrowAnyException();

        // No alert should be created since CPU is not above threshold
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    @Test
    @DisplayName("monitorSystemMetrics - CPU enabled, recent CPU alert exists => no new alert created")
    void monitorSystemMetrics_cpuEnabled_recentCpuAlertExists_noNewAlert() {
        // Set threshold=0, durationMinutes=0 to try to trigger alert creation.
        // We simulate "duration elapsed" by calling twice, but for a unit test we mock
        // the findByTimestampBetween to return an existing unacknowledged CPU alert.
        // This covers the hasRecentCpuAlert() -> true branch.
        AlertConfiguration config = buildDisabledConfig();
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(0);
        config.setCpuDurationMinutes(0);     // 0 minutes => duration elapsed immediately on 2nd call
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        SystemAlert recentCpuAlert = buildCpuAlert();
        // Return a recent CPU alert so hasRecentCpuAlert() returns true
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(recentCpuAlert));

        // First call sets lastCpuHighTime
        alertMonitoringService.monitorSystemMetrics();
        // Second call: duration elapsed (0 min), hasRecentCpuAlert() → true → no persist
        alertMonitoringService.monitorSystemMetrics();

        // persist should never be called because hasRecentCpuAlert() returns true
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    @Test
    @DisplayName("monitorSystemMetrics - CPU enabled, duration elapsed, no recent CPU alert => alert is created")
    void monitorSystemMetrics_cpuEnabled_durationElapsed_noRecentAlert_createsCpuAlert() {
        // Reset state: threshold=100 forces CPU (0.0 on Windows) to be below threshold → lastCpuHighTime = null
        AlertConfiguration resetConfig = buildDisabledConfig();
        resetConfig.setCpuHighAlertEnabled(true);
        resetConfig.setCpuThresholdPercent(100);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
        alertMonitoringService.monitorSystemMetrics();

        // threshold=-1: any CPU (even 0.0 on Windows where loadAvg=-1) satisfies 0.0 > -1
        AlertConfiguration config = buildDisabledConfig();
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(-1); // guaranteed: 0.0 > -1 is always true
        config.setCpuDurationMinutes(0);   // 0 minutes => duration condition always satisfied
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // No recent CPU alerts
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Collections.emptyList());

        // First call sets lastCpuHighTime
        alertMonitoringService.monitorSystemMetrics();
        // Second call: lastCpuHighTime != null, duration >= 0, hasRecentCpuAlert() → false → create alert
        alertMonitoringService.monitorSystemMetrics();

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

    // ==================== Tests: Memory check enabled ====================

    @Test
    @DisplayName("monitorSystemMetrics - memory enabled, recent memory alert exists => no new alert")
    void monitorSystemMetrics_memoryEnabled_recentMemoryAlertExists_noNewAlert() {
        AlertConfiguration config = buildDisabledConfig();
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(0); // any memory > 0 triggers
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        SystemAlert recentMemoryAlert = buildMemoryAlert();
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(recentMemoryAlert));

        alertMonitoringService.monitorSystemMetrics();

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

    @Test
    @DisplayName("monitorSystemMetrics - memory enabled, no recent memory alert => memory alert is created")
    void monitorSystemMetrics_memoryEnabled_noRecentAlert_createsMemoryAlert() {
        AlertConfiguration config = buildDisabledConfig();
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(0); // any memory usage > 0 triggers
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Collections.emptyList());

        alertMonitoringService.monitorSystemMetrics();

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

    @Test
    @DisplayName("monitorSystemMetrics - memory enabled, usage below threshold => no alert")
    void monitorSystemMetrics_memoryEnabled_belowThreshold_noAlert() {
        AlertConfiguration config = buildDisabledConfig();
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(100); // usage is always <= 100%, so never above 100
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        alertMonitoringService.monitorSystemMetrics();

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

    // ==================== Tests: Critical errors check enabled ====================

    @Test
    @DisplayName("monitorSystemMetrics - criticalError enabled, count <= 5 => no alert created")
    void monitorSystemMetrics_criticalErrorEnabled_countBelowThreshold_noAlert() {
        AlertConfiguration config = buildDisabledConfig();
        config.setCriticalErrorAlertEnabled(true);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemLogRepository.countByLevelLast24h("CRITICAL")).thenReturn(3L);

        alertMonitoringService.monitorSystemMetrics();

        verify(systemLogRepository).countByLevelLast24h("CRITICAL");
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    @Test
    @DisplayName("monitorSystemMetrics - criticalError enabled, count > 5, recent alert exists => no new alert")
    void monitorSystemMetrics_criticalErrorEnabled_countAbove5_recentAlertExists_noNewAlert() {
        AlertConfiguration config = buildDisabledConfig();
        config.setCriticalErrorAlertEnabled(true);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemLogRepository.countByLevelLast24h("CRITICAL")).thenReturn(10L);

        SystemAlert recentCriticalAlert = buildCriticalErrorsAlert();
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(recentCriticalAlert));

        alertMonitoringService.monitorSystemMetrics();

        verify(systemLogRepository).countByLevelLast24h("CRITICAL");
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    @Test
    @DisplayName("monitorSystemMetrics - criticalError enabled, count > 5, no recent alert => critical alert created")
    void monitorSystemMetrics_criticalErrorEnabled_countAbove5_noRecentAlert_createsCriticalAlert() {
        AlertConfiguration config = buildDisabledConfig();
        config.setCriticalErrorAlertEnabled(true);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemLogRepository.countByLevelLast24h("CRITICAL")).thenReturn(8L);
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Collections.emptyList());

        alertMonitoringService.monitorSystemMetrics();

        verify(systemLogRepository).countByLevelLast24h("CRITICAL");
        verify(systemAlertRepository, times(1)).persist(any(SystemAlert.class));
    }

    @Test
    @DisplayName("monitorSystemMetrics - criticalError enabled, count exactly 5 => no alert (boundary)")
    void monitorSystemMetrics_criticalErrorEnabled_countExactly5_noAlert() {
        AlertConfiguration config = buildDisabledConfig();
        config.setCriticalErrorAlertEnabled(true);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemLogRepository.countByLevelLast24h("CRITICAL")).thenReturn(5L);

        alertMonitoringService.monitorSystemMetrics();

        // count > 5 is required, count == 5 does NOT trigger
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    // ==================== Tests: All checks enabled simultaneously ====================

    @Test
    @DisplayName("monitorSystemMetrics - all checks enabled, memory triggers alert, criticalErrors triggers alert")
    void monitorSystemMetrics_allChecksEnabled_memoryAndCriticalAlerts() {
        AlertConfiguration config = new AlertConfiguration();
        config.setId(UUID.randomUUID());
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(100);   // CPU never triggers (any value <= 100)
        config.setCpuDurationMinutes(60);
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(0);  // memory always triggers
        config.setCriticalErrorAlertEnabled(true);
        config.setErrorAlertEnabled(true);
        config.setConnectionFailureAlertEnabled(true);
        config.setConnectionFailureThreshold(100);
        config.setConnectionFailureWindowMinutes(5);
        config.setEmailNotificationsEnabled(true);
        config.setPushNotificationsEnabled(false);
        config.setSmsNotificationsEnabled(false);
        config.setAlertEmailRecipients("admin@unionflow.test");
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // Memory: no recent alert => will create one
        // Critical errors: count = 7 > 5, no recent alert => will create one
        when(systemLogRepository.countByLevelLast24h("CRITICAL")).thenReturn(7L);
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Collections.emptyList());

        alertMonitoringService.monitorSystemMetrics();

        // Memory alert + Critical errors alert = 2 persists
        verify(systemAlertRepository, times(2)).persist(any(SystemAlert.class));
        verify(systemLogRepository).countByLevelLast24h("CRITICAL");
    }

    @Test
    @DisplayName("monitorSystemMetrics - all checks enabled with recent alerts present => no new persists")
    void monitorSystemMetrics_allChecksEnabled_allRecentAlertsPresent_noNewAlerts() {
        AlertConfiguration config = new AlertConfiguration();
        config.setId(UUID.randomUUID());
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(0);
        config.setCpuDurationMinutes(0);
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(0);
        config.setCriticalErrorAlertEnabled(true);
        config.setErrorAlertEnabled(true);
        config.setConnectionFailureAlertEnabled(true);
        config.setConnectionFailureThreshold(100);
        config.setConnectionFailureWindowMinutes(5);
        config.setEmailNotificationsEnabled(true);
        config.setPushNotificationsEnabled(true);
        config.setSmsNotificationsEnabled(true);
        config.setAlertEmailRecipients("admin@unionflow.test");
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemLogRepository.countByLevelLast24h("CRITICAL")).thenReturn(10L);

        // Return alerts for CPU, MEMORY, and System/ERROR source so all hasRecent* return true
        SystemAlert cpuAlert = buildCpuAlert();
        SystemAlert memAlert = buildMemoryAlert();
        SystemAlert critAlert = buildCriticalErrorsAlert();
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(cpuAlert, memAlert, critAlert));

        // First call to set lastCpuHighTime
        alertMonitoringService.monitorSystemMetrics();
        // Second call: duration >= 0 => hasRecentCpuAlert returns true => no CPU alert
        alertMonitoringService.monitorSystemMetrics();

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

    // ==================== Tests: Exception handling ====================

    @Test
    @DisplayName("monitorSystemMetrics - repository throws exception => exception is caught and logged, no propagation")
    void monitorSystemMetrics_repositoryThrowsException_isCaught() {
        when(alertConfigurationRepository.getConfiguration())
                .thenThrow(new RuntimeException("Database unavailable"));

        // The @Scheduled method catches all exceptions internally — must not propagate
        assertThatCode(() -> alertMonitoringService.monitorSystemMetrics())
                .doesNotThrowAnyException();
    }

    @Test
    @DisplayName("monitorSystemMetrics - systemLogRepository throws => exception is caught inside checkCriticalErrors")
    void monitorSystemMetrics_systemLogRepositoryThrows_innerExceptionCaught() {
        AlertConfiguration config = buildDisabledConfig();
        config.setCriticalErrorAlertEnabled(true);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
        when(systemLogRepository.countByLevelLast24h("CRITICAL"))
                .thenThrow(new RuntimeException("Query failed"));

        assertThatCode(() -> alertMonitoringService.monitorSystemMetrics())
                .doesNotThrowAnyException();

        verify(systemLogRepository).countByLevelLast24h("CRITICAL");
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }

    @Test
    @DisplayName("monitorSystemMetrics - systemAlertRepository.findByTimestampBetween throws => inner exception caught")
    void monitorSystemMetrics_findByTimestampBetweenThrows_innerExceptionCaught() {
        AlertConfiguration config = buildDisabledConfig();
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(0);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenThrow(new RuntimeException("Timestamp query failed"));

        assertThatCode(() -> alertMonitoringService.monitorSystemMetrics())
                .doesNotThrowAnyException();

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

    // ==================== Tests: hasRecent* branches ====================

    @Test
    @DisplayName("hasRecentCpuAlert - returns false when list is empty => CPU alert created after duration elapses")
    void monitorSystemMetrics_hasRecentCpuAlert_emptyList_alertCreated() {
        // Reset state first
        AlertConfiguration resetConfig = buildDisabledConfig();
        resetConfig.setCpuHighAlertEnabled(true);
        resetConfig.setCpuThresholdPercent(100);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
        alertMonitoringService.monitorSystemMetrics();

        // threshold=-1: 0.0 > -1 is always true (even on Windows with cpuUsage=0.0)
        AlertConfiguration config = buildDisabledConfig();
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(-1);
        config.setCpuDurationMinutes(0);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Collections.emptyList());

        // Call 1: sets lastCpuHighTime
        alertMonitoringService.monitorSystemMetrics();
        // Call 2: duration 0 elapsed, no recent CPU alert => persist
        alertMonitoringService.monitorSystemMetrics();

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

    @Test
    @DisplayName("hasRecentMemoryAlert - acknowledged alert present does not block new alert creation")
    void monitorSystemMetrics_hasRecentMemoryAlert_acknowledgedAlert_doesNotBlock() {
        AlertConfiguration config = buildDisabledConfig();
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(0);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // Acknowledged memory alert => hasRecentMemoryAlert() returns false (acknowledged=true is excluded)
        SystemAlert acknowledgedMemAlert = buildMemoryAlert();
        acknowledgedMemAlert.setAcknowledged(true);  // acknowledged=true is NOT counted as recent active
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(acknowledgedMemAlert));

        alertMonitoringService.monitorSystemMetrics();

        // Since the only alert is acknowledged, hasRecentMemoryAlert() = false => new alert created
        verify(systemAlertRepository, times(1)).persist(any(SystemAlert.class));
    }

    @Test
    @DisplayName("hasRecentCriticalErrorsAlert - acknowledged critical alert does not block new alert")
    void monitorSystemMetrics_hasRecentCriticalErrorsAlert_acknowledgedAlert_doesNotBlock() {
        AlertConfiguration config = buildDisabledConfig();
        config.setCriticalErrorAlertEnabled(true);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemLogRepository.countByLevelLast24h("CRITICAL")).thenReturn(10L);

        // Alert is from System/ERROR but acknowledged=true => hasRecentCriticalErrorsAlert() = false
        SystemAlert acknowledgedAlert = buildCriticalErrorsAlert();
        acknowledgedAlert.setAcknowledged(true);
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(acknowledgedAlert));

        alertMonitoringService.monitorSystemMetrics();

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

    @Test
    @DisplayName("hasRecentCpuAlert - CPU alert from different source does not block creation")
    void monitorSystemMetrics_hasRecentCpuAlert_differentSource_doesNotBlock() {
        // Reset state first
        AlertConfiguration resetConfig = buildDisabledConfig();
        resetConfig.setCpuHighAlertEnabled(true);
        resetConfig.setCpuThresholdPercent(100);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
        alertMonitoringService.monitorSystemMetrics();

        // threshold=-1: 0.0 > -1 is always true (even on Windows with cpuUsage=0.0)
        AlertConfiguration config = buildDisabledConfig();
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(-1);
        config.setCpuDurationMinutes(0);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // An alert from MEMORY source (not CPU) — hasRecentCpuAlert() should return false
        SystemAlert memAlert = buildMemoryAlert(); // source="MEMORY", not "CPU"
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(memAlert));

        // First call: sets lastCpuHighTime
        alertMonitoringService.monitorSystemMetrics();
        // Second call: duration=0 elapsed, hasRecentCpuAlert() = false (no CPU source) => creates alert
        alertMonitoringService.monitorSystemMetrics();

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

    @Test
    @DisplayName("hasRecentCriticalErrorsAlert - wrong alertType does not count as critical error alert")
    void monitorSystemMetrics_hasRecentCriticalErrorsAlert_wrongAlertType_doesNotBlock() {
        AlertConfiguration config = buildDisabledConfig();
        config.setCriticalErrorAlertEnabled(true);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        when(systemLogRepository.countByLevelLast24h("CRITICAL")).thenReturn(9L);

        // Alert from "System" but alertType="THRESHOLD" (not "ERROR") => hasRecentCriticalErrorsAlert() = false
        SystemAlert wrongTypeAlert = new SystemAlert();
        wrongTypeAlert.setId(UUID.randomUUID());
        wrongTypeAlert.setSource("System");
        wrongTypeAlert.setAlertType("THRESHOLD"); // not "ERROR"
        wrongTypeAlert.setAcknowledged(false);
        wrongTypeAlert.setTimestamp(LocalDateTime.now().minusMinutes(10));

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

        alertMonitoringService.monitorSystemMetrics();

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

    // =========================================================================
    // lambda$hasRecentCpuAlert$0 — branch: source="CPU" but acknowledged=true
    // Covers: "CPU".equals(source) = true AND !acknowledged = false → no match
    // =========================================================================

    @Test
    @DisplayName("hasRecentCpuAlert - CPU alert is acknowledged => does NOT block alert creation (acknowledged branch)")
    void monitorSystemMetrics_hasRecentCpuAlert_acknowledgedCpuAlert_doesNotBlock() {
        // Reset lastCpuHighTime with threshold=100 (CPU always below threshold → null)
        AlertConfiguration resetConfig = buildDisabledConfig();
        resetConfig.setCpuHighAlertEnabled(true);
        resetConfig.setCpuThresholdPercent(100);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
        alertMonitoringService.monitorSystemMetrics();

        // Now use threshold=-1 so 0.0 > -1 is always true, durationMinutes=0
        AlertConfiguration config = buildDisabledConfig();
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(-1);
        config.setCpuDurationMinutes(0);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // A CPU alert that IS acknowledged → source="CPU" true, !acknowledged false
        // → lambda returns false → anyMatch returns false → hasRecentCpuAlert() = false → creates alert
        SystemAlert acknowledgedCpuAlert = buildCpuAlert();
        acknowledgedCpuAlert.setAcknowledged(true); // source="CPU" AND acknowledged=true
        when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(List.of(acknowledgedCpuAlert));

        // First call: sets lastCpuHighTime
        alertMonitoringService.monitorSystemMetrics();
        // Second call: duration=0 elapsed, hasRecentCpuAlert() = false (acknowledged) → create alert
        alertMonitoringService.monitorSystemMetrics();

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

    @Test
    @DisplayName("checkMemoryThreshold - memory below threshold (maxMemory = 0) sets usage = 0 => no alert")
    void monitorSystemMetrics_memoryEnabled_maxMemoryZero_noAlert() {
        // When maxMemory = 0 → memoryUsage = 0.0 → 0.0 > threshold only if threshold < 0
        AlertConfiguration config = buildDisabledConfig();
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(10); // any positive threshold → usage 0 or small → below threshold
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // JVM maxMemory is always > 0 in real environment, so usage > 0.
        // With threshold=10, we need usage <= 10 to avoid alert.
        // This test verifies the threshold branch where usage is not > threshold.
        // In a fresh test JVM, memory usage may be > 10% so this is environment-dependent.
        // Use threshold=100 to guarantee no alert.
        config.setMemoryThresholdPercent(100);

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

    // ==================== Tests supplémentaires: checkCpuThreshold — branches manquantes ====================

    @Test
    @DisplayName("checkCpuThreshold - lastCpuHighTime non-null mais durée pas encore écoulée => pas d'alerte (branche else de minutesSinceHigh < durationMinutes)")
    void monitorSystemMetrics_cpuEnabled_lastCpuHighTimeNonNull_durationPasEncoreEcoulee_pasAlerte() {
        // Reset lastCpuHighTime via un appel avec threshold=100 (toujours sous le seuil)
        AlertConfiguration resetConfig = buildDisabledConfig();
        resetConfig.setCpuHighAlertEnabled(true);
        resetConfig.setCpuThresholdPercent(100);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
        alertMonitoringService.monitorSystemMetrics();

        // Maintenant threshold=-1 (0.0 > -1 toujours vrai) et durationMinutes=1000 (très long)
        // → 1er appel: lastCpuHighTime était null → le mettre à now()
        // → 2ème appel: lastCpuHighTime != null, mais duration (0 minutes écoulées) < 1000 → branche else implicite (pas d'alerte)
        AlertConfiguration config = buildDisabledConfig();
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(-1);
        config.setCpuDurationMinutes(1000); // durée suffisamment longue pour ne jamais être atteinte en test
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);

        // 1er appel: lastCpuHighTime null → le setter
        alertMonitoringService.monitorSystemMetrics();
        // 2ème appel: lastCpuHighTime != null, 0 min écoulées < 1000 min → pas d'alerte (branche else du if minutesSinceHigh >= durationMinutes)
        alertMonitoringService.monitorSystemMetrics();

        // Aucune alerte ne doit être créée car la durée minimale n'est pas atteinte
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
        // findByTimestampBetween ne doit pas être appelé non plus (condition de durée non atteinte)
        verify(systemAlertRepository, never()).findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class));
    }

    @Test
    @DisplayName("checkCpuThreshold - CPU passe au-dessus puis en-dessous du seuil => lastCpuHighTime reset à null puis recalculé")
    void monitorSystemMetrics_cpuEnabled_cpuAboveThenBelow_lastCpuHighTimeResetEtRecalcule() {
        // Phase 1: CPU > threshold → set lastCpuHighTime (threshold=-1)
        AlertConfiguration configAbove = buildDisabledConfig();
        configAbove.setCpuHighAlertEnabled(true);
        configAbove.setCpuThresholdPercent(-1);    // 0.0 > -1 toujours vrai
        configAbove.setCpuDurationMinutes(1000);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(configAbove);
        alertMonitoringService.monitorSystemMetrics(); // lastCpuHighTime = now()

        // Phase 2: CPU <= threshold → reset lastCpuHighTime = null (threshold=100)
        AlertConfiguration configBelow = buildDisabledConfig();
        configBelow.setCpuHighAlertEnabled(true);
        configBelow.setCpuThresholdPercent(100);   // 0.0 <= 100 toujours → else branch
        configBelow.setCpuDurationMinutes(1000);
        when(alertConfigurationRepository.getConfiguration()).thenReturn(configBelow);
        alertMonitoringService.monitorSystemMetrics(); // lastCpuHighTime = null

        // Phase 3: CPU > threshold à nouveau → lastCpuHighTime est null → le resetter
        when(alertConfigurationRepository.getConfiguration()).thenReturn(configAbove);
        alertMonitoringService.monitorSystemMetrics(); // lastCpuHighTime = now()

        // Aucune alerte créée durant tout ce cycle
        verify(systemAlertRepository, never()).persist(any(SystemAlert.class));
    }
}
