package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.logs.request.LogSearchRequest;
import dev.lions.unionflow.server.api.dto.logs.request.UpdateAlertConfigRequest;
import dev.lions.unionflow.server.api.dto.logs.response.AlertConfigResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemAlertResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemLogResponse;
import dev.lions.unionflow.server.api.dto.logs.response.SystemMetricsResponse;
import dev.lions.unionflow.server.entity.AlertConfiguration;
import dev.lions.unionflow.server.entity.SystemAlert;
import dev.lions.unionflow.server.entity.SystemLog;
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 LogsMonitoringService.
 * Utilise @InjectMock sur les repositories pour isoler le service.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-20
 */
@QuarkusTest
class LogsMonitoringServiceTest {

    @Inject
    LogsMonitoringService logsMonitoringService;

    @InjectMock
    SystemLogRepository systemLogRepository;

    @InjectMock
    SystemAlertRepository systemAlertRepository;

    @InjectMock
    AlertConfigurationRepository alertConfigurationRepository;

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

    private SystemLog buildSystemLog() {
        SystemLog log = new SystemLog();
        log.setId(UUID.randomUUID());
        log.setLevel("ERROR");
        log.setSource("API");
        log.setMessage("An error occurred during processing");
        log.setDetails("java.lang.NullPointerException at line 42");
        log.setTimestamp(LocalDateTime.now().minusMinutes(10));
        log.setUserId("user-abc-123");
        log.setIpAddress("192.168.1.100");
        log.setSessionId("session-xyz-789");
        log.setEndpoint("/api/v1/membres");
        log.setHttpStatusCode(500);
        return log;
    }

    private SystemAlert buildSystemAlert() {
        SystemAlert alert = new SystemAlert();
        alert.setId(UUID.randomUUID());
        alert.setLevel("WARNING");
        alert.setTitle("High CPU Usage");
        alert.setMessage("CPU usage exceeds configured threshold");
        alert.setTimestamp(LocalDateTime.now().minusMinutes(5));
        alert.setAcknowledged(false);
        alert.setAcknowledgedBy(null);
        alert.setAcknowledgedAt(null);
        alert.setSource("CPU");
        alert.setAlertType("THRESHOLD");
        alert.setCurrentValue(85.5);
        alert.setThresholdValue(80.0);
        alert.setUnit("%");
        alert.setRecommendedActions("Check running processes and consider restarting services.");
        return alert;
    }

    private AlertConfiguration buildAlertConfiguration() {
        AlertConfiguration config = new AlertConfiguration();
        config.setId(UUID.randomUUID());
        config.setCpuHighAlertEnabled(true);
        config.setCpuThresholdPercent(80);
        config.setCpuDurationMinutes(5);
        config.setMemoryLowAlertEnabled(true);
        config.setMemoryThresholdPercent(85);
        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,ops@unionflow.test");
        return config;
    }

    // ==================== searchLogs ====================

    @Test
    @DisplayName("searchLogs - timeRange null => start null, uses default offset/limit")
    void searchLogs_withNullTimeRange_andNullOffsetAndLimit() {
        // Create request with null timeRange, null offset, null limit
        LogSearchRequest request = new LogSearchRequest();
        request.setLevel("ERROR");
        request.setSource("API");
        request.setSearchQuery(null);
        request.setTimeRange(null);
        request.setOffset(null);
        request.setLimit(null);

        SystemLog logEntity = buildSystemLog();
        when(systemLogRepository.search(
                eq("ERROR"),
                eq("API"),
                isNull(),
                isNull(),
                any(LocalDateTime.class),
                eq(0),   // pageIndex = 0/100 = 0
                eq(100)  // default limit = 100
        )).thenReturn(List.of(logEntity));

        List<SystemLogResponse> result = logsMonitoringService.searchLogs(request);

        assertThat(result).hasSize(1);
        SystemLogResponse resp = result.get(0);
        assertThat(resp.getLevel()).isEqualTo("ERROR");
        assertThat(resp.getSource()).isEqualTo("API");
        assertThat(resp.getMessage()).isEqualTo("An error occurred during processing");
        assertThat(resp.getDetails()).isEqualTo("java.lang.NullPointerException at line 42");
        assertThat(resp.getUserId()).isEqualTo("user-abc-123");
        assertThat(resp.getIpAddress()).isEqualTo("192.168.1.100");
        assertThat(resp.getSessionId()).isEqualTo("session-xyz-789");
        assertThat(resp.getEndpoint()).isEqualTo("/api/v1/membres");
        assertThat(resp.getHttpStatusCode()).isEqualTo(500);
        assertThat(resp.getId()).isEqualTo(logEntity.getId());
    }

    @Test
    @DisplayName("searchLogs - timeRange '1H' computes start = now - 1 hour")
    void searchLogs_withTimeRange1H() {
        LogSearchRequest request = LogSearchRequest.builder()
                .level("WARNING")
                .source("Database")
                .searchQuery("connection")
                .timeRange("1H")
                .offset(0)
                .limit(50)
                .build();

        when(systemLogRepository.search(
                eq("WARNING"),
                eq("Database"),
                eq("connection"),
                any(LocalDateTime.class),
                any(LocalDateTime.class),
                eq(0),
                eq(50)
        )).thenReturn(Collections.emptyList());

        List<SystemLogResponse> result = logsMonitoringService.searchLogs(request);

        assertThat(result).isEmpty();
        verify(systemLogRepository).search(
                eq("WARNING"),
                eq("Database"),
                eq("connection"),
                any(LocalDateTime.class),
                any(LocalDateTime.class),
                eq(0),
                eq(50)
        );
    }

    @Test
    @DisplayName("searchLogs - timeRange '24H' computes start = now - 24 hours")
    void searchLogs_withTimeRange24H() {
        LogSearchRequest request = LogSearchRequest.builder()
                .level("CRITICAL")
                .source(null)
                .searchQuery(null)
                .timeRange("24H")
                .offset(0)
                .limit(100)
                .build();

        when(systemLogRepository.search(
                eq("CRITICAL"),
                isNull(),
                isNull(),
                any(LocalDateTime.class),
                any(LocalDateTime.class),
                anyInt(),
                eq(100)
        )).thenReturn(List.of(buildSystemLog()));

        List<SystemLogResponse> result = logsMonitoringService.searchLogs(request);

        assertThat(result).hasSize(1);
    }

    @Test
    @DisplayName("searchLogs - timeRange '7D' computes start = now - 7 days")
    void searchLogs_withTimeRange7D() {
        LogSearchRequest request = LogSearchRequest.builder()
                .level(null)
                .source(null)
                .searchQuery("timeout")
                .timeRange("7D")
                .offset(0)
                .limit(200)
                .build();

        when(systemLogRepository.search(
                isNull(),
                isNull(),
                eq("timeout"),
                any(LocalDateTime.class),
                any(LocalDateTime.class),
                anyInt(),
                eq(200)
        )).thenReturn(Collections.emptyList());

        List<SystemLogResponse> result = logsMonitoringService.searchLogs(request);

        assertThat(result).isEmpty();
    }

    @Test
    @DisplayName("searchLogs - timeRange '30D' computes start = now - 30 days")
    void searchLogs_withTimeRange30D() {
        LogSearchRequest request = LogSearchRequest.builder()
                .level("INFO")
                .source("System")
                .searchQuery(null)
                .timeRange("30D")
                .offset(0)
                .limit(100)
                .build();

        SystemLog log1 = buildSystemLog();
        log1.setLevel("INFO");
        log1.setSource("System");

        when(systemLogRepository.search(
                eq("INFO"),
                eq("System"),
                isNull(),
                any(LocalDateTime.class),
                any(LocalDateTime.class),
                anyInt(),
                eq(100)
        )).thenReturn(List.of(log1));

        List<SystemLogResponse> result = logsMonitoringService.searchLogs(request);

        assertThat(result).hasSize(1);
        assertThat(result.get(0).getLevel()).isEqualTo("INFO");
        assertThat(result.get(0).getSource()).isEqualTo("System");
    }

    @Test
    @DisplayName("searchLogs - non-null offset and limit compute correct pageIndex")
    void searchLogs_withNonNullOffsetAndLimit() {
        // offset=200, limit=50 => pageIndex = 200/50 = 4
        LogSearchRequest request = LogSearchRequest.builder()
                .level(null)
                .source(null)
                .searchQuery(null)
                .timeRange(null)
                .offset(200)
                .limit(50)
                .build();

        when(systemLogRepository.search(
                isNull(),
                isNull(),
                isNull(),
                isNull(),
                any(LocalDateTime.class),
                eq(4),  // 200/50
                eq(50)
        )).thenReturn(Collections.emptyList());

        List<SystemLogResponse> result = logsMonitoringService.searchLogs(request);

        assertThat(result).isEmpty();
        verify(systemLogRepository).search(
                isNull(), isNull(), isNull(),
                isNull(), any(LocalDateTime.class),
                eq(4), eq(50)
        );
    }

    @Test
    @DisplayName("searchLogs - unrecognized timeRange => start remains null")
    void searchLogs_withUnrecognizedTimeRange() {
        LogSearchRequest request = LogSearchRequest.builder()
                .level(null)
                .source(null)
                .searchQuery(null)
                .timeRange("UNKNOWN")
                .offset(0)
                .limit(100)
                .build();

        when(systemLogRepository.search(
                isNull(), isNull(), isNull(),
                isNull(), any(LocalDateTime.class),
                eq(0), eq(100)
        )).thenReturn(Collections.emptyList());

        List<SystemLogResponse> result = logsMonitoringService.searchLogs(request);

        assertThat(result).isEmpty();
    }

    // ==================== getSystemMetrics ====================

    @Test
    @DisplayName("getSystemMetrics - returns non-null response with all required fields")
    void getSystemMetrics_returnsCompleteResponse() {
        SystemMetricsResponse result = logsMonitoringService.getSystemMetrics();

        assertThat(result).isNotNull();
        assertThat(result.getCpuUsagePercent()).isNotNull().isGreaterThanOrEqualTo(0.0);
        assertThat(result.getMemoryUsagePercent()).isNotNull().isGreaterThanOrEqualTo(0.0);
        assertThat(result.getDiskUsagePercent()).isEqualTo(45.8);
        assertThat(result.getNetworkUsageMbps()).isNotNull().isGreaterThan(0.0);
        assertThat(result.getActiveConnections()).isNotNull().isGreaterThanOrEqualTo(1200);
        assertThat(result.getErrorRate()).isEqualTo(0.02);
        assertThat(result.getAverageResponseTimeMs()).isEqualTo(127.0);
        assertThat(result.getUptime()).isNotNull().isGreaterThanOrEqualTo(0L);
        assertThat(result.getUptimeFormatted()).isNotNull().isNotEmpty();
        assertThat(result.getServices()).isNotNull().containsKeys("api", "database", "keycloak", "cdn");
        assertThat(result.getTotalLogs24h()).isEqualTo(15247L);
        assertThat(result.getTotalErrors24h()).isEqualTo(23L);
        assertThat(result.getTotalWarnings24h()).isEqualTo(156L);
        assertThat(result.getTotalRequests24h()).isEqualTo(45000L);
        assertThat(result.getTimestamp()).isNotNull();
    }

    @Test
    @DisplayName("getSystemMetrics - service statuses have expected properties")
    void getSystemMetrics_serviceStatuses() {
        SystemMetricsResponse result = logsMonitoringService.getSystemMetrics();

        SystemMetricsResponse.ServiceStatus api = result.getServices().get("api");
        assertThat(api.getName()).isEqualTo("API Server");
        assertThat(api.getOnline()).isTrue();
        assertThat(api.getStatus()).isEqualTo("OK");
        assertThat(api.getResponseTimeMs()).isEqualTo(25L);

        SystemMetricsResponse.ServiceStatus cdn = result.getServices().get("cdn");
        assertThat(cdn.getName()).isEqualTo("CDN");
        assertThat(cdn.getOnline()).isFalse();
        assertThat(cdn.getStatus()).isEqualTo("DOWN");
        assertThat(cdn.getResponseTimeMs()).isEqualTo(0L);
    }

    @Test
    @DisplayName("getSystemMetrics - uptime formatted correctly covers formatUptime branches")
    void getSystemMetrics_uptimeFormattedNotEmpty() {
        // Calling getSystemMetrics() will internally call formatUptime(uptimeMs).
        // The system has just started so uptimeMs is small (few milliseconds).
        // This covers the "minutes only" branch. The other branches are also tested by
        // calling the static helper directly on SystemMetricsResponse.
        SystemMetricsResponse result = logsMonitoringService.getSystemMetrics();
        assertThat(result.getUptimeFormatted()).isNotNull();

        // Cover formatUptime with hours (e.g. 2h 15m = 8100000 ms)
        String hours = SystemMetricsResponse.formatUptime(8_100_000L);
        assertThat(hours).contains("h").contains("m");

        // Cover formatUptime with days (e.g. 1j 2h 15m = 94500000 ms)
        String days = SystemMetricsResponse.formatUptime(94_500_000L);
        assertThat(days).contains("j").contains("h").contains("m");

        // Cover formatUptime with minutes only (e.g. 5m = 300000 ms)
        String minutes = SystemMetricsResponse.formatUptime(300_000L);
        assertThat(minutes).contains("m");
    }

    // ==================== getActiveAlerts ====================

    @Test
    @DisplayName("getActiveAlerts - empty list when no active alerts")
    void getActiveAlerts_emptyList() {
        when(systemAlertRepository.findActiveAlerts()).thenReturn(Collections.emptyList());

        List<SystemAlertResponse> result = logsMonitoringService.getActiveAlerts();

        assertThat(result).isEmpty();
        verify(systemAlertRepository).findActiveAlerts();
    }

    @Test
    @DisplayName("getActiveAlerts - maps all fields of SystemAlert to SystemAlertResponse")
    void getActiveAlerts_withPopulatedAlerts() {
        SystemAlert alert = buildSystemAlert();
        UUID alertId = alert.getId();

        when(systemAlertRepository.findActiveAlerts()).thenReturn(List.of(alert));

        List<SystemAlertResponse> result = logsMonitoringService.getActiveAlerts();

        assertThat(result).hasSize(1);
        SystemAlertResponse resp = result.get(0);
        assertThat(resp.getId()).isEqualTo(alertId);
        assertThat(resp.getLevel()).isEqualTo("WARNING");
        assertThat(resp.getTitle()).isEqualTo("High CPU Usage");
        assertThat(resp.getMessage()).isEqualTo("CPU usage exceeds configured threshold");
        assertThat(resp.getTimestamp()).isNotNull();
        assertThat(resp.getAcknowledged()).isFalse();
        assertThat(resp.getAcknowledgedBy()).isNull();
        assertThat(resp.getAcknowledgedAt()).isNull();
        assertThat(resp.getSource()).isEqualTo("CPU");
        assertThat(resp.getAlertType()).isEqualTo("THRESHOLD");
        assertThat(resp.getCurrentValue()).isEqualTo(85.5);
        assertThat(resp.getThresholdValue()).isEqualTo(80.0);
        assertThat(resp.getUnit()).isEqualTo("%");
        assertThat(resp.getRecommendedActions()).isEqualTo("Check running processes and consider restarting services.");
    }

    @Test
    @DisplayName("getActiveAlerts - acknowledged alert with acknowledgedBy and acknowledgedAt is mapped correctly")
    void getActiveAlerts_acknowledgedAlert_isMappedCorrectly() {
        SystemAlert alert = buildSystemAlert();
        alert.setAcknowledged(true);
        alert.setAcknowledgedBy("admin@unionflow.test");
        alert.setAcknowledgedAt(LocalDateTime.now().minusMinutes(2));

        when(systemAlertRepository.findActiveAlerts()).thenReturn(List.of(alert));

        List<SystemAlertResponse> result = logsMonitoringService.getActiveAlerts();

        assertThat(result).hasSize(1);
        SystemAlertResponse resp = result.get(0);
        assertThat(resp.getAcknowledged()).isTrue();
        assertThat(resp.getAcknowledgedBy()).isEqualTo("admin@unionflow.test");
        assertThat(resp.getAcknowledgedAt()).isNotNull();
    }

    // ==================== acknowledgeAlert ====================

    @Test
    @DisplayName("acknowledgeAlert - calls repository with the given alertId and current user")
    void acknowledgeAlert_callsRepository() {
        UUID alertId = UUID.randomUUID();

        logsMonitoringService.acknowledgeAlert(alertId);

        verify(systemAlertRepository).acknowledgeAlert(eq(alertId), eq("admin@unionflow.test"));
    }

    // ==================== getAlertConfig ====================

    @Test
    @DisplayName("getAlertConfig - maps configuration and alert counts correctly")
    void getAlertConfig_returnsCorrectResponse() {
        AlertConfiguration config = buildAlertConfiguration();
        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
        when(systemAlertRepository.countLast24h()).thenReturn(42L);
        when(systemAlertRepository.countActive()).thenReturn(7L);
        when(systemAlertRepository.countAcknowledgedLast24h()).thenReturn(15L);

        AlertConfigResponse result = logsMonitoringService.getAlertConfig();

        assertThat(result).isNotNull();
        assertThat(result.getCpuHighAlertEnabled()).isTrue();
        assertThat(result.getCpuThresholdPercent()).isEqualTo(80);
        assertThat(result.getCpuDurationMinutes()).isEqualTo(5);
        assertThat(result.getMemoryLowAlertEnabled()).isTrue();
        assertThat(result.getMemoryThresholdPercent()).isEqualTo(85);
        assertThat(result.getCriticalErrorAlertEnabled()).isTrue();
        assertThat(result.getErrorAlertEnabled()).isTrue();
        assertThat(result.getConnectionFailureAlertEnabled()).isTrue();
        assertThat(result.getConnectionFailureThreshold()).isEqualTo(100);
        assertThat(result.getConnectionFailureWindowMinutes()).isEqualTo(5);
        assertThat(result.getEmailNotificationsEnabled()).isTrue();
        assertThat(result.getPushNotificationsEnabled()).isFalse();
        assertThat(result.getSmsNotificationsEnabled()).isFalse();
        assertThat(result.getAlertEmailRecipients()).isEqualTo("admin@unionflow.test,ops@unionflow.test");
        assertThat(result.getTotalAlertsLast24h()).isEqualTo(42);
        assertThat(result.getActiveAlerts()).isEqualTo(7);
        assertThat(result.getAcknowledgedAlerts()).isEqualTo(15);
    }

    @Test
    @DisplayName("getAlertConfig - all alerts disabled returns correct response")
    void getAlertConfig_allDisabled() {
        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(false);
        config.setPushNotificationsEnabled(false);
        config.setSmsNotificationsEnabled(false);
        config.setAlertEmailRecipients("admin@unionflow.test");

        when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
        when(systemAlertRepository.countLast24h()).thenReturn(0L);
        when(systemAlertRepository.countActive()).thenReturn(0L);
        when(systemAlertRepository.countAcknowledgedLast24h()).thenReturn(0L);

        AlertConfigResponse result = logsMonitoringService.getAlertConfig();

        assertThat(result.getCpuHighAlertEnabled()).isFalse();
        assertThat(result.getMemoryLowAlertEnabled()).isFalse();
        assertThat(result.getCriticalErrorAlertEnabled()).isFalse();
        assertThat(result.getErrorAlertEnabled()).isFalse();
        assertThat(result.getConnectionFailureAlertEnabled()).isFalse();
        assertThat(result.getTotalAlertsLast24h()).isEqualTo(0);
        assertThat(result.getActiveAlerts()).isEqualTo(0);
        assertThat(result.getAcknowledgedAlerts()).isEqualTo(0);
    }

    // ==================== updateAlertConfig ====================

    @Test
    @DisplayName("updateAlertConfig - maps request, persists, and returns updated configuration")
    void updateAlertConfig_returnsUpdatedResponse() {
        UpdateAlertConfigRequest request = UpdateAlertConfigRequest.builder()
                .cpuHighAlertEnabled(false)
                .cpuThresholdPercent(90)
                .cpuDurationMinutes(10)
                .memoryLowAlertEnabled(true)
                .memoryThresholdPercent(90)
                .criticalErrorAlertEnabled(true)
                .errorAlertEnabled(false)
                .connectionFailureAlertEnabled(true)
                .connectionFailureThreshold(50)
                .connectionFailureWindowMinutes(10)
                .emailNotificationsEnabled(true)
                .pushNotificationsEnabled(true)
                .smsNotificationsEnabled(false)
                .alertEmailRecipients("team@unionflow.test")
                .build();

        AlertConfiguration updated = new AlertConfiguration();
        updated.setId(UUID.randomUUID());
        updated.setCpuHighAlertEnabled(false);
        updated.setCpuThresholdPercent(90);
        updated.setCpuDurationMinutes(10);
        updated.setMemoryLowAlertEnabled(true);
        updated.setMemoryThresholdPercent(90);
        updated.setCriticalErrorAlertEnabled(true);
        updated.setErrorAlertEnabled(false);
        updated.setConnectionFailureAlertEnabled(true);
        updated.setConnectionFailureThreshold(50);
        updated.setConnectionFailureWindowMinutes(10);
        updated.setEmailNotificationsEnabled(true);
        updated.setPushNotificationsEnabled(true);
        updated.setSmsNotificationsEnabled(false);
        updated.setAlertEmailRecipients("team@unionflow.test");

        when(alertConfigurationRepository.updateConfiguration(any(AlertConfiguration.class))).thenReturn(updated);
        when(systemAlertRepository.countLast24h()).thenReturn(10L);
        when(systemAlertRepository.countActive()).thenReturn(3L);
        when(systemAlertRepository.countAcknowledgedLast24h()).thenReturn(7L);

        AlertConfigResponse result = logsMonitoringService.updateAlertConfig(request);

        assertThat(result).isNotNull();
        assertThat(result.getCpuHighAlertEnabled()).isFalse();
        assertThat(result.getCpuThresholdPercent()).isEqualTo(90);
        assertThat(result.getCpuDurationMinutes()).isEqualTo(10);
        assertThat(result.getMemoryLowAlertEnabled()).isTrue();
        assertThat(result.getMemoryThresholdPercent()).isEqualTo(90);
        assertThat(result.getCriticalErrorAlertEnabled()).isTrue();
        assertThat(result.getErrorAlertEnabled()).isFalse();
        assertThat(result.getConnectionFailureAlertEnabled()).isTrue();
        assertThat(result.getConnectionFailureThreshold()).isEqualTo(50);
        assertThat(result.getConnectionFailureWindowMinutes()).isEqualTo(10);
        assertThat(result.getEmailNotificationsEnabled()).isTrue();
        assertThat(result.getPushNotificationsEnabled()).isTrue();
        assertThat(result.getSmsNotificationsEnabled()).isFalse();
        assertThat(result.getAlertEmailRecipients()).isEqualTo("team@unionflow.test");
        assertThat(result.getTotalAlertsLast24h()).isEqualTo(10);
        assertThat(result.getActiveAlerts()).isEqualTo(3);
        assertThat(result.getAcknowledgedAlerts()).isEqualTo(7);

        verify(alertConfigurationRepository).updateConfiguration(any(AlertConfiguration.class));
        verify(systemAlertRepository).countLast24h();
        verify(systemAlertRepository).countActive();
        verify(systemAlertRepository).countAcknowledgedLast24h();
    }

    @Test
    @DisplayName("updateAlertConfig - all notifications enabled in updated config")
    void updateAlertConfig_allNotificationsEnabled() {
        UpdateAlertConfigRequest request = UpdateAlertConfigRequest.builder()
                .cpuHighAlertEnabled(true)
                .cpuThresholdPercent(75)
                .cpuDurationMinutes(3)
                .memoryLowAlertEnabled(true)
                .memoryThresholdPercent(80)
                .criticalErrorAlertEnabled(true)
                .errorAlertEnabled(true)
                .connectionFailureAlertEnabled(true)
                .connectionFailureThreshold(200)
                .connectionFailureWindowMinutes(15)
                .emailNotificationsEnabled(true)
                .pushNotificationsEnabled(true)
                .smsNotificationsEnabled(true)
                .alertEmailRecipients("all@unionflow.test")
                .build();

        AlertConfiguration updated = new AlertConfiguration();
        updated.setId(UUID.randomUUID());
        updated.setCpuHighAlertEnabled(true);
        updated.setCpuThresholdPercent(75);
        updated.setCpuDurationMinutes(3);
        updated.setMemoryLowAlertEnabled(true);
        updated.setMemoryThresholdPercent(80);
        updated.setCriticalErrorAlertEnabled(true);
        updated.setErrorAlertEnabled(true);
        updated.setConnectionFailureAlertEnabled(true);
        updated.setConnectionFailureThreshold(200);
        updated.setConnectionFailureWindowMinutes(15);
        updated.setEmailNotificationsEnabled(true);
        updated.setPushNotificationsEnabled(true);
        updated.setSmsNotificationsEnabled(true);
        updated.setAlertEmailRecipients("all@unionflow.test");

        when(alertConfigurationRepository.updateConfiguration(any(AlertConfiguration.class))).thenReturn(updated);
        when(systemAlertRepository.countLast24h()).thenReturn(5L);
        when(systemAlertRepository.countActive()).thenReturn(1L);
        when(systemAlertRepository.countAcknowledgedLast24h()).thenReturn(4L);

        AlertConfigResponse result = logsMonitoringService.updateAlertConfig(request);

        assertThat(result.getEmailNotificationsEnabled()).isTrue();
        assertThat(result.getPushNotificationsEnabled()).isTrue();
        assertThat(result.getSmsNotificationsEnabled()).isTrue();
        assertThat(result.getAlertEmailRecipients()).isEqualTo("all@unionflow.test");
    }

    // ==================== searchLogs - multiple log results ====================

    // ==================== formatUptime — hours and days branches (via private method reflection) ====================

    @Test
    @DisplayName("formatUptime covers 'hours > 0' branch via private method reflection (2h30m = 9000000ms)")
    void formatUptime_hoursBranch_viaPrivateMethodReflection() throws Exception {
        // The injected proxy is a subclass of LogsMonitoringService, so invoke directly on it
        java.lang.reflect.Method formatUptimeMethod = LogsMonitoringService.class.getDeclaredMethod("formatUptime", long.class);
        formatUptimeMethod.setAccessible(true);

        // 2h 30m = 2*3600*1000 + 30*60*1000 = 9_000_000 ms → days=0, hours=2 → "2h 30m"
        String result = (String) formatUptimeMethod.invoke(logsMonitoringService, 9_000_000L);

        assertThat(result).contains("h").contains("m");
        assertThat(result).doesNotContain("j"); // No days
    }

    @Test
    @DisplayName("formatUptime covers 'days > 0' branch via private method reflection (2j 3h = 194400000ms)")
    void formatUptime_daysBranch_viaPrivateMethodReflection() throws Exception {
        // The injected proxy is a subclass of LogsMonitoringService, so invoke directly on it
        java.lang.reflect.Method formatUptimeMethod = LogsMonitoringService.class.getDeclaredMethod("formatUptime", long.class);
        formatUptimeMethod.setAccessible(true);

        // 2 days 3 hours = (2*24 + 3)*3600*1000 = 194_400_000 ms → days=2 → "2j 3h 0m"
        String result = (String) formatUptimeMethod.invoke(logsMonitoringService, 194_400_000L);

        assertThat(result).contains("j").contains("h");
    }

    @Test
    @DisplayName("searchLogs - multiple logs are all mapped to responses")
    void searchLogs_withMultipleLogs_allMapped() {
        LogSearchRequest request = LogSearchRequest.builder()
                .level(null)
                .source(null)
                .searchQuery(null)
                .timeRange(null)
                .offset(0)
                .limit(100)
                .build();

        SystemLog log1 = buildSystemLog();
        SystemLog log2 = buildSystemLog();
        log2.setId(UUID.randomUUID());
        log2.setLevel("INFO");
        log2.setSource("Keycloak");
        log2.setMessage("User authenticated successfully");
        log2.setDetails(null);
        log2.setUserId("user-def-456");
        log2.setIpAddress("10.0.0.1");
        log2.setSessionId(null);
        log2.setEndpoint("/auth/token");
        log2.setHttpStatusCode(200);

        when(systemLogRepository.search(
                isNull(), isNull(), isNull(),
                isNull(), any(LocalDateTime.class),
                eq(0), eq(100)
        )).thenReturn(List.of(log1, log2));

        List<SystemLogResponse> result = logsMonitoringService.searchLogs(request);

        assertThat(result).hasSize(2);
        assertThat(result.get(0).getLevel()).isEqualTo("ERROR");
        assertThat(result.get(1).getLevel()).isEqualTo("INFO");
        assertThat(result.get(1).getSource()).isEqualTo("Keycloak");
        assertThat(result.get(1).getDetails()).isNull();
        assertThat(result.get(1).getSessionId()).isNull();
    }
}
