package com.mosquito.project.web;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

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

@DisplayName("UrlValidator 测试")
class UrlValidatorTest {

    private UrlValidator urlValidator;

    @BeforeEach
    void setUp() {
        urlValidator = new UrlValidator();
    }

    @Test
    @DisplayName("应该拒绝null URL")
    void shouldRejectNullUrl() {
        assertThat(urlValidator.isAllowedUrl(null)).isFalse();
    }

    @Test
    @DisplayName("应该拒绝空白URL")
    void shouldRejectBlankUrl() {
        assertThat(urlValidator.isAllowedUrl("")).isFalse();
        assertThat(urlValidator.isAllowedUrl("   ")).isFalse();
    }

    @Test
    @DisplayName("应该拒绝相对URL")
    void shouldRejectRelativeUrl() {
        assertThat(urlValidator.isAllowedUrl("/path/to/resource")).isFalse();
        assertThat(urlValidator.isAllowedUrl("path/to/resource")).isFalse();
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "ftp://example.com",
        "file:///etc/passwd",
        "javascript:alert(1)",
        "data:text/html,<script>alert(1)</script>"
    })
    @DisplayName("应该拒绝不允许的协议")
    void shouldRejectDisallowedSchemes(String url) {
        assertThat(urlValidator.isAllowedUrl(url)).isFalse();
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "http://localhost",
        "http://127.0.0.1",
        "http://::1",
        "http://0.0.0.0",
        "https://localhost:8080"
    })
    @DisplayName("应该拒绝localhost地址")
    void shouldRejectLocalhostAddresses(String url) {
        assertThat(urlValidator.isAllowedUrl(url)).isFalse();
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "http://10.0.0.1",
        "http://10.255.255.255",
        "http://172.16.0.1",
        "http://172.31.255.255",
        "http://192.168.0.1",
        "http://192.168.255.255"
    })
    @DisplayName("应该拒绝私有IP地址")
    void shouldRejectPrivateIpAddresses(String url) {
        assertThat(urlValidator.isAllowedUrl(url)).isFalse();
    }

    @Test
    @DisplayName("应该接受有效的公网URL - google.com")
    void shouldAcceptValidPublicUrls() {
        // 使用真实存在的公网域名进行测试
        assertThat(urlValidator.isAllowedUrl("https://www.google.com")).isTrue();
        assertThat(urlValidator.isAllowedUrl("https://github.com")).isTrue();
        assertThat(urlValidator.isAllowedUrl("http://www.baidu.com")).isTrue();
    }

    @Test
    @DisplayName("应该拒绝无效的URL语法")
    void shouldRejectInvalidUrlSyntax() {
        assertThat(urlValidator.isAllowedUrl("not a url")).isFalse();
        assertThat(urlValidator.isAllowedUrl("http://")).isFalse();
        assertThat(urlValidator.isAllowedUrl("://example.com")).isFalse();
    }

    @Test
    @DisplayName("sanitizeUrl应该返回有效URL的字符串形式")
    void shouldSanitizeValidUrl() {
        String url = "https://example.com/path";
        String sanitized = urlValidator.sanitizeUrl(url);
        assertThat(sanitized).isNotNull();
        assertThat(sanitized).contains("example.com");
    }

    @Test
    @DisplayName("sanitizeUrl应该对无效URL返回null")
    void shouldReturnNullForInvalidUrl() {
        assertThat(urlValidator.sanitizeUrl(null)).isNull();
        assertThat(urlValidator.sanitizeUrl("")).isNull();
        assertThat(urlValidator.sanitizeUrl("http://localhost")).isNull();
        assertThat(urlValidator.sanitizeUrl("not a url")).isNull();
    }

    @Test
    @DisplayName("应该处理URL中的大小写")
    void shouldHandleUrlCaseInsensitivity() {
        assertThat(urlValidator.isAllowedUrl("HTTP://EXAMPLE.COM")).isTrue();
        assertThat(urlValidator.isAllowedUrl("HTTPS://EXAMPLE.COM")).isTrue();
    }

    @Test
    @DisplayName("应该拒绝空主机名")
    void shouldRejectEmptyHost() {
        assertThat(urlValidator.isAllowedUrl("http://")).isFalse();
    }

    @ParameterizedTest
    @ValueSource(strings = {
        "http://169.254.0.1",  // Link-local
        "http://224.0.0.1"     // Multicast
    })
    @DisplayName("应该拒绝特殊用途的IP地址")
    void shouldRejectSpecialPurposeIpAddresses(String url) {
        assertThat(urlValidator.isAllowedUrl(url)).isFalse();
    }

    @Test
    @DisplayName("应该处理带端口的URL")
    void shouldHandleUrlsWithPorts() {
        assertThat(urlValidator.isAllowedUrl("https://example.com:443")).isTrue();
        assertThat(urlValidator.isAllowedUrl("http://example.com:80")).isTrue();
        assertThat(urlValidator.isAllowedUrl("https://example.com:8443")).isTrue();
    }

    @Test
    @DisplayName("应该处理带查询参数的URL")
    void shouldHandleUrlsWithQueryParameters() {
        assertThat(urlValidator.isAllowedUrl("https://example.com/path?key=value&foo=bar")).isTrue();
    }

    @Test
    @DisplayName("应该处理带片段的URL")
    void shouldHandleUrlsWithFragments() {
        assertThat(urlValidator.isAllowedUrl("https://example.com/path#section")).isTrue();
    }

    @Test
    @DisplayName("应该拒绝IPv6 loopback地址")
    void shouldRejectIpv6LoopbackAddress() {
        assertThat(urlValidator.isAllowedUrl("http://[::1]")).isFalse();
        assertThat(urlValidator.isAllowedUrl("http://[0:0:0:0:0:0:0:1]")).isFalse();
    }

    @Test
    @DisplayName("应该接受IPv6公网地址")
    void shouldAcceptPublicIpv6Address() {
        // 使用Google的公网IPv6地址
        assertThat(urlValidator.isAllowedUrl("http://[2001:4860:4860::8888]")).isTrue();
    }

    @Test
    @DisplayName("应该拒绝0.0.0.0地址")
    void shouldRejectZeroAddress() {
        assertThat(urlValidator.isAllowedUrl("http://0.0.0.0")).isFalse();
        assertThat(urlValidator.isAllowedUrl("http://0.0.0.0:8080")).isFalse();
    }

    @Test
    @DisplayName("应该处理无效主机名")
    void shouldHandleInvalidHostname() {
        assertThat(urlValidator.isAllowedUrl("http://invalid..hostname")).isFalse();
        assertThat(urlValidator.isAllowedUrl("http://-invalid-hostname")).isFalse();
    }

    @Test
    @DisplayName("sanitizeUrl应该处理URISyntaxException")
    void shouldHandleUriSyntaxExceptionInSanitize() {
        assertThat(urlValidator.sanitizeUrl("http://invalid url with spaces")).isNull();
    }
}
