"""Tests for ledgrab.utils.url_scheme.infer_http_scheme."""

import pytest

from ledgrab.utils.url_scheme import infer_http_scheme


@pytest.mark.parametrize(
    "url",
    [
        "http://192.168.1.10",
        "https://wled.example.com",
        "HTTP://wled.local",
        "HTTPS://example.com/api",
        "ws://device.local",
        "openrgb://localhost:6742/0",
    ],
)
def test_preserves_existing_scheme(url):
    assert infer_http_scheme(url) == url


@pytest.mark.parametrize(
    "raw",
    [
        "192.168.1.10",
        "192.168.1.10:8080",
        "10.0.0.5",
        "172.16.1.1",
        "127.0.0.1",
        "localhost",
        "localhost:8080",
        "wled-desk.local",
        "wled-desk.local:80",
        "wled",  # bare label ⇒ mDNS-style
        "kitchen-strip",
        "[::1]",
        "[fe80::1]:80",
        "fe80::1",  # bracketless link-local IPv6
        "device.lan",
        "rack.home",
        "service.internal",
    ],
)
def test_local_targets_get_http(raw):
    assert infer_http_scheme(raw) == f"http://{raw}"


@pytest.mark.parametrize(
    "raw",
    [
        "example.com",
        "wled.example.com",
        "wled.example.com:443",
        "wled.example.com/api",
        "1.2.3.4",  # public IPv4
        "8.8.8.8:80",
        "my-host.io/path?x=1",
    ],
)
def test_external_targets_get_https(raw):
    assert infer_http_scheme(raw) == f"https://{raw}"


def test_trims_whitespace_before_inference():
    assert infer_http_scheme("  192.168.0.1  ") == "http://192.168.0.1"
    assert infer_http_scheme("  example.com  ") == "https://example.com"


def test_empty_string_returns_unchanged():
    assert infer_http_scheme("") == ""


def test_none_returns_unchanged():
    # Callers occasionally hand us None; preserve it so the validator can complain.
    assert infer_http_scheme(None) is None


def test_whitespace_only_collapses_to_empty():
    # Whitespace alone has no host to infer a scheme for — trim and bail out.
    assert infer_http_scheme("   ") == ""


# ---------------------------------------------------------------------------
# Malicious / hostile inputs — must round-trip *unchanged* (no scheme
# coerced onto them) so the downstream validator surfaces a clean error
# rather than letting a coerced scheme slip past as a "valid" URL.
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    "raw",
    [
        "javascript:alert(1)",  # already has a scheme — must pass through
        "data:text/html,<script>",
        "file:///etc/passwd",
        "gopher://internal/path",
        "ftp://files.example.com",
    ],
)
def test_non_http_schemes_pass_through_untouched(raw):
    """A scheme other than http/https is the caller's problem to validate."""
    assert infer_http_scheme(raw) == raw


@pytest.mark.parametrize(
    "raw",
    [
        "evil.com@192.168.1.1",  # bare-host userinfo trick
        "evil.com@192.168.1.1/path",
        "wled\x00.local",  # NUL byte
        "wled\r\nHost: x",  # CRLF injection inside host
        "wled\x07.local",  # BEL
        "host\x1f.local",  # US (control char)
        "host\x7f.local",  # DEL
    ],
)
def test_forbidden_characters_leave_input_unchanged(raw):
    """Inputs containing ``@`` or control chars must NOT receive a scheme.

    Returning them unchanged forces the downstream validator (httpx /
    WLED provider) to reject them with a precise error rather than us
    silently prefixing ``http://``/``https://`` and producing a request
    against an attacker-controlled target.
    """
    # The result should NOT carry an inferred http(s):// prefix.
    result = infer_http_scheme(raw)
    assert not result.startswith(("http://", "https://"))


def test_userinfo_does_not_get_https():
    """``evil.com@192.168.1.1`` must not be coerced to ``https://`` (which
    would send credentials ``evil.com`` to ``192.168.1.1``)."""
    assert "://" not in infer_http_scheme("evil.com@192.168.1.1")
