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 static org.assertj.core.api.Assertions.assertThat;

class ShareTrackingResponseTest {

    private ObjectMapper objectMapper;

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

    @Test
    void shouldCreateEmptyInstance_whenDefaultConstructorCalled() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        assertThat(response.getTrackingId()).isNull();
        assertThat(response.getShortCode()).isNull();
        assertThat(response.getOriginalUrl()).isNull();
        assertThat(response.getActivityId()).isNull();
        assertThat(response.getInviterUserId()).isNull();
        assertThat(response.getCreatedAt()).isNull();
    }

    @Test
    void shouldInitializeWithValues_whenParameterizedConstructorCalled() {
        String trackingId = "track-123";
        String shortCode = "abc123";
        String originalUrl = "https://example.com/page";
        Long activityId = 1L;
        Long inviterUserId = 100L;

        ShareTrackingResponse response = new ShareTrackingResponse(
            trackingId, shortCode, originalUrl, activityId, inviterUserId
        );

        assertThat(response.getTrackingId()).isEqualTo(trackingId);
        assertThat(response.getShortCode()).isEqualTo(shortCode);
        assertThat(response.getOriginalUrl()).isEqualTo(originalUrl);
        assertThat(response.getActivityId()).isEqualTo(activityId);
        assertThat(response.getInviterUserId()).isEqualTo(inviterUserId);
        assertThat(response.getCreatedAt()).isNotNull();
    }

    @Test
    void shouldSetCreatedAtToNow_whenUsingParameterizedConstructor() {
        OffsetDateTime before = OffsetDateTime.now();
        ShareTrackingResponse response = new ShareTrackingResponse("t", "s", "u", 1L, 1L);
        OffsetDateTime after = OffsetDateTime.now();

        assertThat(response.getCreatedAt()).isBetween(before, after);
    }

    @ParameterizedTest
    @ValueSource(strings = {"", "   ", "track-id-123", "TRACK-ABC-999"})
    void shouldAcceptVariousTrackingIds_whenSet(String trackingId) {
        // Note: 100 character string tested separately
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setTrackingId(trackingId);
        assertThat(response.getTrackingId()).isEqualTo(trackingId);
    }

    @Test
    void shouldAcceptLongTrackingId_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        String longTrackingId = "a".repeat(100);
        response.setTrackingId(longTrackingId);
        assertThat(response.getTrackingId()).hasSize(100);
    }

    @Test
    void shouldAcceptNullTrackingId_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setTrackingId("initial");
        response.setTrackingId(null);
        assertThat(response.getTrackingId()).isNull();
    }

    @ParameterizedTest
    @ValueSource(strings = {"abc123", "XYZ789", "123456", "a1b2c3", "SHORT"})
    void shouldAcceptVariousShortCodes_whenSet(String shortCode) {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setShortCode(shortCode);
        assertThat(response.getShortCode()).isEqualTo(shortCode);
    }

    @Test
    void shouldAcceptEmptyShortCode_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setShortCode("");
        assertThat(response.getShortCode()).isEmpty();
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "https://example.com",
        "http://localhost:8080/page",
        "https://very-long-domain-name.example.com/path/to/resource",
        "ftp://files.example.com",
        "custom://app/data"
    })
    void shouldAcceptVariousUrls_whenSet(String url) {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setOriginalUrl(url);
        assertThat(response.getOriginalUrl()).isEqualTo(url);
    }

    @Test
    void shouldAcceptLongUrl_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        String longUrl = "https://example.com/" + "a".repeat(2000);
        response.setOriginalUrl(longUrl);
        assertThat(response.getOriginalUrl()).hasSize(longUrl.length());
    }

    @ParameterizedTest
    @CsvSource({
        "1, 1",
        "0, 0",
        "999999, 999999",
        "-1, -1"
    })
    void shouldAcceptVariousActivityIds_whenSet(Long activityId, Long expected) {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setActivityId(activityId);
        assertThat(response.getActivityId()).isEqualTo(expected);
    }

    @Test
    void shouldAcceptMaxLongActivityId_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setActivityId(Long.MAX_VALUE);
        assertThat(response.getActivityId()).isEqualTo(Long.MAX_VALUE);
    }

    @ParameterizedTest
    @CsvSource({
        "1, 1",
        "100, 100",
        "0, 0",
        "-999, -999"
    })
    void shouldAcceptVariousInviterUserIds_whenSet(Long inviterUserId, Long expected) {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setInviterUserId(inviterUserId);
        assertThat(response.getInviterUserId()).isEqualTo(expected);
    }

    @Test
    void shouldHandleTimeInDifferentTimeZones_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        OffsetDateTime utcTime = OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC);
        OffsetDateTime beijingTime = OffsetDateTime.of(2024, 1, 1, 20, 0, 0, 0, ZoneOffset.ofHours(8));

        response.setCreatedAt(utcTime);
        assertThat(response.getCreatedAt()).isEqualTo(utcTime);

        response.setCreatedAt(beijingTime);
        assertThat(response.getCreatedAt()).isEqualTo(beijingTime);
    }

    @Test
    void shouldSerializeToJson_whenValidData() throws JsonProcessingException {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setTrackingId("track-123");
        response.setShortCode("abc456");
        response.setOriginalUrl("https://example.com/page1");
        response.setActivityId(42L);
        response.setInviterUserId(99L);
        response.setCreatedAt(OffsetDateTime.of(2024, 6, 15, 10, 30, 0, 0, ZoneOffset.UTC));

        String json = objectMapper.writeValueAsString(response);

        assertThat(json).contains("\"trackingId\":\"track-123\"");
        assertThat(json).contains("\"shortCode\":\"abc456\"");
        assertThat(json).contains("\"originalUrl\":\"https://example.com/page1\"");
        assertThat(json).contains("\"activityId\":42");
        assertThat(json).contains("\"inviterUserId\":99");
    }

    @Test
    void shouldDeserializeFromJson_whenValidJson() throws JsonProcessingException {
        String json = """
            {
                "trackingId": "track-456",
                "shortCode": "xyz789",
                "originalUrl": "https://test.com",
                "activityId": 10,
                "inviterUserId": 20,
                "createdAt": "2024-03-20T15:45:00Z"
            }
            """;

        ShareTrackingResponse response = objectMapper.readValue(json, ShareTrackingResponse.class);

        assertThat(response.getTrackingId()).isEqualTo("track-456");
        assertThat(response.getShortCode()).isEqualTo("xyz789");
        assertThat(response.getOriginalUrl()).isEqualTo("https://test.com");
        assertThat(response.getActivityId()).isEqualTo(10L);
        assertThat(response.getInviterUserId()).isEqualTo(20L);
        assertThat(response.getCreatedAt()).isNotNull();
    }

    @Test
    void shouldDeserializeEmptyJson_whenNoData() throws JsonProcessingException {
        String json = "{}";
        ShareTrackingResponse response = objectMapper.readValue(json, ShareTrackingResponse.class);
        assertThat(response.getTrackingId()).isNull();
        assertThat(response.getActivityId()).isNull();
    }

    @Test
    void shouldAllowFieldReassignment_whenMultipleSets() {
        ShareTrackingResponse response = new ShareTrackingResponse();

        response.setTrackingId("first");
        response.setTrackingId("second");
        assertThat(response.getTrackingId()).isEqualTo("second");

        response.setShortCode("code1");
        response.setShortCode("code2");
        assertThat(response.getShortCode()).isEqualTo("code2");

        response.setActivityId(1L);
        response.setActivityId(2L);
        assertThat(response.getActivityId()).isEqualTo(2L);
    }

    @Test
    void shouldHandleSpecialCharactersInStrings_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        response.setTrackingId("track-id\twith\ttabs");
        response.setShortCode("code\nwith\nnewlines");
        response.setOriginalUrl("https://example.com/path?param=value&other=test");

        assertThat(response.getTrackingId()).contains("\t");
        assertThat(response.getShortCode()).contains("\n");
        assertThat(response.getOriginalUrl()).contains("?").contains("&").contains("=");
    }

    @Test
    void shouldSerializeNullValues_whenFieldsNotSet() throws JsonProcessingException {
        ShareTrackingResponse response = new ShareTrackingResponse();
        String json = objectMapper.writeValueAsString(response);
        assertThat(json).contains("trackingId").contains("shortCode").contains("activityId");
    }

    @Test
    void shouldMaintainTimePrecision_whenSerializing() throws JsonProcessingException {
        ShareTrackingResponse response = new ShareTrackingResponse();
        OffsetDateTime preciseTime = OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 123456789, ZoneOffset.UTC);
        response.setCreatedAt(preciseTime);

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

        assertThat(deserialized.getCreatedAt()).isEqualTo(preciseTime);
    }

    @Test
    void shouldCreateFromFactoryMethod_withAllFields() {
        ShareTrackingResponse response = new ShareTrackingResponse(
            "factory-id",
            "factory-code",
            "https://factory.example.com",
            999L,
            888L
        );

        assertThat(response.getTrackingId()).isEqualTo("factory-id");
        assertThat(response.getShortCode()).isEqualTo("factory-code");
        assertThat(response.getOriginalUrl()).isEqualTo("https://factory.example.com");
        assertThat(response.getActivityId()).isEqualTo(999L);
        assertThat(response.getInviterUserId()).isEqualTo(888L);
    }

    @Test
    void shouldAllowNullActivityId_whenUsingConstructor() {
        ShareTrackingResponse response = new ShareTrackingResponse(
            "t", "s", "url", null, 1L
        );
        assertThat(response.getActivityId()).isNull();
    }

    @Test
    void shouldAllowNullInviterUserId_whenUsingConstructor() {
        ShareTrackingResponse response = new ShareTrackingResponse(
            "t", "s", "url", 1L, null
        );
        assertThat(response.getInviterUserId()).isNull();
    }

    @Test
    void shouldHandleEpochTime_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        OffsetDateTime epoch = OffsetDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
        response.setCreatedAt(epoch);
        assertThat(response.getCreatedAt()).isEqualTo(epoch);
    }

    @Test
    void shouldHandleFutureTime_whenSet() {
        ShareTrackingResponse response = new ShareTrackingResponse();
        OffsetDateTime future = OffsetDateTime.of(2099, 12, 31, 23, 59, 59, 0, ZoneOffset.UTC);
        response.setCreatedAt(future);
        assertThat(response.getCreatedAt()).isEqualTo(future);
    }

    @Test
    void shouldRoundTripThroughJson_whenCompleteObject() throws JsonProcessingException {
        ShareTrackingResponse original = new ShareTrackingResponse();
        original.setTrackingId("round-trip-test");
        original.setShortCode("rt123");
        original.setOriginalUrl("https://roundtrip.example.com/test");
        original.setActivityId(777L);
        original.setInviterUserId(666L);
        original.setCreatedAt(OffsetDateTime.now());

        String json = objectMapper.writeValueAsString(original);
        ShareTrackingResponse roundTripped = objectMapper.readValue(json, ShareTrackingResponse.class);

        assertThat(roundTripped.getTrackingId()).isEqualTo(original.getTrackingId());
        assertThat(roundTripped.getShortCode()).isEqualTo(original.getShortCode());
        assertThat(roundTripped.getOriginalUrl()).isEqualTo(original.getOriginalUrl());
        assertThat(roundTripped.getActivityId()).isEqualTo(original.getActivityId());
        assertThat(roundTripped.getInviterUserId()).isEqualTo(original.getInviterUserId());
        assertThat(roundTripped.getCreatedAt()).isEqualTo(original.getCreatedAt());
    }
}
