package com.mosquito.project.persistence.entity;

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 ShortLinkEntityTest {

    private ShortLinkEntity entity;

    @BeforeEach
    void setUp() {
        entity = new ShortLinkEntity();
    }

    @Test
    void shouldReturnNullId_whenNotSet() {
        assertThat(entity.getId()).isNull();
    }

    @ParameterizedTest
    @CsvSource({
        "1, 1",
        "100, 100",
        "999999, 999999",
        "0, 0"
    })
    void shouldReturnSetId_whenSetWithValue(Long id, Long expected) {
        entity.setId(id);
        assertThat(entity.getId()).isEqualTo(expected);
    }

    @Test
    void shouldReturnSetId_whenSetWithMaxValue() {
        entity.setId(Long.MAX_VALUE);
        assertThat(entity.getId()).isEqualTo(Long.MAX_VALUE);
    }

    @Test
    void shouldReturnNullCode_whenNotSet() {
        assertThat(entity.getCode()).isNull();
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "abc123",
        "ABC456",
        "123xyz",
        "short",
        "a"
    })
    void shouldAcceptVariousCodeFormats_whenSet(String code) {
        entity.setCode(code);
        assertThat(entity.getCode()).isEqualTo(code);
    }

    @Test
    void shouldAccept32CharCode_whenSet() {
        String code32 = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
        entity.setCode(code32);
        assertThat(entity.getCode()).hasSize(32);
    }

    @Test
    void shouldAcceptEmptyCode_whenSet() {
        entity.setCode("");
        assertThat(entity.getCode()).isEmpty();
    }

    @Test
    void shouldAcceptLongCode_whenUpTo2048Chars() {
        String longCode = "c".repeat(2048);
        entity.setCode(longCode);
        assertThat(entity.getCode()).hasSize(2048);
    }

    @Test
    void shouldReturnNullOriginalUrl_whenNotSet() {
        assertThat(entity.getOriginalUrl()).isNull();
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "https://example.com",
        "http://localhost:8080/page",
        "https://very.long.domain.example.com/path/to/resource/page.html",
        "ftp://files.example.com/download.zip"
    })
    void shouldAcceptVariousUrlFormats_whenSet(String url) {
        entity.setOriginalUrl(url);
        assertThat(entity.getOriginalUrl()).isEqualTo(url);
    }

    @Test
    void shouldHandleLongUrl_whenUpTo2048Chars() {
        String baseUrl = "https://example.com/";
        String longPath = "path/".repeat(400);
        String longUrl = baseUrl + longPath;
        entity.setOriginalUrl(longUrl);
        assertThat(entity.getOriginalUrl()).hasSize(longUrl.length());
    }

    @Test
    void shouldHandleVeryLongUrl_whenExceeding2048() {
        String veryLongUrl = "https://example.com/" + "x".repeat(3000);
        entity.setOriginalUrl(veryLongUrl);
        assertThat(entity.getOriginalUrl()).hasSizeGreaterThan(2048);
    }

    @Test
    void shouldReturnNullCreatedAt_whenNotSet() {
        assertThat(entity.getCreatedAt()).isNull();
    }

    @Test
    void shouldReturnSetCreatedAt_whenSet() {
        OffsetDateTime now = OffsetDateTime.now();
        entity.setCreatedAt(now);
        assertThat(entity.getCreatedAt()).isEqualTo(now);
    }

    @Test
    void shouldHandleDifferentTimeZones_whenSettingCreatedAt() {
        OffsetDateTime utc = OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC);
        OffsetDateTime beijing = OffsetDateTime.of(2024, 1, 1, 20, 0, 0, 0, ZoneOffset.ofHours(8));

        entity.setCreatedAt(utc);
        assertThat(entity.getCreatedAt()).isEqualTo(utc);

        entity.setCreatedAt(beijing);
        assertThat(entity.getCreatedAt()).isEqualTo(beijing);
    }

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

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

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

    @ParameterizedTest
    @CsvSource({
        "1, 1",
        "100, 100",
        "0, 0",
        "-1, -1"
    })
    void shouldReturnSetActivityId_whenSetWithValue(Long activityId, Long expected) {
        entity.setActivityId(activityId);
        assertThat(entity.getActivityId()).isEqualTo(expected);
    }

    @Test
    void shouldReturnNullInviterUserId_whenNotSet() {
        assertThat(entity.getInviterUserId()).isNull();
    }

    @ParameterizedTest
    @CsvSource({
        "1, 1",
        "999, 999",
        "0, 0",
        "-999, -999"
    })
    void shouldReturnSetInviterUserId_whenSetWithValue(Long inviterUserId, Long expected) {
        entity.setInviterUserId(inviterUserId);
        assertThat(entity.getInviterUserId()).isEqualTo(expected);
    }

    @Test
    void shouldCreateCompleteEntity_whenAllFieldsSet() {
        entity.setId(1L);
        entity.setCode("abc123");
        entity.setOriginalUrl("https://example.com/page");
        entity.setCreatedAt(OffsetDateTime.now());
        entity.setActivityId(100L);
        entity.setInviterUserId(50L);

        assertThat(entity.getId()).isEqualTo(1L);
        assertThat(entity.getCode()).isEqualTo("abc123");
        assertThat(entity.getOriginalUrl()).isEqualTo("https://example.com/page");
        assertThat(entity.getCreatedAt()).isNotNull();
        assertThat(entity.getActivityId()).isEqualTo(100L);
        assertThat(entity.getInviterUserId()).isEqualTo(50L);
    }

    @Test
    void shouldAllowFieldReassignment_whenMultipleSets() {
        entity.setId(1L);
        entity.setId(2L);
        assertThat(entity.getId()).isEqualTo(2L);

        entity.setCode("first");
        entity.setCode("second");
        assertThat(entity.getCode()).isEqualTo("second");

        entity.setOriginalUrl("http://first.com");
        entity.setOriginalUrl("http://second.com");
        assertThat(entity.getOriginalUrl()).isEqualTo("http://second.com");
    }

    @Test
    void shouldAcceptNullValues_whenExplicitlySetToNull() {
        entity.setId(1L);
        entity.setId(null);
        assertThat(entity.getId()).isNull();

        entity.setCode("code");
        entity.setCode(null);
        assertThat(entity.getCode()).isNull();

        entity.setCreatedAt(OffsetDateTime.now());
        entity.setCreatedAt(null);
        assertThat(entity.getCreatedAt()).isNull();
    }

    @Test
    void shouldGenerateValidCode_whenUsingConsistentFormat() {
        String code = generateShortCode("https://example.com/page123");
        entity.setCode(code);
        assertThat(entity.getCode()).matches("^[a-zA-Z0-9]+$");
        assertThat(entity.getCode().length()).isLessThanOrEqualTo(32);
    }

    @Test
    void shouldHandleSpecialCharactersInUrl_whenSet() {
        String urlWithParams = "https://example.com/path?query=value&other=test#fragment";
        entity.setOriginalUrl(urlWithParams);
        assertThat(entity.getOriginalUrl())
            .contains("?")
            .contains("&")
            .contains("#");
    }

    @Test
    void shouldHandleInternationalizedUrl_whenSet() {
        String internationalUrl = "https://münchen.example/über-path?param=日本語";
        entity.setOriginalUrl(internationalUrl);
        assertThat(entity.getOriginalUrl()).isEqualTo(internationalUrl);
    }

    @Test
    void shouldSupportChainedSetters_whenBuildingEntity() {
        OffsetDateTime createdAt = OffsetDateTime.now();
        entity.setId(1L);
        entity.setCode("chain123");
        entity.setOriginalUrl("https://chain.example.com");
        entity.setCreatedAt(createdAt);
        entity.setActivityId(100L);
        entity.setInviterUserId(50L);

        assertThat(entity.getId()).isEqualTo(1L);
        assertThat(entity.getCode()).isEqualTo("chain123");
        assertThat(entity.getOriginalUrl()).isEqualTo("https://chain.example.com");
        assertThat(entity.getCreatedAt()).isEqualTo(createdAt);
    }

    @Test
    void shouldHandleUrlWithPort_whenSet() {
        String urlWithPort = "http://localhost:8080/api/v1/users/123";
        entity.setOriginalUrl(urlWithPort);
        assertThat(entity.getOriginalUrl()).isEqualTo(urlWithPort);
    }

    @Test
    void shouldHandleUrlWithUserInfo_whenSet() {
        String urlWithAuth = "https://user:password@example.com/private";
        entity.setOriginalUrl(urlWithAuth);
        assertThat(entity.getOriginalUrl()).contains("user:").contains("@");
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "http://example.com",
        "https://example.com",
        "ftp://ftp.example.com",
        "file:///local/path",
        "mailto:test@example.com",
        "custom://app/resource"
    })
    void shouldAcceptVariousUrlSchemes_whenSet(String url) {
        entity.setOriginalUrl(url);
        assertThat(entity.getOriginalUrl()).isEqualTo(url);
    }

    @Test
    void shouldHandleEmptyEntityState_whenNoFieldsSet() {
        assertThat(entity.getId()).isNull();
        assertThat(entity.getCode()).isNull();
        assertThat(entity.getOriginalUrl()).isNull();
        assertThat(entity.getCreatedAt()).isNull();
        assertThat(entity.getActivityId()).isNull();
        assertThat(entity.getInviterUserId()).isNull();
    }

    @Test
    void shouldAcceptPartialEntityState_whenSomeFieldsSet() {
        entity.setCode("partial");
        entity.setOriginalUrl("https://partial.example.com");

        assertThat(entity.getId()).isNull();
        assertThat(entity.getCode()).isEqualTo("partial");
        assertThat(entity.getActivityId()).isNull();
    }

    @Test
    void shouldHandleUnicodeCharactersInCode_whenSet() {
        entity.setCode("代码-123-🎉");
        assertThat(entity.getCode()).contains("代码").contains("🎉");
    }

    @Test
    void shouldHandleWhitespaceOnlyCode_whenSet() {
        entity.setCode("   ");
        assertThat(entity.getCode()).isEqualTo("   ");
    }

    @Test
    void shouldHandleTimePrecision_whenMillisecondsSet() {
        OffsetDateTime precise = OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 123456789, ZoneOffset.UTC);
        entity.setCreatedAt(precise);
        assertThat(entity.getCreatedAt()).isEqualTo(precise);
    }

    @Test
    void shouldHandleMaxLongIds_whenSet() {
        entity.setId(Long.MAX_VALUE);
        entity.setActivityId(Long.MAX_VALUE);
        entity.setInviterUserId(Long.MAX_VALUE);

        assertThat(entity.getId()).isEqualTo(Long.MAX_VALUE);
        assertThat(entity.getActivityId()).isEqualTo(Long.MAX_VALUE);
        assertThat(entity.getInviterUserId()).isEqualTo(Long.MAX_VALUE);
    }

    @Test
    void shouldHandleNegativeIds_whenSet() {
        entity.setId(-1L);
        entity.setActivityId(-100L);
        entity.setInviterUserId(-999L);

        assertThat(entity.getId()).isEqualTo(-1L);
        assertThat(entity.getActivityId()).isEqualTo(-100L);
        assertThat(entity.getInviterUserId()).isEqualTo(-999L);
    }

    @Test
    void shouldAssociateWithActivity_whenActivityIdSet() {
        ActivityEntity activity = new ActivityEntity();
        activity.setId(100L);

        entity.setActivityId(activity.getId());
        entity.setCode("assoc123");
        entity.setOriginalUrl("https://example.com");

        assertThat(entity.getActivityId()).isEqualTo(100L);
    }

    @Test
    void shouldHandleZeroIds_whenSet() {
        entity.setId(0L);
        entity.setActivityId(0L);
        entity.setInviterUserId(0L);

        assertThat(entity.getId()).isZero();
        assertThat(entity.getActivityId()).isZero();
        assertThat(entity.getInviterUserId()).isZero();
    }

    private String generateShortCode(String originalUrl) {
        return Integer.toHexString(originalUrl.hashCode()) + "x";
    }
}
