"""URL 安全校验纯逻辑单元测试 — wave128."""

import pytest

from packages.domain.url_security import (
    ALLOWED_PORTS,
    ALLOWED_SCHEMES,
    MAGIC_NUMBERS,
    MAX_URL_LENGTH,
    UrlSecurityError,
    check_internal_hostname,
    check_ssrf_ip,
    is_ip_address,
    is_trusted_domain,
    is_url_basic_safe,
    validate_magic_number,
    validate_url_basic,
)

# ── 常量校验 ────────────────────────────────────────────────────────────────


class TestConstants:
    def test_allowed_schemes(self):
        assert "http" in ALLOWED_SCHEMES
        assert "https" in ALLOWED_SCHEMES

    def test_allowed_ports(self):
        assert 80 in ALLOWED_PORTS
        assert 443 in ALLOWED_PORTS

    def test_max_url_length(self):
        assert MAX_URL_LENGTH == 2048

    def test_magic_numbers_has_common_formats(self):
        assert "audio/mpeg" in MAGIC_NUMBERS
        assert "image/png" in MAGIC_NUMBERS
        assert "video/mp4" in MAGIC_NUMBERS


# ── 内部主机名检查 ──────────────────────────────────────────────────────────


class TestCheckInternalHostname:
    @pytest.mark.parametrize(
        "hostname",
        [
            "localhost",
            "LOCALHOST",
            "LocalHost",
            "localhost.localdomain",
            "ip6-localhost",
            "ip6-loopback",
            "metadata",
            "metadata.google.internal",
            "169.254.169.254",
        ],
    )
    def test_internal_hostnames_rejected(self, hostname):
        with pytest.raises(UrlSecurityError, match="禁止访问内部主机名"):
            check_internal_hostname(hostname)

    @pytest.mark.parametrize(
        "hostname",
        [
            "foo.local",
            "bar.internal",
            "baz.localdomain",
            "sub.foo.local",
        ],
    )
    def test_internal_domain_suffixes_rejected(self, hostname):
        with pytest.raises(UrlSecurityError, match="禁止访问内网域名"):
            check_internal_hostname(hostname)

    @pytest.mark.parametrize(
        "hostname",
        [
            "example.com",
            "www.google.com",
            "oss-cn-hangzhou.aliyuncs.com",
            "123.45.67.89",
        ],
    )
    def test_normal_hostnames_allowed(self, hostname):
        check_internal_hostname("example.com")  # 不抛异常即通过


# ── 可信域名匹配 ────────────────────────────────────────────────────────────


class TestIsTrustedDomain:
    def test_empty_trusted_always_true(self):
        assert is_trusted_domain("anything.com", set()) is True

    def test_exact_match(self):
        trusted = {"example.com", "foo.bar"}
        assert is_trusted_domain("example.com", trusted) is True
        assert is_trusted_domain("foo.bar", trusted) is True

    def test_exact_no_match(self):
        trusted = {"example.com"}
        assert is_trusted_domain("other.com", trusted) is False

    def test_subdomain_match(self):
        trusted = {"example.com"}
        assert is_trusted_domain("sub.example.com", trusted) is True
        assert is_trusted_domain("a.b.example.com", trusted) is True

    def test_subdomain_partial_no_match(self):
        trusted = {"example.com"}
        # fakeexample.com 不是 example.com 的子域名
        assert is_trusted_domain("fakeexample.com", trusted) is False

    def test_case_insensitive(self):
        trusted = {"Example.COM"}
        assert is_trusted_domain("example.com", trusted) is True
        assert is_trusted_domain("SUB.Example.COM", trusted) is True


# ── IP SSRF 检查 ────────────────────────────────────────────────────────────


class TestCheckSrfIp:
    @pytest.mark.parametrize("ip", ["127.0.0.1", "127.1.2.3", "::1"])
    def test_loopback_rejected(self, ip):
        with pytest.raises(UrlSecurityError, match="回环"):
            check_ssrf_ip(ip)

    @pytest.mark.parametrize(
        "ip",
        [
            "10.0.0.1",
            "10.255.255.255",
            "172.16.0.1",
            "172.31.255.255",
            "192.168.1.1",
            "192.168.0.1",
            "fd00::1",  # IPv6 unique local
        ],
    )
    def test_private_rejected(self, ip):
        with pytest.raises(UrlSecurityError, match="内网"):
            check_ssrf_ip(ip)

    @pytest.mark.parametrize("ip", ["169.254.1.1", "169.254.169.254", "fe80::1"])
    def test_link_local_rejected(self, ip):
        with pytest.raises(UrlSecurityError, match="链路本地"):
            check_ssrf_ip(ip)

    @pytest.mark.parametrize("ip", ["224.0.0.1", "239.255.255.255", "ff00::1"])
    def test_multicast_rejected(self, ip):
        with pytest.raises(UrlSecurityError, match="组播"):
            check_ssrf_ip(ip)

    @pytest.mark.parametrize("ip", ["0.0.0.0", "::"])
    def test_unspecified_rejected(self, ip):
        with pytest.raises(UrlSecurityError, match="未指定"):
            check_ssrf_ip(ip)

    def test_reserved_rejected(self):
        with pytest.raises(UrlSecurityError):
            check_ssrf_ip("240.0.0.1")  # 保留地址段

    @pytest.mark.parametrize(
        "ip",
        [
            "8.8.8.8",
            "1.1.1.1",
            "223.5.5.5",
            "2001:4860:4860::8888",
        ],
    )
    def test_public_ip_allowed(self, ip):
        check_ssrf_ip(ip)  # 不抛异常即通过

    def test_invalid_ip_raises_value_error(self):
        with pytest.raises(ValueError):
            check_ssrf_ip("not-an-ip")


# ── IP 地址判断 ─────────────────────────────────────────────────────────────


class TestIsIpAddress:
    @pytest.mark.parametrize(
        "host",
        [
            "127.0.0.1",
            "8.8.8.8",
            "192.168.1.1",
            "::1",
            "2001:db8::1",
            "fe80::1",
        ],
    )
    def test_ip_addresses(self, host):
        assert is_ip_address(host) is True

    @pytest.mark.parametrize(
        "host",
        [
            "example.com",
            "www.google.com",
            "localhost",
            "not-an-ip",
            "",
        ],
    )
    def test_not_ip_addresses(self, host):
        assert is_ip_address(host) is False


# ── URL 基础校验 ────────────────────────────────────────────────────────────


class TestValidateUrlBasic:
    def test_normal_http_url_passes(self):
        result = validate_url_basic("http://example.com/file.txt")
        assert result == "http://example.com/file.txt"

    def test_normal_https_url_passes(self):
        result = validate_url_basic("https://www.example.com/path?q=1")
        assert result == "https://www.example.com/path?q=1"

    def test_standard_port_80_passes(self):
        validate_url_basic("http://example.com:80/file")

    def test_standard_port_443_passes(self):
        validate_url_basic("https://example.com:443/file")

    def test_empty_url_rejected(self):
        with pytest.raises(UrlSecurityError, match="URL 为空"):
            validate_url_basic("")

    def test_none_url_rejected(self):
        with pytest.raises(UrlSecurityError, match="URL 为空"):
            validate_url_basic(None)  # type: ignore

    def test_too_long_url_rejected(self):
        long_url = "https://example.com/" + "a" * 2100
        with pytest.raises(UrlSecurityError, match="URL 过长"):
            validate_url_basic(long_url)

    @pytest.mark.parametrize(
        "url",
        [
            "ftp://example.com/file",
            "file:///etc/passwd",
            "javascript:alert(1)",
            "data:text/html,<h1>hi</h1>",
        ],
    )
    def test_bad_scheme_rejected(self, url):
        with pytest.raises(UrlSecurityError, match="不允许的 URL scheme"):
            validate_url_basic(url)

    def test_missing_hostname_rejected(self):
        with pytest.raises(UrlSecurityError, match="URL 缺少主机名"):
            validate_url_basic("http:///path")

    @pytest.mark.parametrize(
        "url",
        [
            "http://localhost/test",
            "http://metadata/test",
            "http://foo.local/test",
        ],
    )
    def test_internal_hostname_rejected(self, url):
        with pytest.raises(UrlSecurityError):
            validate_url_basic(url)

    @pytest.mark.parametrize(
        "url",
        [
            "http://example.com:8080/file",
            "http://example.com:22/file",
            "http://example.com:3306/file",
        ],
    )
    def test_non_standard_port_rejected(self, url):
        with pytest.raises(UrlSecurityError, match="不允许的端口"):
            validate_url_basic(url)

    def test_direct_ip_rejected_by_default(self):
        with pytest.raises(UrlSecurityError, match="禁止直接 IP 访问"):
            validate_url_basic("http://8.8.8.8/file")

    def test_direct_ip_allowed_with_flag_public(self):
        result = validate_url_basic("http://8.8.8.8/file", allow_direct_ip=True)
        assert result == "http://8.8.8.8/file"

    def test_direct_ip_allowed_flag_but_private_still_rejected(self):
        with pytest.raises(UrlSecurityError, match="内网地址"):
            validate_url_basic("http://10.0.0.1/file", allow_direct_ip=True)

    def test_direct_ip_loopback_rejected(self):
        with pytest.raises(UrlSecurityError):
            validate_url_basic("http://127.0.0.1/test", allow_direct_ip=True)

    def test_trusted_domains_whitelist_pass(self):
        trusted = {"example.com"}
        result = validate_url_basic("https://example.com/file", trusted_domains=trusted)
        assert result == "https://example.com/file"

    def test_trusted_domains_subdomain_pass(self):
        trusted = {"example.com"}
        result = validate_url_basic("https://cdn.example.com/file", trusted_domains=trusted)
        assert result == "https://cdn.example.com/file"

    def test_trusted_domains_not_in_list_rejected(self):
        trusted = {"example.com"}
        with pytest.raises(UrlSecurityError, match="不在可信白名单"):
            validate_url_basic("https://other.com/file", trusted_domains=trusted)

    def test_case_insensitive_scheme(self):
        # 大写 HTTP 也应该通过（我们用 .lower() 检查）
        result = validate_url_basic("HTTP://example.com/file")
        assert "HTTP://example.com/file" == result


# ── is_url_basic_safe 便捷函数 ──────────────────────────────────────────────


class TestIsUrlBasicSafe:
    def test_safe_url_returns_true(self):
        assert is_url_basic_safe("https://example.com/file") is True

    def test_unsafe_url_returns_false(self):
        assert is_url_basic_safe("http://localhost/test") is False

    def test_empty_returns_false(self):
        assert is_url_basic_safe("") is False

    def test_with_trusted_domains(self):
        trusted = {"allowed.com"}
        assert is_url_basic_safe("https://allowed.com/x", trusted_domains=trusted) is True
        assert is_url_basic_safe("https://other.com/x", trusted_domains=trusted) is False


# ── 魔数校验 ────────────────────────────────────────────────────────────────


class TestValidateMagicNumber:
    def test_mp3_id3_header(self):
        data = b"ID3" + b"\x00" * 100
        validate_magic_number(data, {"audio/mpeg"})  # 不抛异常

    def test_mp3_frame_sync(self):
        data = b"\xff\xfb" + b"\x00" * 100
        validate_magic_number(data, {"audio/mpeg"})

    def test_wav_header(self):
        data = b"RIFF" + b"\x00" * 4 + b"WAVE" + b"\x00" * 100
        validate_magic_number(data, {"audio/wav"})

    def test_png_header(self):
        data = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100
        validate_magic_number(data, {"image/png"})

    def test_jpeg_header(self):
        data = b"\xff\xd8\xff" + b"\x00" * 100
        validate_magic_number(data, {"image/jpeg"})

    def test_gif87a_header(self):
        data = b"GIF87a" + b"\x00" * 100
        validate_magic_number(data, {"image/gif"})

    def test_gif89a_header(self):
        data = b"GIF89a" + b"\x00" * 100
        validate_magic_number(data, {"image/gif"})

    def test_mp4_ftyp_header(self):
        data = b"\x00\x00\x00\x20ftypisom" + b"\x00" * 100
        validate_magic_number(data, {"video/mp4"})

    def test_ogg_header(self):
        data = b"OggS" + b"\x00" * 100
        validate_magic_number(data, {"audio/ogg"})

    def test_flac_header(self):
        data = b"fLaC" + b"\x00" * 100
        validate_magic_number(data, {"audio/flac"})

    def test_webp_header(self):
        data = b"RIFF" + b"\x00" * 4 + b"WEBP" + b"\x00" * 100
        validate_magic_number(data, {"image/webp"})

    def test_bmp_header(self):
        data = b"BM" + b"\x00" * 100
        validate_magic_number(data, {"image/bmp"})

    def test_empty_file_rejected(self):
        with pytest.raises(UrlSecurityError, match="文件为空"):
            validate_magic_number(b"", {"image/png"})

    def test_mismatched_magic_rejected(self):
        data = b"NOTAPNG" + b"\x00" * 100
        with pytest.raises(UrlSecurityError, match="魔数与允许的 MIME 类型不匹配"):
            validate_magic_number(data, {"image/png"})

    def test_multiple_allowed_types_one_matches(self):
        data = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100
        # 多个允许类型，只要有一个匹配就通过
        validate_magic_number(data, {"image/png", "image/jpeg", "image/gif"})

    def test_no_known_magic_skips_validation(self):
        # 自定义 MIME 类型没有已知魔数，跳过校验不阻断
        validate_magic_number(b"random data", {"application/x-custom"})

    def test_too_short_header_no_match(self):
        # 文件头太短，无法匹配需要 8 字节偏移的格式
        data = b"RIFF"  # 只有 4 字节，不够 offset 8 的 WAVE 匹配
        with pytest.raises(UrlSecurityError, match="魔数"):
            validate_magic_number(data, {"audio/wav"})

    def test_error_message_contains_mime_and_header(self):
        with pytest.raises(UrlSecurityError) as exc_info:
            validate_magic_number(b"XXXXYYY", {"image/png"})
        msg = str(exc_info.value)
        assert "image/png" in msg
        assert "文件头前16字节" in msg
