package com.mosquito.project.dto;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class ShareMetricsResponseTest {

    private ObjectMapper objectMapper;
    private ShareMetricsResponse response;

    @BeforeEach
    void setUp() {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        response = new ShareMetricsResponse();
    }

    @Test
    void shouldReturnNullActivityId_whenNotSet() {
        assertThat(response.getActivityId()).isNull();
    }

    @Test
    void shouldReturnSetActivityId_whenSetWithPositiveValue() {
        response.setActivityId(123L);
        assertThat(response.getActivityId()).isEqualTo(123L);
    }

    @Test
    void shouldReturnSetActivityId_whenSetWithMaxValue() {
        response.setActivityId(Long.MAX_VALUE);
        assertThat(response.getActivityId()).isEqualTo(Long.MAX_VALUE);
    }

    @Test
    void shouldReturnSetActivityId_whenSetWithZero() {
        response.setActivityId(0L);
        assertThat(response.getActivityId()).isZero();
    }

    @Test
    void shouldReturnNullStartTime_whenNotSet() {
        assertThat(response.getStartTime()).isNull();
    }

    @Test
    void shouldReturnSetStartTime_whenSet() {
        OffsetDateTime startTime = OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
        response.setStartTime(startTime);
        assertThat(response.getStartTime()).isEqualTo(startTime);
    }

    @Test
    void shouldReturnNullEndTime_whenNotSet() {
        assertThat(response.getEndTime()).isNull();
    }

    @Test
    void shouldReturnSetEndTime_whenSet() {
        OffsetDateTime endTime = OffsetDateTime.of(2024, 12, 31, 23, 59, 59, 0, ZoneOffset.UTC);
        response.setEndTime(endTime);
        assertThat(response.getEndTime()).isEqualTo(endTime);
    }

    @Test
    void shouldReturnZeroTotalClicks_whenNotSet() {
        assertThat(response.getTotalClicks()).isZero();
    }

    @ParameterizedTest
    @CsvSource({
        "0, 0",
        "1, 1",
        "100, 100",
        "999999, 999999"
    })
    void shouldReturnSetTotalClicks_whenSetWithValue(long input, long expected) {
        response.setTotalClicks(input);
        assertThat(response.getTotalClicks()).isEqualTo(expected);
    }

    @Test
    void shouldReturnSetTotalClicks_whenSetWithMaxValue() {
        response.setTotalClicks(Long.MAX_VALUE);
        assertThat(response.getTotalClicks()).isEqualTo(Long.MAX_VALUE);
    }

    @Test
    void shouldReturnZeroUniqueVisitors_whenNotSet() {
        assertThat(response.getUniqueVisitors()).isZero();
    }

    @ParameterizedTest
    @CsvSource({
        "0, 0",
        "1, 1",
        "500, 500",
        "1000000, 1000000"
    })
    void shouldReturnSetUniqueVisitors_whenSetWithValue(long input, long expected) {
        response.setUniqueVisitors(input);
        assertThat(response.getUniqueVisitors()).isEqualTo(expected);
    }

    @Test
    void shouldReturnNullSourceDistribution_whenNotSet() {
        assertThat(response.getSourceDistribution()).isNull();
    }

    @Test
    void shouldReturnSetSourceDistribution_whenSetWithEmptyMap() {
        Map<String, Long> emptyMap = new HashMap<>();
        response.setSourceDistribution(emptyMap);
        assertThat(response.getSourceDistribution()).isEmpty();
    }

    @Test
    void shouldReturnSetSourceDistribution_whenSetWithData() {
        Map<String, Long> distribution = new HashMap<>();
        distribution.put("wechat", 100L);
        distribution.put("weibo", 50L);
        distribution.put("qq", 25L);
        response.setSourceDistribution(distribution);
        assertThat(response.getSourceDistribution())
            .hasSize(3)
            .containsEntry("wechat", 100L)
            .containsEntry("weibo", 50L)
            .containsEntry("qq", 25L);
    }

    @Test
    void shouldReturnNullHourlyDistribution_whenNotSet() {
        assertThat(response.getHourlyDistribution()).isNull();
    }

    @Test
    void shouldReturnSetHourlyDistribution_whenSetWith24Hours() {
        Map<String, Long> hourly = new HashMap<>();
        for (int i = 0; i < 24; i++) {
            hourly.put(String.format("%02d:00", i), (long) (i * 10));
        }
        response.setHourlyDistribution(hourly);
        assertThat(response.getHourlyDistribution()).hasSize(24);
    }

    @Test
    void shouldSerializeAndDeserialize_whenValidData() throws JsonProcessingException {
        response.setActivityId(1L);
        response.setStartTime(OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC));
        response.setEndTime(OffsetDateTime.of(2024, 1, 31, 23, 59, 59, 0, ZoneOffset.UTC));
        response.setTotalClicks(1000L);
        response.setUniqueVisitors(500L);

        String json = objectMapper.writeValueAsString(response);
        ShareMetricsResponse deserialized = objectMapper.readValue(json, ShareMetricsResponse.class);

        assertThat(deserialized.getActivityId()).isEqualTo(1L);
        assertThat(deserialized.getStartTime()).isEqualTo(response.getStartTime());
        assertThat(deserialized.getEndTime()).isEqualTo(response.getEndTime());
        assertThat(deserialized.getTotalClicks()).isEqualTo(1000L);
        assertThat(deserialized.getUniqueVisitors()).isEqualTo(500L);
    }

    @Test
    void shouldSerializeWithNullValues_whenFieldsNotSet() throws JsonProcessingException {
        String json = objectMapper.writeValueAsString(response);
        assertThat(json).contains("activityId").contains("totalClicks");
    }

    @Test
    void shouldDeserializeEmptyJson_whenNoData() throws JsonProcessingException {
        String json = "{}";
        ShareMetricsResponse result = objectMapper.readValue(json, ShareMetricsResponse.class);
        assertThat(result.getActivityId()).isNull();
        assertThat(result.getTotalClicks()).isZero();
    }

    @Test
    void shouldHandleDistributionSerialization_whenComplexData() throws JsonProcessingException {
        Map<String, Long> sourceDist = new HashMap<>();
        sourceDist.put("mobile", 800L);
        sourceDist.put("desktop", 200L);
        response.setSourceDistribution(sourceDist);

        String json = objectMapper.writeValueAsString(response);
        ShareMetricsResponse deserialized = objectMapper.readValue(json, ShareMetricsResponse.class);

        assertThat(deserialized.getSourceDistribution())
            .containsEntry("mobile", 800L)
            .containsEntry("desktop", 200L);
    }

    @Test
    void shouldWorkWithBuilderPattern_whenMultipleSets() {
        response.setActivityId(1L);
        response.setTotalClicks(100L);
        response.setUniqueVisitors(50L);

        assertThat(response.getActivityId()).isEqualTo(1L);
        assertThat(response.getTotalClicks()).isEqualTo(100L);
    }

    @Test
    void shouldHandleTimeRangeAcrossYearBoundary() {
        OffsetDateTime start = OffsetDateTime.of(2023, 12, 31, 0, 0, 0, 0, ZoneOffset.UTC);
        OffsetDateTime end = OffsetDateTime.of(2024, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
        response.setStartTime(start);
        response.setEndTime(end);
        assertThat(response.getEndTime().isAfter(response.getStartTime())).isTrue();
    }

    @Test
    void shouldAllowReassignmentOfAllFields() {
        response.setActivityId(1L);
        response.setActivityId(2L);
        assertThat(response.getActivityId()).isEqualTo(2L);

        response.setTotalClicks(100L);
        response.setTotalClicks(200L);
        assertThat(response.getTotalClicks()).isEqualTo(200L);
    }

    @Test
    void shouldStoreNullActivityId_whenExplicitlySetToNull() {
        response.setActivityId(1L);
        response.setActivityId(null);
        assertThat(response.getActivityId()).isNull();
    }

    @Test
    void shouldStoreNullMaps_whenExplicitlySetToNull() {
        Map<String, Long> data = new HashMap<>();
        data.put("key", 1L);
        response.setSourceDistribution(data);
        response.setSourceDistribution(null);
        assertThat(response.getSourceDistribution()).isNull();
    }

    @ParameterizedTest
    @ValueSource(strings = {"wechat", "weibo", "qq", "douyin", "xiaohongshu"})
    void shouldAcceptVariousSourceKeys_whenSettingDistribution(String source) {
        Map<String, Long> dist = new HashMap<>();
        dist.put(source, 100L);
        response.setSourceDistribution(dist);
        assertThat(response.getSourceDistribution()).containsKey(source);
    }

    @Test
    void shouldHandleEmptyStringKeysInDistribution() {
        Map<String, Long> dist = new HashMap<>();
        dist.put("", 0L);
        dist.put("   ", 0L);
        response.setSourceDistribution(dist);
        assertThat(response.getSourceDistribution()).hasSize(2);
    }

    @Test
    void shouldHandleNegativeValuesInDistribution() {
        Map<String, Long> dist = new HashMap<>();
        dist.put("source", -1L);
        response.setSourceDistribution(dist);
        assertThat(response.getSourceDistribution()).containsEntry("source", -1L);
    }

    @Test
    void shouldHandleLargeValuesInMetrics() {
        response.setTotalClicks(9_223_372_036_854_775_807L);
        response.setUniqueVisitors(9_223_372_036_854_775_806L);
        assertThat(response.getTotalClicks()).isEqualTo(Long.MAX_VALUE);
    }
}
