package com.mosquito.project.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mosquito.project.dto.CreateApiKeyRequest;
import com.mosquito.project.dto.UseApiKeyRequest;
import com.mosquito.project.permission.ApprovalFlowService;
import com.mosquito.project.service.ActivityService;
import com.mosquito.project.support.TestAuthSupport;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Map;

import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(ApiKeyController.class)
@Import(com.mosquito.project.config.ControllerTestConfig.class)
@TestPropertySource(properties = {
    "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration," +
    "org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration," +
    "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"
})
class ApiKeyControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private ActivityService activityService;

    @MockBean
    private ApprovalFlowService approvalFlowService;

    // ==================== 业务功能测试 ====================

    @Test
    void createApiKey_shouldReturn201WithEnvelope() throws Exception {
        when(activityService.savePendingApiKey(any(CreateApiKeyRequest.class))).thenReturn(101L);
        when(approvalFlowService.submitApprovalByEvent(any(), any(), anyLong(), any(), anyLong(), any(), any()))
            .thenReturn(Map.of("recordId", 1001L));
        CreateApiKeyRequest request = new CreateApiKeyRequest();
        request.setActivityId(1L);
        request.setName("test");

        mockMvc.perform(post("/api/v1/keys")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request))
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.apiKeyId").value(101))
            .andExpect(jsonPath("$.data.recordId").value(1001))
            .andExpect(jsonPath("$.data.status").exists());
    }

    @Test
    void createApiKey_shouldReturnConflict_whenApprovalSubmitFailsAndRollbackFails() throws Exception {
        when(activityService.savePendingApiKey(any(CreateApiKeyRequest.class))).thenReturn(202L);
        when(approvalFlowService.submitApprovalByEvent(any(), any(), anyLong(), any(), anyLong(), any(), any()))
            .thenThrow(new RuntimeException("approval service unavailable"));
        doThrow(new RuntimeException("rollback failed")).when(activityService).revokeApiKey(202L);

        CreateApiKeyRequest request = new CreateApiKeyRequest();
        request.setActivityId(1L);
        request.setName("test-fail");

        mockMvc.perform(post("/api/v1/keys")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request))
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isConflict())
            .andExpect(jsonPath("$.code").value(409))
            .andExpect(jsonPath("$.message").value("审批服务异常，无法执行敏感操作"));

        verify(activityService).revokeApiKey(202L);
    }

    @Test
    void revealApiKey_shouldReturnMessage() throws Exception {
        when(activityService.revealApiKey(1L)).thenReturn("raw-key");

        mockMvc.perform(get("/api/v1/keys/1/reveal")
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.apiKey").value("raw-key"));
    }

    @Test
    void revokeApiKey_shouldReturnOk() throws Exception {
        mockMvc.perform(delete("/api/v1/keys/1")
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200));

        verify(activityService).revokeApiKey(1L);
    }

    @Test
    void useApiKey_shouldReturnOk() throws Exception {
        UseApiKeyRequest request = new UseApiKeyRequest();
        request.setApiKey("raw-key");

        mockMvc.perform(post("/api/v1/keys/1/use")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request))
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200));

        verify(activityService).validateAndMarkApiKeyUsed(1L, "raw-key");
    }

    @Test
    void validateApiKey_shouldReturnOk() throws Exception {
        UseApiKeyRequest request = new UseApiKeyRequest();
        request.setApiKey("raw-key");

        mockMvc.perform(post("/api/v1/keys/validate")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request))
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200));

        verify(activityService).validateApiKeyByPrefixAndMarkUsed("raw-key");
    }

    // ==================== 权限契约测试 ====================
    // 注：权限拦截由 PermissionInterceptor 处理，此处验证细粒度权限注解已正确配置
    // 实际403测试需要集成测试环境，此处验证接口正常访问

    @Test
    void listApiKeys_shouldReturnOk_withValidAuth() throws Exception {
        when(activityService.listApiKeys()).thenReturn(java.util.Collections.emptyList());

        mockMvc.perform(get("/api/v1/keys")
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200));
    }

    @Test
    void enableApiKey_shouldReturnOk() throws Exception {
        mockMvc.perform(post("/api/v1/keys/1/enable")
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200));

        verify(activityService).enableApiKey(1L);
    }

    @Test
    void disableApiKey_shouldReturnOk() throws Exception {
        mockMvc.perform(post("/api/v1/keys/1/disable")
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200));

        verify(activityService).disableApiKey(1L);
    }

    @Test
    void resetApiKey_shouldReturnOk() throws Exception {
        when(activityService.resetApiKey(1L)).thenReturn("new-raw-key");

        mockMvc.perform(post("/api/v1/keys/1/reset")
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.apiKey").value("new-raw-key"));

        verify(activityService).resetApiKey(1L);
    }
}
