package com.mosquito.project.dto;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.Set;

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

/**
 * RegisterCallbackRequest DTO测试
 */
@DisplayName("RegisterCallbackRequest DTO测试")
class RegisterCallbackRequestTest {

    private Validator validator;
    private RegisterCallbackRequest request;
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
        request = new RegisterCallbackRequest();
        objectMapper = new ObjectMapper();
    }

    @Nested
    @DisplayName("验证测试")
    class ValidationTests {

        @Test
        @DisplayName("有效的请求应该通过验证")
        void shouldPassValidation_WhenValidRequest() {
            // Given
            request.setTrackingId("track-123");
            request.setExternalUserId("user-456");
            request.setTimestamp(1234567890L);

            // When
            Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(request);

            // Then
            assertThat(violations).isEmpty();
        }

        @Test
        @DisplayName("只有trackingId的请求应该通过验证")
        void shouldPassValidation_WhenOnlyTrackingId() {
            // Given
            request.setTrackingId("track-123");

            // When
            Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(request);

            // Then
            assertThat(violations).isEmpty();
        }

        @ParameterizedTest
        @NullAndEmptySource
        @ValueSource(strings = {"   ", "\t", "\n", "\r"})
        @DisplayName("无效trackingId应该失败验证")
        void shouldFailValidation_WhenInvalidTrackingId(String trackingId) {
            // Given
            request.setTrackingId(trackingId);

            // When
            Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(request);

            // Then
            assertThat(violations).isNotEmpty();
            assertThat(violations).hasSize(1);
            
            ConstraintViolation<RegisterCallbackRequest> violation = violations.iterator().next();
            assertThat(violation.getPropertyPath().toString()).isEqualTo("trackingId");
        }

        @Test
        @DisplayName("null externalUserId应该通过验证")
        void shouldPassValidation_WhenExternalUserIdIsNull() {
            // Given
            request.setTrackingId("track-123");
            request.setExternalUserId(null);

            // When
            Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(request);

            // Then
            assertThat(violations).isEmpty();
        }

        @Test
        @DisplayName("空externalUserId应该通过验证")
        void shouldPassValidation_WhenExternalUserIdIsEmpty() {
            // Given
            request.setTrackingId("track-123");
            request.setExternalUserId("");

            // When
            Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(request);

            // Then
            assertThat(violations).isEmpty();
        }

        @Test
        @DisplayName("null timestamp应该通过验证")
        void shouldPassValidation_WhenTimestampIsNull() {
            // Given
            request.setTrackingId("track-123");
            request.setTimestamp(null);

            // When
            Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(request);

            // Then
            assertThat(violations).isEmpty();
        }

        @Test
        @DisplayName("零timestamp应该通过验证")
        void shouldPassValidation_WhenTimestampIsZero() {
            // Given
            request.setTrackingId("track-123");
            request.setTimestamp(0L);

            // When
            Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(request);

            // Then
            assertThat(violations).isEmpty();
        }

        @Test
        @DisplayName("负timestamp应该通过验证")
        void shouldPassValidation_WhenTimestampIsNegative() {
            // Given
            request.setTrackingId("track-123");
            request.setTimestamp(-1L);

            // When
            Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(request);

            // Then
            assertThat(violations).isEmpty();
        }
    }

    @Nested
    @DisplayName("Getter和Setter测试")
    class GetterSetterTests {

        @Test
        @DisplayName("trackingId字段的getter和setter应该正常工作")
        void shouldWorkCorrectly_TrackingIdGetterSetter() {
            // Given
            String trackingId = "track-12345";

            // When
            request.setTrackingId(trackingId);

            // Then
            assertThat(request.getTrackingId()).isEqualTo(trackingId);
        }

        @Test
        @DisplayName("externalUserId字段的getter和setter应该正常工作")
        void shouldWorkCorrectly_ExternalUserIdGetterSetter() {
            // Given
            String externalUserId = "user-67890";

            // When
            request.setExternalUserId(externalUserId);

            // Then
            assertThat(request.getExternalUserId()).isEqualTo(externalUserId);
        }

        @Test
        @DisplayName("timestamp字段的getter和setter应该正常工作")
        void shouldWorkCorrectly_TimestampGetterSetter() {
            // Given
            Long timestamp = 1234567890L;

            // When
            request.setTimestamp(timestamp);

            // Then
            assertThat(request.getTimestamp()).isEqualTo(timestamp);
        }

        @Test
        @DisplayName("多次设置trackingId应该正确更新")
        void shouldUpdateCorrectly_WhenSettingTrackingIdMultipleTimes() {
            // Given
            request.setTrackingId("track-1");
            assertThat(request.getTrackingId()).isEqualTo("track-1");

            // When
            request.setTrackingId("track-2");

            // Then
            assertThat(request.getTrackingId()).isEqualTo("track-2");
        }

        @Test
        @DisplayName("多次设置externalUserId应该正确更新")
        void shouldUpdateCorrectly_WhenSettingExternalUserIdMultipleTimes() {
            // Given
            request.setExternalUserId("user-1");
            assertThat(request.getExternalUserId()).isEqualTo("user-1");

            // When
            request.setExternalUserId("user-2");

            // Then
            assertThat(request.getExternalUserId()).isEqualTo("user-2");
        }

        @Test
        @DisplayName("多次设置timestamp应该正确更新")
        void shouldUpdateCorrectly_WhenSettingTimestampMultipleTimes() {
            // Given
            request.setTimestamp(1000L);
            assertThat(request.getTimestamp()).isEqualTo(1000L);

            // When
            request.setTimestamp(2000L);

            // Then
            assertThat(request.getTimestamp()).isEqualTo(2000L);
        }

        @Test
        @DisplayName("设置null值应该正确处理")
        void shouldHandleNullValues_WhenSettingFields() {
            // Given
            request.setTrackingId("track-1");
            request.setExternalUserId("user-1");
            request.setTimestamp(1000L);

            // When
            request.setTrackingId(null);
            request.setExternalUserId(null);
            request.setTimestamp(null);

            // Then
            assertThat(request.getTrackingId()).isNull();
            assertThat(request.getExternalUserId()).isNull();
            assertThat(request.getTimestamp()).isNull();
        }
    }

    @Nested
    @DisplayName("边界值测试")
    class BoundaryTests {

        @ParameterizedTest
        @ValueSource(strings = {
            "a",
            "track-123",
            "uuid-550e8400-e29b-41d4-a716-446655440000",
            "ID_with_underscores",
            "ID.with.dots",
            "ID:with:colons",
            "ID/with/slashes",
            "very-long-tracking-id-very-long-tracking-id-very-long-tracking-id-very-long-tracking-id-very-long-tracking-id-very-long-tracking-id-very-long-tracking-id-very-long-tracking-id-very-long-tracking-id-very-long-tracking-id-"
        })
        @DisplayName("各种格式trackingId应该正确处理")
        void shouldHandleVariousFormats_ForTrackingId(String trackingId) {
            // When
            request.setTrackingId(trackingId);

            // Then
            assertThat(request.getTrackingId()).isEqualTo(trackingId);
        }

        @ParameterizedTest
        @ValueSource(strings = {
            "user-123",
            "user@example.com",
            "550e8400-e29b-41d4-a716-446655440000",
            "external-id-with-many-chars-12345",
            ""
        })
        @DisplayName("各种格式externalUserId应该正确处理")
        void shouldHandleVariousFormats_ForExternalUserId(String externalUserId) {
            // Given
            request.setTrackingId("track-123");

            // When
            request.setExternalUserId(externalUserId);

            // Then
            assertThat(request.getExternalUserId()).isEqualTo(externalUserId);
        }

        @ParameterizedTest
        @ValueSource(longs = {
            0L,
            1L,
            -1L,
            Long.MAX_VALUE,
            Long.MIN_VALUE,
            1704067200000L, // 2024-01-01 00:00:00 UTC
            1609459200000L  // 2021-01-01 00:00:00 UTC
        })
        @DisplayName("各种timestamp值应该正确处理")
        void shouldHandleVariousTimestamps(Long timestamp) {
            // Given
            request.setTrackingId("track-123");

            // When
            request.setTimestamp(timestamp);

            // Then
            assertThat(request.getTimestamp()).isEqualTo(timestamp);
        }

        @Test
        @DisplayName("特殊字符trackingId应该正确处理")
        void shouldHandleSpecialCharacters_ForTrackingId() {
            // Given
            String[] specialIds = {
                "track-🔑-测试",
                "track-!@#$%^&*()",
                "track_with_unicode_🔐",
                "track.with.many.dots"
            };

            for (String id : specialIds) {
                // When
                request.setTrackingId(id);

                // Then
                assertThat(request.getTrackingId()).isEqualTo(id);
            }
        }

        @Test
        @DisplayName("特殊字符externalUserId应该正确处理")
        void shouldHandleSpecialCharacters_ForExternalUserId() {
            // Given
            request.setTrackingId("track-123");
            String[] specialIds = {
                "user-🔑-测试",
                "user@domain.com",
                "user-!@#$%^&*()",
                "user_with_unicode_🎉"
            };

            for (String id : specialIds) {
                // When
                request.setExternalUserId(id);

                // Then
                assertThat(request.getExternalUserId()).isEqualTo(id);
            }
        }

        @Test
        @DisplayName("长字符串字段应该正确处理")
        void shouldHandleLongStrings() {
            // Given
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 1000; i++) {
                sb.append("id-").append(i).append("-");
            }
            String longId = sb.toString();

            // When
            request.setTrackingId(longId);
            request.setExternalUserId(longId);

            // Then
            assertThat(request.getTrackingId()).hasSizeGreaterThan(5000);
            assertThat(request.getExternalUserId()).hasSizeGreaterThan(5000);
        }

        @Test
        @DisplayName("包含换行符字段应该正确处理")
        void shouldHandleNewlines() {
            // Given
            String idWithNewlines = "track\nwith\nnewlines";
            String userIdWithNewlines = "user\r\nwith\r\nnewlines";

            // When
            request.setTrackingId(idWithNewlines);
            request.setExternalUserId(userIdWithNewlines);

            // Then
            assertThat(request.getTrackingId()).isEqualTo(idWithNewlines);
            assertThat(request.getExternalUserId()).isEqualTo(userIdWithNewlines);
        }
    }

    @Nested
    @DisplayName("JSON序列化测试")
    class JsonSerializationTests {

        @Test
        @DisplayName("完整对象应该正确序列化为JSON")
        void shouldSerializeCorrectly_CompleteObject() throws JsonProcessingException {
            // Given
            request.setTrackingId("track-123");
            request.setExternalUserId("user-456");
            request.setTimestamp(1234567890L);

            // When
            String json = objectMapper.writeValueAsString(request);

            // Then
            assertThat(json).isNotNull();
            assertThat(json).contains("\"trackingId\":\"track-123\"");
            assertThat(json).contains("\"externalUserId\":\"user-456\"");
            assertThat(json).contains("\"timestamp\":1234567890");
        }

        @Test
        @DisplayName("部分字段应该正确序列化为JSON")
        void shouldSerializeCorrectly_WithPartialFields() throws JsonProcessingException {
            // Given
            request.setTrackingId("track-123");

            // When
            String json = objectMapper.writeValueAsString(request);

            // Then
            assertThat(json).contains("\"trackingId\":\"track-123\"");
        }

        @Test
        @DisplayName("null值应该正确序列化为JSON")
        void shouldSerializeCorrectly_WithNullValues() throws JsonProcessingException {
            // Given
            request.setTrackingId("track-123");
            request.setExternalUserId(null);
            request.setTimestamp(null);

            // When
            String json = objectMapper.writeValueAsString(request);

            // Then
            assertThat(json).contains("\"trackingId\":\"track-123\"");
            assertThat(json).contains("\"externalUserId\":null");
            assertThat(json).contains("\"timestamp\":null");
        }

        @Test
        @DisplayName("空字符串应该正确序列化为JSON")
        void shouldSerializeCorrectly_WithEmptyStrings() throws JsonProcessingException {
            // Given
            request.setTrackingId("track-123");
            request.setExternalUserId("");

            // When
            String json = objectMapper.writeValueAsString(request);

            // Then
            assertThat(json).contains("\"externalUserId\":\"\"");
        }

        @Test
        @DisplayName("特殊字符应该正确序列化为JSON")
        void shouldSerializeCorrectly_WithSpecialCharacters() throws JsonProcessingException {
            // Given
            request.setTrackingId("track-🔑-测试");
            request.setExternalUserId("user-🎉");

            // When
            String json = objectMapper.writeValueAsString(request);

            // Then
            assertThat(json).isNotNull();
            // 验证反序列化后值相同
            RegisterCallbackRequest deserialized = objectMapper.readValue(json, RegisterCallbackRequest.class);
            assertThat(deserialized.getTrackingId()).isEqualTo("track-🔑-测试");
            assertThat(deserialized.getExternalUserId()).isEqualTo("user-🎉");
        }

        @Test
        @DisplayName("零timestamp应该正确序列化为JSON")
        void shouldSerializeCorrectly_WithZeroTimestamp() throws JsonProcessingException {
            // Given
            request.setTrackingId("track-123");
            request.setTimestamp(0L);

            // When
            String json = objectMapper.writeValueAsString(request);

            // Then
            assertThat(json).contains("\"timestamp\":0");
        }

        @Test
        @DisplayName("负timestamp应该正确序列化为JSON")
        void shouldSerializeCorrectly_WithNegativeTimestamp() throws JsonProcessingException {
            // Given
            request.setTrackingId("track-123");
            request.setTimestamp(-1L);

            // When
            String json = objectMapper.writeValueAsString(request);

            // Then
            assertThat(json).contains("\"timestamp\":-1");
        }
    }

    @Nested
    @DisplayName("JSON反序列化测试")
    class JsonDeserializationTests {

        @Test
        @DisplayName("完整JSON应该正确反序列化")
        void shouldDeserializeCorrectly_CompleteJson() throws JsonProcessingException {
            // Given
            String json = "{\"trackingId\":\"track-123\",\"externalUserId\":\"user-456\",\"timestamp\":1234567890}";

            // When
            RegisterCallbackRequest deserialized = objectMapper.readValue(json, RegisterCallbackRequest.class);

            // Then
            assertThat(deserialized.getTrackingId()).isEqualTo("track-123");
            assertThat(deserialized.getExternalUserId()).isEqualTo("user-456");
            assertThat(deserialized.getTimestamp()).isEqualTo(1234567890L);
        }

        @Test
        @DisplayName("部分字段JSON应该正确反序列化")
        void shouldDeserializeCorrectly_PartialFieldsJson() throws JsonProcessingException {
            // Given
            String json = "{\"trackingId\":\"track-123\"}";

            // When
            RegisterCallbackRequest deserialized = objectMapper.readValue(json, RegisterCallbackRequest.class);

            // Then
            assertThat(deserialized.getTrackingId()).isEqualTo("track-123");
            assertThat(deserialized.getExternalUserId()).isNull();
            assertThat(deserialized.getTimestamp()).isNull();
        }

        @Test
        @DisplayName("null值JSON应该正确反序列化")
        void shouldDeserializeCorrectly_WithNullValues() throws JsonProcessingException {
            // Given
            String json = "{\"trackingId\":\"track-123\",\"externalUserId\":null,\"timestamp\":null}";

            // When
            RegisterCallbackRequest deserialized = objectMapper.readValue(json, RegisterCallbackRequest.class);

            // Then
            assertThat(deserialized.getTrackingId()).isEqualTo("track-123");
            assertThat(deserialized.getExternalUserId()).isNull();
            assertThat(deserialized.getTimestamp()).isNull();
        }

        @Test
        @DisplayName("空对象JSON应该正确反序列化")
        void shouldDeserializeCorrectly_EmptyObjectJson() throws JsonProcessingException {
            // Given
            String json = "{}";

            // When
            RegisterCallbackRequest deserialized = objectMapper.readValue(json, RegisterCallbackRequest.class);

            // Then
            assertThat(deserialized.getTrackingId()).isNull();
            assertThat(deserialized.getExternalUserId()).isNull();
            assertThat(deserialized.getTimestamp()).isNull();
        }

        @Test
        @DisplayName("特殊字符JSON应该正确反序列化")
        void shouldDeserializeCorrectly_WithSpecialCharacters() throws JsonProcessingException {
            // Given
            String json = "{\"trackingId\":\"track-🔑\",\"externalUserId\":\"user-🎉\",\"timestamp\":1234567890}";

            // When
            RegisterCallbackRequest deserialized = objectMapper.readValue(json, RegisterCallbackRequest.class);

            // Then
            assertThat(deserialized.getTrackingId()).isEqualTo("track-🔑");
            assertThat(deserialized.getExternalUserId()).isEqualTo("user-🎉");
        }

        @Test
        @DisplayName("零timestamp JSON应该正确反序列化")
        void shouldDeserializeCorrectly_WithZeroTimestamp() throws JsonProcessingException {
            // Given
            String json = "{\"trackingId\":\"track-123\",\"timestamp\":0}";

            // When
            RegisterCallbackRequest deserialized = objectMapper.readValue(json, RegisterCallbackRequest.class);

            // Then
            assertThat(deserialized.getTimestamp()).isZero();
        }

        @Test
        @DisplayName("JSON格式错误应该抛出异常")
        void shouldThrowException_WhenJsonIsMalformed() {
            // Given
            String malformedJson = "{\"trackingId\":\"test\",\"timestamp\":123"; // 缺少闭合括号

            // When & Then
            assertThatThrownBy(() -> objectMapper.readValue(malformedJson, RegisterCallbackRequest.class))
                .isInstanceOf(JsonProcessingException.class);
        }
    }

    @Nested
    @DisplayName("对象行为测试")
    class ObjectBehaviorTests {

        @Test
        @DisplayName("两个相同字段的请求应该相等")
        void shouldBeEqual_WhenSameFields() {
            // Given
            RegisterCallbackRequest request1 = new RegisterCallbackRequest();
            RegisterCallbackRequest request2 = new RegisterCallbackRequest();
            request1.setTrackingId("track-123");
            request1.setExternalUserId("user-456");
            request1.setTimestamp(1000L);
            request2.setTrackingId("track-123");
            request2.setExternalUserId("user-456");
            request2.setTimestamp(1000L);

            // Then
            assertThat(request1.getTrackingId()).isEqualTo(request2.getTrackingId());
            assertThat(request1.getExternalUserId()).isEqualTo(request2.getExternalUserId());
            assertThat(request1.getTimestamp()).isEqualTo(request2.getTimestamp());
        }

        @Test
        @DisplayName("多次调用getter应该返回相同值")
        void shouldReturnSameValue_WhenCallingGetterMultipleTimes() {
            // Given
            request.setTrackingId("consistent-track");
            request.setExternalUserId("consistent-user");
            request.setTimestamp(12345L);

            // When & Then
            assertThat(request.getTrackingId()).isEqualTo("consistent-track");
            assertThat(request.getTrackingId()).isEqualTo("consistent-track");
            assertThat(request.getExternalUserId()).isEqualTo("consistent-user");
            assertThat(request.getTimestamp()).isEqualTo(12345L);
        }

        @Test
        @DisplayName("对象状态应该在setter调用后保持正确")
        void shouldMaintainCorrectState_AfterSetterCalls() {
            // Given
            request.setTrackingId("track-1");
            request.setExternalUserId("user-1");
            request.setTimestamp(1000L);

            // When - 更新所有字段
            request.setTrackingId("track-2");
            request.setExternalUserId("user-2");
            request.setTimestamp(2000L);

            // Then
            assertThat(request.getTrackingId()).isEqualTo("track-2");
            assertThat(request.getExternalUserId()).isEqualTo("user-2");
            assertThat(request.getTimestamp()).isEqualTo(2000L);
        }
    }

    @Nested
    @DisplayName("并发安全测试")
    class ConcurrencyTests {

        @Test
        @DisplayName("多线程并发操作应该是安全的")
        void shouldBeThreadSafe_ConcurrentOperations() throws InterruptedException {
            // Given
            int threadCount = 10;
            Thread[] threads = new Thread[threadCount];
            boolean[] results = new boolean[threadCount];

            // When
            for (int i = 0; i < threadCount; i++) {
                final int threadIndex = i;
                threads[i] = new Thread(() -> {
                    try {
                        RegisterCallbackRequest localRequest = new RegisterCallbackRequest();
                        localRequest.setTrackingId("track-" + threadIndex);
                        localRequest.setExternalUserId("user-" + threadIndex);
                        localRequest.setTimestamp((long) threadIndex);
                        
                        // 验证getter
                        assertThat(localRequest.getTrackingId()).isEqualTo("track-" + threadIndex);
                        assertThat(localRequest.getExternalUserId()).isEqualTo("user-" + threadIndex);
                        assertThat(localRequest.getTimestamp()).isEqualTo((long) threadIndex);
                        
                        // 验证验证器
                        Set<ConstraintViolation<RegisterCallbackRequest>> violations = validator.validate(localRequest);
                        assertThat(violations).isEmpty();
                        
                        results[threadIndex] = true;
                    } catch (Exception e) {
                        results[threadIndex] = false;
                    }
                });
            }

            // 启动所有线程
            for (Thread thread : threads) {
                thread.start();
            }

            // 等待所有线程完成
            for (Thread thread : threads) {
                thread.join();
            }

            // Then
            for (int i = 0; i < threadCount; i++) {
                assertThat(results[i]).isTrue();
            }
        }
    }
}
