package com.saas.admin.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.saas.admin.dto.response.AuditLogResponse;
import com.saas.admin.service.AuditLogService;
import com.saas.shared.dto.common.PageResponse;
import com.saas.shared.security.JwtTokenProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Integration Tests for AdminAuditLogController
 * 
 * Tests HTTP endpoints with MockMvc.
 * Verifies:
 * - Correct HTTP status codes
 * - JSON serialization
 * - Query parameter handling
 * - Authentication/Authorization
 */
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("AdminAuditLogController Integration Tests")
class AdminAuditLogControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private AuditLogService auditLogService;

    @MockBean
    private JwtTokenProvider jwtTokenProvider;

    private AuditLogResponse testAuditLogResponse;

    @BeforeEach
    void setUp() {
        testAuditLogResponse = AuditLogResponse.builder()
            .id(1L)
            .userId(100L)
            .userEmail("admin@example.com")
            .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 return 401 Unauthorized when not authenticated")
    void testGetAuditLogsUnauthorized() throws Exception {
        mockMvc.perform(get("/api/admin/audit-logs"))
            .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(roles = "USER")
    @DisplayName("Should return 403 Forbidden when user lacks SYSTEM_ADMIN role")
    void testGetAuditLogsForbidden() throws Exception {
        mockMvc.perform(get("/api/admin/audit-logs"))
            .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should return paginated audit logs")
    void testGetAuditLogsSuccess() throws Exception {
        // Arrange
        List<AuditLogResponse> content = List.of(testAuditLogResponse);
        PageResponse<AuditLogResponse> pageResponse = new PageResponse<>(content, 0, 20, 1, 1);

        when(auditLogService.getAuditLogs(
            eq(0), eq(20), isNull(), isNull(), isNull(), isNull(), isNull(), isNull()
        )).thenReturn(pageResponse);

        // Act & Assert
        mockMvc.perform(get("/api/admin/audit-logs")
                .param("page", "0")
                .param("size", "20")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.success").value(true))
            .andExpect(jsonPath("$.data.content").isArray())
            .andExpect(jsonPath("$.data.content[0].id").value(1L))
            .andExpect(jsonPath("$.data.content[0].action").value("CREATE"))
            .andExpect(jsonPath("$.data.pageNumber").value(0))
            .andExpect(jsonPath("$.data.pageSize").value(20));
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should filter audit logs by action")
    void testGetAuditLogsFilterByAction() throws Exception {
        // Arrange
        List<AuditLogResponse> content = List.of(testAuditLogResponse);
        PageResponse<AuditLogResponse> pageResponse = new PageResponse<>(content, 0, 20, 1, 1);

        when(auditLogService.getAuditLogs(
            eq(0), eq(20), eq("CREATE"), isNull(), isNull(), isNull(), isNull(), isNull()
        )).thenReturn(pageResponse);

        // Act & Assert
        mockMvc.perform(get("/api/admin/audit-logs")
                .param("page", "0")
                .param("size", "20")
                .param("action", "CREATE")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.data.content[0].action").value("CREATE"));
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should filter audit logs by entity type")
    void testGetAuditLogsFilterByEntityType() throws Exception {
        // Arrange
        List<AuditLogResponse> content = List.of(testAuditLogResponse);
        PageResponse<AuditLogResponse> pageResponse = new PageResponse<>(content, 0, 20, 1, 1);

        when(auditLogService.getAuditLogs(
            eq(0), eq(20), isNull(), eq("User"), isNull(), isNull(), isNull(), isNull()
        )).thenReturn(pageResponse);

        // Act & Assert
        mockMvc.perform(get("/api/admin/audit-logs")
                .param("page", "0")
                .param("size", "20")
                .param("entityType", "User")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.data.content[0].entityType").value("User"));
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should get single audit log by ID")
    void testGetAuditLogById() throws Exception {
        // Arrange
        when(auditLogService.getAuditLogById(1L))
            .thenReturn(testAuditLogResponse);

        // Act & Assert
        mockMvc.perform(get("/api/admin/audit-logs/1")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.success").value(true))
            .andExpect(jsonPath("$.data.id").value(1L))
            .andExpect(jsonPath("$.data.action").value("CREATE"));
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should get audit logs statistics")
    void testGetAuditLogStatistics() throws Exception {
        // Arrange
        Map<String, Object> stats = new HashMap<>();
        stats.put("actionCounts", Map.of("CREATE", 150L, "UPDATE", 85L));
        stats.put("entityTypeCounts", Map.of("User", 100L, "Tenant", 80L));
        stats.put("tenantCounts", Map.of("tenant_acme", 120L));
        stats.put("logsLast24h", 42L);
        stats.put("totalLogs", 227L);

        when(auditLogService.getAuditLogStatistics()).thenReturn(stats);

        // Act & Assert
        mockMvc.perform(get("/api/admin/audit-logs/stats/summary")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.success").value(true))
            .andExpect(jsonPath("$.data.logsLast24h").value(42L))
            .andExpect(jsonPath("$.data.totalLogs").value(227L))
            .andExpect(jsonPath("$.data.actionCounts").isMap())
            .andExpect(jsonPath("$.data.entityTypeCounts").isMap());
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should get tenant audit logs")
    void testGetTenantAuditLogs() throws Exception {
        // Arrange
        List<AuditLogResponse> content = List.of(testAuditLogResponse);
        PageResponse<AuditLogResponse> pageResponse = new PageResponse<>(content, 0, 20, 1, 1);

        when(auditLogService.getTenantAuditLogs("tenant_acme", 0, 20))
            .thenReturn(pageResponse);

        // Act & Assert
        mockMvc.perform(get("/api/admin/audit-logs/tenant/tenant_acme")
                .param("page", "0")
                .param("size", "20")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.data.content[0].tenantId").value("tenant_acme"));
    }

    @Test
    @WithMockUser(roles = "SYSTEM_ADMIN")
    @DisplayName("Should get user audit logs")
    void testGetUserAuditLogs() throws Exception {
        // Arrange
        List<AuditLogResponse> content = List.of(testAuditLogResponse);
        PageResponse<AuditLogResponse> pageResponse = new PageResponse<>(content, 0, 20, 1, 1);

        when(auditLogService.getUserAuditLogs(100L, 0, 20))
            .thenReturn(pageResponse);

        // Act & Assert
        mockMvc.perform(get("/api/admin/audit-logs/user/100")
                .param("page", "0")
                .param("size", "20")
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.data.content[0].userId").value(100L));
    }
}
