package com.mosquito.project.dto;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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 static org.assertj.core.api.Assertions.assertThat;

class ShortenResponseTest {

    private ObjectMapper objectMapper;

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

    @Test
    void shouldCreateInstanceWithAllFields_whenConstructorCalled() {
        String code = "abc123";
        String path = "/s/abc123";
        String originalUrl = "https://example.com/long/path/to/resource";

        ShortenResponse response = new ShortenResponse(code, path, originalUrl);

        assertThat(response.getCode()).isEqualTo(code);
        assertThat(response.getPath()).isEqualTo(path);
        assertThat(response.getOriginalUrl()).isEqualTo(originalUrl);
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "a",
        "abc",
        "abc123",
        "ABC456",
        "123xyz",
        "a1b2c3d4e5",
        "short-code-with-hyphens"
    })
    void shouldAcceptVariousCodeFormats_whenSet(String code) {
        ShortenResponse response = new ShortenResponse("initial", "/p", "http://example.com");
        response.setCode(code);
        assertThat(response.getCode()).isEqualTo(code);
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "",
        "   ",
        "CODE_WITH_UNDERSCORES_123"
    })
    void shouldAcceptEdgeCaseCodeValues_whenSet(String code) {
        // Note: 100 char code tested separately
        ShortenResponse response = new ShortenResponse("x", "/x", "http://x.com");
        response.setCode(code);
        assertThat(response.getCode()).isEqualTo(code);
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "/s/abc",
        "/r/xyz123",
        "/link/ABC456",
        "/go/short",
        "/",
        "/very/long/path/with/many/segments"
    })
    void shouldAcceptVariousPathFormats_whenSet(String path) {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        response.setPath(path);
        assertThat(response.getPath()).isEqualTo(path);
    }

    @ParameterizedTest
    @CsvSource({
        "https://example.com, https://example.com",
        "http://localhost:8080/page, http://localhost:8080/page",
        "https://very.long.domain.example.com/path, https://very.long.domain.example.com/path",
        "ftp://files.example.com/resource, ftp://files.example.com/resource",
        "custom://app/data, custom://app/data"
    })
    void shouldAcceptVariousUrlSchemes_whenSet(String url, String expected) {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://old.com");
        response.setOriginalUrl(url);
        assertThat(response.getOriginalUrl()).isEqualTo(expected);
    }

    @Test
    void shouldHandleLongUrls_whenSet() {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        String longUrl = "https://example.com/" + "path-segment/".repeat(100) + "?" +
                        "param1=" + "a".repeat(100) + "&" +
                        "param2=" + "b".repeat(100);
        response.setOriginalUrl(longUrl);
        assertThat(response.getOriginalUrl()).hasSize(longUrl.length());
    }

    @Test
    void shouldSerializeToJson_whenValidData() throws JsonProcessingException {
        ShortenResponse response = new ShortenResponse("xyz789", "/s/xyz789", "https://test.com/page");

        String json = objectMapper.writeValueAsString(response);

        assertThat(json).contains("\"code\":\"xyz789\"");
        assertThat(json).contains("\"path\":\"/s/xyz789\"");
        assertThat(json).contains("\"originalUrl\":\"https://test.com/page\"");
    }

    @Test
    void shouldContainAllFieldsInJson_whenSerialized() throws JsonProcessingException {
        ShortenResponse response = new ShortenResponse("def456", "/link/def456", "https://example.com/deserialized");
        String json = objectMapper.writeValueAsString(response);

        assertThat(json).contains("\"code\":\"def456\"");
        assertThat(json).contains("\"path\":\"/link/def456\"");
        assertThat(json).contains("\"originalUrl\":\"https://example.com/deserialized\"");
    }

    @Test
    void shouldSerializeEmptyFields_whenValuesNull() throws JsonProcessingException {
        ShortenResponse response = new ShortenResponse(null, null, null);
        String json = objectMapper.writeValueAsString(response);
        assertThat(json).contains("code");
        assertThat(json).contains("path");
        assertThat(json).contains("originalUrl");
    }

    @Test
    void shouldAllowFieldReassignment_whenMultipleSets() {
        ShortenResponse response = new ShortenResponse("initial", "/initial", "http://initial.com");

        response.setCode("updated");
        assertThat(response.getCode()).isEqualTo("updated");

        response.setPath("/updated");
        assertThat(response.getPath()).isEqualTo("/updated");

        response.setOriginalUrl("http://updated.com");
        assertThat(response.getOriginalUrl()).isEqualTo("http://updated.com");
    }

    @Test
    void shouldAcceptNullValues_whenSetToNull() {
        ShortenResponse response = new ShortenResponse("code", "/path", "http://url.com");

        response.setCode(null);
        assertThat(response.getCode()).isNull();

        response.setPath(null);
        assertThat(response.getPath()).isNull();

        response.setOriginalUrl(null);
        assertThat(response.getOriginalUrl()).isNull();
    }

    @Test
    void shouldHandleSpecialCharactersInUrl_whenSet() {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        String urlWithSpecialChars = "https://example.com/path?query=value&other=test#fragment" +
                                    "&encoded=%20%2F%3D";
        response.setOriginalUrl(urlWithSpecialChars);
        assertThat(response.getOriginalUrl())
            .contains("?")
            .contains("&")
            .contains("#")
            .contains("%");
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "https://example.com/",
        "https://example.com/path",
        "https://example.com/path/",
        "https://example.com/path/to/resource",
        "https://example.com/path?query=1",
        "https://example.com/path#anchor",
        "https://example.com:8080/path",
        "https://user:pass@example.com/path"
    })
    void shouldAcceptVariousUrlFormats_whenSet(String url) {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://old.com");
        response.setOriginalUrl(url);
        assertThat(response.getOriginalUrl()).isEqualTo(url);
    }

    @Test
    void shouldHandleInternationalizedDomain_whenSet() {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        String internationalUrl = "https://münchen.example/über-path";
        response.setOriginalUrl(internationalUrl);
        assertThat(response.getOriginalUrl()).isEqualTo(internationalUrl);
    }

    @Test
    void shouldSerializeCompleteObject_whenAllFieldsSet() throws JsonProcessingException {
        ShortenResponse response = new ShortenResponse("roundtrip", "/s/roundtrip", "https://roundtrip.example.com/test");

        String json = objectMapper.writeValueAsString(response);

        assertThat(json).contains("\"code\":\"roundtrip\"");
        assertThat(json).contains("\"path\":\"/s/roundtrip\"");
        assertThat(json).contains("\"originalUrl\":\"https://roundtrip.example.com/test\"");
    }

    @Test
    void shouldGenerateConsistentCode_whenSameInput() {
        String code = "consistent123";
        ShortenResponse response1 = new ShortenResponse(code, "/s/" + code, "https://example.com");
        ShortenResponse response2 = new ShortenResponse(code, "/s/" + code, "https://example.com");

        assertThat(response1.getCode()).isEqualTo(response2.getCode());
        assertThat(response1.getPath()).isEqualTo(response2.getPath());
    }

    @ParameterizedTest
    @CsvSource({
        "'','',''",
        "'   ','   ','   '",
        "null, /path, http://test.com"
    })
    void shouldHandleEdgeCaseConstructorValues_whenCreated(String code, String path, String url) {
        String actualCode = "null".equals(code) ? null : code;
        String actualPath = "null".equals(path) ? null : path;
        String actualUrl = "null".equals(url) ? null : url;

        ShortenResponse response = new ShortenResponse(actualCode, actualPath, actualUrl);
        assertThat(response.getCode()).isEqualTo(actualCode);
        assertThat(response.getPath()).isEqualTo(actualPath);
        assertThat(response.getOriginalUrl()).isEqualTo(actualUrl);
    }

    @Test
    void shouldAcceptVeryLongPath_whenSet() {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        String longPath = "/" + "segment/".repeat(500);
        response.setPath(longPath);
        assertThat(response.getPath()).hasSize(longPath.length());
    }

    @Test
    void shouldHandleUrlWithQueryParameters_whenSet() {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        String urlWithParams = "https://example.com/search?q=test&category=all&sort=date&page=1&limit=100";
        response.setOriginalUrl(urlWithParams);
        assertThat(response.getOriginalUrl()).isEqualTo(urlWithParams);
    }

    @Test
    void shouldHandleUrlWithFragment_whenSet() {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        String urlWithFragment = "https://example.com/page#section1";
        response.setOriginalUrl(urlWithFragment);
        assertThat(response.getOriginalUrl()).isEqualTo(urlWithFragment);
    }

    @Test
    void shouldAcceptUnicodeCharactersInCode_whenSet() {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        response.setCode("代码-123-émoji-🎉");
        assertThat(response.getCode()).contains("代码").contains("🎉");
    }

    @Test
    void shouldSerializeWithNullFields_whenNotSet() throws JsonProcessingException {
        ShortenResponse response = new ShortenResponse(null, null, null);
        String json = objectMapper.writeValueAsString(response);
        assertThat(json).contains("code").contains("path").contains("originalUrl");
    }

    @Test
    void shouldMaintainCodeFormatConsistency_whenUsedForRedirect() {
        String code = "ABC123xyz";
        String path = "/r/" + code;
        ShortenResponse response = new ShortenResponse(code, path, "https://destination.com/page");

        assertThat(response.getPath()).contains(response.getCode());
    }

    @Test
    void shouldAcceptUrlWithPort_whenSet() {
        ShortenResponse response = new ShortenResponse("c", "/c", "http://example.com");
        String urlWithPort = "http://localhost:3000/api/v1/users?id=123";
        response.setOriginalUrl(urlWithPort);
        assertThat(response.getOriginalUrl()).isEqualTo(urlWithPort);
    }
}
