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 jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeAll;
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.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;

import java.time.ZonedDateTime;
import java.util.Set;

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

class UpdateActivityRequestTest {

    private static Validator validator;
    private ObjectMapper objectMapper;
    private UpdateActivityRequest request;

    @BeforeAll
    static void setUpValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

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

    @Test
    void shouldPassValidation_whenAllFieldsValid() {
        request.setName("Valid Activity Name");
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusDays(1));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations).isEmpty();
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "A",
        "Test Activity",
        "Activity With 100 Characters Max Length Test Data Here End Point",
        "中文活动名称",
        "Special !@#$%^&*() Characters"
    })
    void shouldPassValidation_whenNameValid(String name) {
        request.setName(name);
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusHours(1));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations).isEmpty();
    }

    @ParameterizedTest
    @NullAndEmptySource
    @ValueSource(strings = {"   ", "\t", "\n"})
    void shouldFailValidation_whenNameBlank(String name) {
        request.setName(name);
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusHours(1));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations)
            .hasSizeGreaterThanOrEqualTo(1)
            .anyMatch(v -> v.getMessage().contains("不能为空"));
    }

    @Test
    void shouldFailValidation_whenNameExceedsMaxLength() {
        request.setName("A".repeat(101));
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusHours(1));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations)
            .anyMatch(v -> v.getPropertyPath().toString().equals("name") &&
                          v.getMessage().contains("不能超过100个字符"));
    }

    @Test
    void shouldPassValidation_whenNameAtMaxLength() {
        request.setName("A".repeat(100));
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusHours(1));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations).isEmpty();
    }

    @Test
    void shouldFailValidation_whenStartTimeNull() {
        request.setName("Valid Name");
        request.setStartTime(null);
        request.setEndTime(ZonedDateTime.now().plusHours(1));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations)
            .anyMatch(v -> v.getPropertyPath().toString().equals("startTime") &&
                          v.getMessage().contains("不能为空"));
    }

    @Test
    void shouldFailValidation_whenEndTimeNull() {
        request.setName("Valid Name");
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(null);

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations)
            .anyMatch(v -> v.getPropertyPath().toString().equals("endTime") &&
                          v.getMessage().contains("不能为空"));
    }

    @Test
    void shouldFailValidation_whenAllFieldsNull() {
        request.setName(null);
        request.setStartTime(null);
        request.setEndTime(null);

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations).hasSizeGreaterThanOrEqualTo(3);
    }

    @Test
    void shouldSerializeToJson_whenValidData() throws JsonProcessingException {
        request.setName("Test Activity");
        ZonedDateTime startTime = ZonedDateTime.of(2024, 1, 1, 9, 0, 0, 0, java.time.ZoneId.of("Asia/Shanghai"));
        ZonedDateTime endTime = ZonedDateTime.of(2024, 1, 31, 18, 0, 0, 0, java.time.ZoneId.of("Asia/Shanghai"));
        request.setStartTime(startTime);
        request.setEndTime(endTime);

        String json = objectMapper.writeValueAsString(request);

        assertThat(json).contains("\"name\":\"Test Activity\"");
        assertThat(json).contains("startTime");
        assertThat(json).contains("endTime");
        assertThat(json).isNotNull();
    }

    @Test
    void shouldDeserializeFromJson_whenValidJson() throws JsonProcessingException {
        String json = """
            {
                "name": "Deserialized Activity",
                "startTime": "2024-06-15T10:30:00+08:00",
                "endTime": "2024-06-20T18:00:00+08:00"
            }
            """;

        UpdateActivityRequest deserialized = objectMapper.readValue(json, UpdateActivityRequest.class);

        assertThat(deserialized.getName()).isEqualTo("Deserialized Activity");
        assertThat(deserialized.getStartTime()).isNotNull();
        assertThat(deserialized.getEndTime()).isNotNull();
        assertThat(deserialized.getEndTime()).isAfter(deserialized.getStartTime());
    }

    @Test
    void shouldDeserializeEmptyJson_whenNoData() throws JsonProcessingException {
        String json = "{}";
        UpdateActivityRequest deserialized = objectMapper.readValue(json, UpdateActivityRequest.class);
        assertThat(deserialized.getName()).isNull();
        assertThat(deserialized.getStartTime()).isNull();
        assertThat(deserialized.getEndTime()).isNull();
    }

    @Test
    void shouldHandleDifferentTimeZones_whenSettingTimes() {
        ZonedDateTime utcTime = ZonedDateTime.now(java.time.ZoneId.of("UTC"));
        ZonedDateTime beijingTime = ZonedDateTime.now(java.time.ZoneId.of("Asia/Shanghai"));

        request.setStartTime(utcTime);
        request.setEndTime(beijingTime);

        assertThat(request.getStartTime()).isEqualTo(utcTime);
        assertThat(request.getEndTime()).isEqualTo(beijingTime);
    }

    @Test
    void shouldAllowReassignmentOfFields_whenMultipleSets() {
        request.setName("First Name");
        request.setName("Second Name");
        assertThat(request.getName()).isEqualTo("Second Name");

        ZonedDateTime firstStart = ZonedDateTime.now();
        ZonedDateTime secondStart = firstStart.plusDays(1);
        request.setStartTime(firstStart);
        request.setStartTime(secondStart);
        assertThat(request.getStartTime()).isEqualTo(secondStart);
    }

    @Test
    void shouldHandleUnicodeCharacters_whenSettingName() {
        request.setName("活动名称 🎉 Émojis Ñoño 日本語");
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusHours(1));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations).isEmpty();
        assertThat(request.getName()).contains("🎉").contains("日本語");
    }

    @Test
    void shouldValidateEndTimeAfterStartTime_whenBusinessLogicApplied() {
        ZonedDateTime startTime = ZonedDateTime.of(2024, 1, 2, 10, 0, 0, 0, java.time.ZoneId.systemDefault());
        ZonedDateTime endTime = ZonedDateTime.of(2024, 1, 1, 10, 0, 0, 0, java.time.ZoneId.systemDefault());

        request.setName("Test");
        request.setStartTime(startTime);
        request.setEndTime(endTime);

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations).isEmpty();
        assertThat(request.getEndTime()).isBefore(request.getStartTime());
    }

    @Test
    void shouldCompareWithCreateActivityRequest_whenDifferencesChecked() {
        CreateActivityRequest createRequest = new CreateActivityRequest();
        createRequest.setName("Create Name");
        createRequest.setStartTime(ZonedDateTime.now());
        createRequest.setEndTime(ZonedDateTime.now().plusDays(1));

        UpdateActivityRequest updateRequest = new UpdateActivityRequest();
        updateRequest.setName("Update Name");
        updateRequest.setStartTime(ZonedDateTime.now());
        updateRequest.setEndTime(ZonedDateTime.now().plusDays(1));

        assertThat(createRequest.getName()).isNotEqualTo(updateRequest.getName());
        assertThat(createRequest.getClass()).isNotEqualTo(updateRequest.getClass());
    }

    @Test
    void shouldHandleSingleCharacterName_whenSet() {
        request.setName("X");
        request.setStartTime(ZonedDateTime.now());
        request.setEndTime(ZonedDateTime.now().plusHours(1));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations).isEmpty();
    }

    @ParameterizedTest
    @CsvSource({
        "2024-01-01T00:00:00Z, 2024-01-01T00:00:01Z",
        "2024-01-01T00:00:00Z, 2024-12-31T23:59:59Z",
        "2020-01-01T00:00:00Z, 2030-12-31T23:59:59Z"
    })
    void shouldAcceptVariousTimeRanges_whenValid(String start, String end) {
        request.setName("Time Range Test");
        request.setStartTime(ZonedDateTime.parse(start));
        request.setEndTime(ZonedDateTime.parse(end));

        Set<ConstraintViolation<UpdateActivityRequest>> violations = validator.validate(request);
        assertThat(violations).isEmpty();
    }

    @Test
    void shouldRoundTripThroughJson_whenCompleteObject() throws JsonProcessingException {
        request.setName("Round Trip Test");
        ZonedDateTime start = ZonedDateTime.of(2024, 3, 15, 14, 30, 0, 0, java.time.ZoneId.of("Europe/London"));
        ZonedDateTime end = ZonedDateTime.of(2024, 4, 15, 14, 30, 0, 0, java.time.ZoneId.of("Europe/London"));
        request.setStartTime(start);
        request.setEndTime(end);

        String json = objectMapper.writeValueAsString(request);
        UpdateActivityRequest roundTripped = objectMapper.readValue(json, UpdateActivityRequest.class);

        assertThat(roundTripped.getName()).isEqualTo(request.getName());
        assertThat(roundTripped.getStartTime()).isEqualTo(request.getStartTime());
        assertThat(roundTripped.getEndTime()).isEqualTo(request.getEndTime());
    }

    @Test
    void shouldReturnNull_whenFieldsNotSet() {
        assertThat(request.getName()).isNull();
        assertThat(request.getStartTime()).isNull();
        assertThat(request.getEndTime()).isNull();
    }

    @Test
    void shouldHandleNullNameExplicitly_whenSetToNull() {
        request.setName("initial");
        request.setName(null);
        assertThat(request.getName()).isNull();
    }

    @Test
    void shouldStoreWhitespaceName_whenExplicitlySet() {
        request.setName("   ");
        assertThat(request.getName()).isEqualTo("   ");
    }
}
