package com.mosquito.project.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mosquito.project.domain.Activity;
import com.mosquito.project.domain.LeaderboardEntry;
import com.mosquito.project.dto.ActivityGraphResponse;
import com.mosquito.project.dto.ActivityStatsResponse;
import com.mosquito.project.dto.CreateActivityRequest;
import com.mosquito.project.dto.UpdateActivityRequest;
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.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(ActivityController.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 ActivityControllerContractTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private ActivityService activityService;

    @Test
    void shouldReturnApiResponseEnvelope() throws Exception {
        Activity activity = new Activity();
        activity.setId(1L);
        activity.setName("测试活动");
        when(activityService.getActivityById(1L)).thenReturn(activity);

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

    @Test
    void shouldCreateActivity() throws Exception {
        CreateActivityRequest request = new CreateActivityRequest();
        request.setName("新活动");
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusDays(7));

        Activity activity = new Activity();
        activity.setId(1L);
        activity.setName("新活动");
        when(activityService.createActivity(any(CreateActivityRequest.class))).thenReturn(activity);

        mockMvc.perform(post("/api/v1/activities")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request))
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.code").value(201))
            .andExpect(jsonPath("$.data.id").value(1));
    }

    @Test
    void shouldGetActivities() throws Exception {
        List<Activity> activities = new ArrayList<>();
        Activity activity = new Activity();
        activity.setId(1L);
        activity.setName("活动1");
        activities.add(activity);
        when(activityService.getAllActivities()).thenReturn(activities);

        mockMvc.perform(get("/api/v1/activities")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data[0].id").value(1));
    }

    @Test
    void shouldUpdateActivity() throws Exception {
        UpdateActivityRequest request = new UpdateActivityRequest();
        request.setName("更新活动");
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusDays(7));

        Activity activity = new Activity();
        activity.setId(1L);
        activity.setName("更新活动");
        when(activityService.updateActivity(eq(1L), any(UpdateActivityRequest.class))).thenReturn(activity);

        mockMvc.perform(put("/api/v1/activities/1")
                .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.id").value(1));
    }

    @Test
    void shouldGetActivityStats() throws Exception {
        ActivityStatsResponse stats = new ActivityStatsResponse(100L, 50L, new ArrayList<>());
        when(activityService.getActivityStats(1L)).thenReturn(stats);

        mockMvc.perform(get("/api/v1/activities/1/stats")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.totalParticipants").value(100));
    }

    @Test
    void shouldGetActivityGraph() throws Exception {
        ActivityGraphResponse graph = new ActivityGraphResponse(new ArrayList<>(), new ArrayList<>());
        when(activityService.getActivityGraph(anyLong(), any(), any(), any())).thenReturn(graph);

        mockMvc.perform(get("/api/v1/activities/1/graph")
                .param("rootUserId", "1")
                .param("maxDepth", "3")
                .param("limit", "1000")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200));
    }

    @Test
    void shouldGetLeaderboard_withTopN() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            entries.add(new LeaderboardEntry((long) i, "用户" + i, 100 - i));
        }
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .param("topN", "5")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.length()").value(5));
    }

    @Test
    void shouldGetLeaderboard_withPagination() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        for (int i = 1; i <= 50; i++) {
            entries.add(new LeaderboardEntry((long) i, "用户" + i, 100 - i));
        }
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .param("page", "1")
                .param("size", "20")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.length()").value(20))
            .andExpect(jsonPath("$.meta.pagination.page").value(1))
            .andExpect(jsonPath("$.meta.pagination.size").value(20))
            .andExpect(jsonPath("$.meta.pagination.total").value(50));
    }

    @Test
    void shouldGetLeaderboard_returnEmptyWhenPageExceedsTotal() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            entries.add(new LeaderboardEntry((long) i, "用户" + i, 100 - i));
        }
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .param("page", "10")
                .param("size", "20")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.length()").value(0));
    }

    @Test
    void shouldGetLeaderboard_handleNullPage() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        entries.add(new LeaderboardEntry(1L, "用户1", 100));
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.meta.pagination.page").value(0));
    }

    @Test
    void shouldGetLeaderboard_handleNegativePage() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        entries.add(new LeaderboardEntry(1L, "用户1", 100));
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .param("page", "-1")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.meta.pagination.page").value(0));
    }

    @Test
    void shouldGetLeaderboard_handleInvalidSize() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        entries.add(new LeaderboardEntry(1L, "用户1", 100));
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .param("size", "0")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.meta.pagination.size").value(20));
    }

    @Test
    void shouldExportLeaderboard_withoutTopN() throws Exception {
        when(activityService.generateLeaderboardCsv(1L)).thenReturn("userId,userName,score\n1,用户1,100\n");

        mockMvc.perform(get("/api/v1/activities/1/leaderboard/export")
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(header().string("Content-Type", "text/csv;charset=UTF-8"))
            .andExpect(header().string("Content-Disposition", "attachment; filename=\"leaderboard_1.csv\""));
    }

    @Test
    void shouldExportLeaderboard_withTopN() throws Exception {
        when(activityService.generateLeaderboardCsv(1L, 10)).thenReturn("userId,userName,score\n1,用户1,100\n");

        mockMvc.perform(get("/api/v1/activities/1/leaderboard/export")
                .param("topN", "10")
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(header().string("Content-Type", "text/csv;charset=UTF-8"));
    }

    @Test
    void shouldGetLeaderboard_whenTopNExceedsListSize() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            entries.add(new LeaderboardEntry((long) i, "用户" + i, 100 - i));
        }
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .param("topN", "100")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.length()").value(5));
    }

    @Test
    void shouldGetLeaderboard_whenTopNIsZero() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        entries.add(new LeaderboardEntry(1L, "用户1", 100));
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .param("topN", "0")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.length()").value(1));
    }

    @Test
    void shouldGetLeaderboard_whenTopNIsNegative() throws Exception {
        List<LeaderboardEntry> entries = new ArrayList<>();
        entries.add(new LeaderboardEntry(1L, "用户1", 100));
        when(activityService.getLeaderboard(1L)).thenReturn(entries);

        mockMvc.perform(get("/api/v1/activities/1/leaderboard")
                .param("topN", "-1")
                .accept(MediaType.APPLICATION_JSON)
                .header("X-API-Key", TestAuthSupport.RAW_API_KEY)
                .header("Authorization", "Bearer test-token"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.code").value(200))
            .andExpect(jsonPath("$.data.length()").value(1));
    }
}
