package com.saas.admin.service.impl;

import com.saas.admin.dto.response.AuditLogResponse;
import com.saas.admin.repository.AuditLogRepository;
import com.saas.shared.audit.entity.AuditLog;
import com.saas.shared.dto.common.PageResponse;
import com.saas.shared.dto.mapper.AuditLogMapper;
import com.saas.shared.exception.BusinessException;
import com.saas.shared.exception.ErrorCode;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * Unit Tests for AuditLogServiceImpl
 * 
 * Tests business logic in isolation using mocked repositories.
 * Verifies:
 * - Correct filtering and pagination
 * - Proper DTO mapping
 * - Statistics aggregation
 * - Error handling
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("AuditLogService Unit Tests")
class AuditLogServiceImplTest {

    @Mock
    private AuditLogRepository auditLogRepository;

    @Mock
    private AuditLogMapper auditLogMapper;

    @InjectMocks
    private AuditLogServiceImpl auditLogService;

    private AuditLog testAuditLog;
    private AuditLogResponse testResponse;

    @BeforeEach
    void setUp() {
        testAuditLog = AuditLog.builder()
            .id(1L)
            .userId(100L)
            .action("CREATE")
            .entityType("User")
            .entityId(5L)
            .tenantId("tenant_acme")
            .createdAt(LocalDateTime.now())
            .ipAddress("127.0.0.1")
            .userAgent("Mozilla/5.0")
            .build();

        testResponse = AuditLogResponse.builder()
            .id(1L)
            .userId(100L)
            .action("CREATE")
            .entityType("User")
            .entityId(5L)
            .tenantId("tenant_acme")
            .createdAt(LocalDateTime.now())
            .ipAddress("127.0.0.1")
            .userAgent("Mozilla/5.0")
            .build();
    }

    @Test
    @DisplayName("Should get paginated audit logs with filters")
    void testGetAuditLogsWithFilters() {
        // Arrange
        int page = 0;
        int size = 20;
        String action = "CREATE";
        String entityType = "User";
        Long userId = 100L;
        String tenantId = "tenant_acme";
        LocalDate startDate = LocalDate.now().minusDays(7);
        LocalDate endDate = LocalDate.now();

        List<AuditLog> auditLogs = List.of(testAuditLog);
        Page<AuditLog> mockPage = new PageImpl<>(auditLogs, PageRequest.of(page, size), 1);

        when(auditLogRepository.findAll(any(Specification.class), any(Pageable.class)))
            .thenReturn(mockPage);
        when(auditLogMapper.toResponse(testAuditLog))
            .thenReturn(testResponse);

        // Act
        PageResponse<AuditLogResponse> result = auditLogService.getAuditLogs(
            page, size, action, entityType, userId, tenantId, startDate, endDate
        );

        // Assert
        assertNotNull(result);
        assertEquals(1, result.getContent().size());
        assertEquals(testResponse, result.getContent().get(0));
        assertEquals(page, result.getPageNumber());
        assertEquals(size, result.getPageSize());
        assertEquals(1, result.getTotalElements());
        assertEquals(1, result.getTotalPages());

        verify(auditLogRepository, times(1)).findAll(any(Specification.class), any(Pageable.class));
        verify(auditLogMapper, times(1)).toResponse(testAuditLog);
    }

    @Test
    @DisplayName("Should get audit log by ID")
    void testGetAuditLogById() {
        // Arrange
        long logId = 1L;
        when(auditLogRepository.findById(logId))
            .thenReturn(Optional.of(testAuditLog));
        when(auditLogMapper.toResponse(testAuditLog))
            .thenReturn(testResponse);

        // Act
        AuditLogResponse result = auditLogService.getAuditLogById(logId);

        // Assert
        assertNotNull(result);
        assertEquals(testResponse, result);
        verify(auditLogRepository, times(1)).findById(logId);
        verify(auditLogMapper, times(1)).toResponse(testAuditLog);
    }

    @Test
    @DisplayName("Should throw BusinessException when audit log not found")
    void testGetAuditLogByIdNotFound() {
        // Arrange
        long logId = 999L;
        when(auditLogRepository.findById(logId))
            .thenReturn(Optional.empty());

        // Act & Assert
        assertThrows(BusinessException.class, () -> auditLogService.getAuditLogById(logId));
        verify(auditLogRepository, times(1)).findById(logId);
    }

    @Test
    @DisplayName("Should get tenant audit logs with pagination")
    void testGetTenantAuditLogs() {
        // Arrange
        String tenantId = "tenant_acme";
        int page = 0;
        int size = 20;

        List<AuditLog> auditLogs = List.of(testAuditLog);
        Page<AuditLog> mockPage = new PageImpl<>(auditLogs, PageRequest.of(page, size), 1);

        when(auditLogRepository.findByTenantId(tenantId, PageRequest.of(page, size)))
            .thenReturn(mockPage);
        when(auditLogMapper.toResponse(testAuditLog))
            .thenReturn(testResponse);

        // Act
        PageResponse<AuditLogResponse> result = auditLogService.getTenantAuditLogs(tenantId, page, size);

        // Assert
        assertNotNull(result);
        assertEquals(1, result.getContent().size());
        assertEquals(testResponse, result.getContent().get(0));
    }

    @Test
    @DisplayName("Should get user audit logs with pagination")
    void testGetUserAuditLogs() {
        // Arrange
        Long userId = 100L;
        int page = 0;
        int size = 20;

        List<AuditLog> auditLogs = List.of(testAuditLog);
        Page<AuditLog> mockPage = new PageImpl<>(auditLogs, PageRequest.of(page, size), 1);

        when(auditLogRepository.findByUserId(userId, PageRequest.of(page, size)))
            .thenReturn(mockPage);
        when(auditLogMapper.toResponse(testAuditLog))
            .thenReturn(testResponse);

        // Act
        PageResponse<AuditLogResponse> result = auditLogService.getUserAuditLogs(userId, page, size);

        // Assert
        assertNotNull(result);
        assertEquals(1, result.getContent().size());
        assertEquals(testResponse, result.getContent().get(0));
    }

    @Test
    @DisplayName("Should compute audit log statistics")
    void testGetAuditLogStatistics() {
        // Arrange
        List<Object[]> actionCounts = Arrays.asList(
            new Object[]{"CREATE", 150L},
            new Object[]{"UPDATE", 85L},
            new Object[]{"DELETE", 12L}
        );

        List<Object[]> entityTypeCounts = Arrays.asList(
            new Object[]{"User", 100L},
            new Object[]{"Tenant", 80L},
            new Object[]{"Role", 47L}
        );

        List<Object[]> tenantCounts = Arrays.asList(
            new Object[]{"tenant_acme", 120L},
            new Object[]{"tenant_techco", 85L}
        );

        List<Object[]> userCounts = Arrays.asList(
            new Object[]{1L, 50L},
            new Object[]{2L, 35L}
        );

        when(auditLogRepository.countByActionGrouped()).thenReturn(actionCounts);
        when(auditLogRepository.countByEntityTypeGrouped()).thenReturn(entityTypeCounts);
        when(auditLogRepository.countByTenantGrouped()).thenReturn(tenantCounts);
        when(auditLogRepository.countByUserGrouped()).thenReturn(userCounts);
        when(auditLogRepository.countByCreatedAtAfter(any(LocalDateTime.class)))
            .thenReturn(42L);
        when(auditLogRepository.count()).thenReturn(227L);

        // Act
        Map<String, Object> stats = auditLogService.getAuditLogStatistics();

        // Assert
        assertNotNull(stats);
        assertNotNull(stats.get("actionCounts"));
        assertNotNull(stats.get("entityTypeCounts"));
        assertNotNull(stats.get("tenantCounts"));
        assertNotNull(stats.get("userCounts"));
        assertEquals(42L, stats.get("logsLast24h"));
        assertEquals(227L, stats.get("totalLogs"));

        @SuppressWarnings("unchecked")
        Map<String, Long> actions = (Map<String, Long>) stats.get("actionCounts");
        assertEquals(3, actions.size());
        assertEquals(150L, actions.get("CREATE"));

        verify(auditLogRepository, times(1)).countByActionGrouped();
        verify(auditLogRepository, times(1)).countByEntityTypeGrouped();
        verify(auditLogRepository, times(1)).countByTenantGrouped();
        verify(auditLogRepository, times(1)).countByUserGrouped();
        verify(auditLogRepository, times(1)).countByCreatedAtAfter(any(LocalDateTime.class));
        verify(auditLogRepository, times(1)).count();
    }

    @Test
    @DisplayName("Should handle empty audit logs list")
    void testGetAuditLogsEmptyResult() {
        // Arrange
        Page<AuditLog> emptyPage = new PageImpl<>(Collections.emptyList(), PageRequest.of(0, 20), 0);
        when(auditLogRepository.findAll(any(Specification.class), any(Pageable.class)))
            .thenReturn(emptyPage);

        // Act
        PageResponse<AuditLogResponse> result = auditLogService.getAuditLogs(
            0, 20, null, null, null, null, null, null
        );

        // Assert
        assertNotNull(result);
        assertTrue(result.getContent().isEmpty());
        assertEquals(0, result.getTotalElements());
        assertEquals(0, result.getTotalPages());
    }
}
