"""
SSRF Validator Tests

Tests for the SSRF (Server-Side Request Forgery) protection that validates URLs
before making outgoing HTTP requests.

Security model:
- By default, block all private/internal IPs (RFC1918, localhost, link-local, CGNAT)
- allow_localhost=True: Allow only loopback addresses (127.x.x.x, ::1)
- allow_private_ips=True: Allow all private/internal IPs + localhost:
  - RFC1918: 10.x.x.x, 172.16-31.x.x, 192.168.x.x
  - CGNAT: 100.64.x.x (used by Podman/rootless containers)
  - Link-local: 169.254.x.x (except AWS metadata)
  - IPv6 ULA: fc00::/7
  - IPv6 Link-local: fe80::/10
- AWS metadata endpoint (169.254.169.254) is ALWAYS blocked

The allow_private_ips parameter is designed for trusted self-hosted services like
SearXNG or Ollama that may be running in containerized environments (Docker, Podman)
or on a different machine on the local network.
"""

import pytest
from unittest.mock import patch

from tests.test_utils import add_src_to_path

add_src_to_path()


class TestIsIpBlocked:
    """Test the is_ip_blocked function."""

    def test_localhost_blocked_by_default(self):
        """Localhost should be blocked by default."""
        from local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("127.0.0.1") is True
        assert is_ip_blocked("127.0.0.2") is True

    def test_localhost_allowed_with_allow_localhost(self):
        """Localhost should be allowed with allow_localhost=True."""
        from local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("127.0.0.1", allow_localhost=True) is False
        assert is_ip_blocked("127.0.0.2", allow_localhost=True) is False

    def test_private_ip_blocked_with_allow_localhost(self):
        """Private IPs should still be blocked with allow_localhost=True."""
        from local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("192.168.1.100", allow_localhost=True) is True
        assert is_ip_blocked("10.0.0.5", allow_localhost=True) is True
        assert is_ip_blocked("172.16.0.1", allow_localhost=True) is True

    def test_private_ip_allowed_with_allow_private_ips(self):
        """Private IPs should be allowed with allow_private_ips=True."""
        from local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        # 192.168.x.x range
        assert is_ip_blocked("192.168.1.100", allow_private_ips=True) is False
        assert is_ip_blocked("192.168.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("192.168.255.255", allow_private_ips=True) is False

        # 10.x.x.x range
        assert is_ip_blocked("10.0.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("10.255.255.255", allow_private_ips=True) is False

        # 172.16-31.x.x range
        assert is_ip_blocked("172.16.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("172.31.255.255", allow_private_ips=True) is False

    def test_localhost_also_allowed_with_allow_private_ips(self):
        """Localhost should also be allowed with allow_private_ips=True."""
        from local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("127.0.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("127.0.0.2", allow_private_ips=True) is False

    def test_aws_metadata_always_blocked(self):
        """AWS metadata endpoint should ALWAYS be blocked, even with allow_private_ips."""
        from local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        # Without any allowlist
        assert is_ip_blocked("169.254.169.254") is True

        # With allow_localhost
        assert is_ip_blocked("169.254.169.254", allow_localhost=True) is True

        # With allow_private_ips - CRITICAL: Must still be blocked!
        assert is_ip_blocked("169.254.169.254", allow_private_ips=True) is True

    def test_public_ip_not_blocked(self):
        """Public IPs should not be blocked."""
        from local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("8.8.8.8") is False
        assert is_ip_blocked("1.1.1.1") is False
        assert is_ip_blocked("142.250.185.206") is False  # google.com

    def test_link_local_blocked(self):
        """Link-local addresses should be blocked."""
        from local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("169.254.1.1") is True
        assert is_ip_blocked("169.254.100.100") is True

    def test_cgnat_blocked_by_default(self):
        """CGNAT addresses (100.64.x.x) should be blocked by default."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("100.64.0.1") is True
        assert is_ip_blocked("100.100.100.100") is True
        assert is_ip_blocked("100.127.255.255") is True

    def test_cgnat_allowed_with_allow_private_ips(self):
        """CGNAT addresses (100.64.x.x) should be allowed with allow_private_ips=True."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        # CGNAT range used by Podman rootless containers
        assert is_ip_blocked("100.64.0.1", allow_private_ips=True) is False
        assert is_ip_blocked("100.100.100.100", allow_private_ips=True) is False
        assert is_ip_blocked("100.127.255.255", allow_private_ips=True) is False

    def test_link_local_allowed_with_allow_private_ips(self):
        """Link-local addresses (169.254.x.x) should be allowed with allow_private_ips=True."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        # Non-AWS-metadata link-local addresses should be allowed
        assert is_ip_blocked("169.254.1.1", allow_private_ips=True) is False
        assert is_ip_blocked("169.254.100.100", allow_private_ips=True) is False
        # AWS metadata endpoint MUST still be blocked
        assert is_ip_blocked("169.254.169.254", allow_private_ips=True) is True

    def test_ipv6_ula_blocked_by_default(self):
        """IPv6 Unique Local Addresses (fc00::/7) should be blocked by default."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("fc00::1") is True
        assert is_ip_blocked("fd00::1") is True

    def test_ipv6_ula_allowed_with_allow_private_ips(self):
        """IPv6 ULA (fc00::/7) should be allowed with allow_private_ips=True."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("fc00::1", allow_private_ips=True) is False
        assert is_ip_blocked("fd00::1", allow_private_ips=True) is False

    def test_ipv6_link_local_blocked_by_default(self):
        """IPv6 link-local addresses (fe80::/10) should be blocked by default."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("fe80::1") is True
        assert is_ip_blocked("fe80::1234:5678") is True

    def test_ipv6_link_local_allowed_with_allow_private_ips(self):
        """IPv6 link-local (fe80::/10) should be allowed with allow_private_ips=True."""
        from src.local_deep_research.security.ssrf_validator import (
            is_ip_blocked,
        )

        assert is_ip_blocked("fe80::1", allow_private_ips=True) is False
        assert is_ip_blocked("fe80::1234:5678", allow_private_ips=True) is False


class TestValidateUrl:
    """Test the validate_url function."""

    def test_public_url_allowed(self):
        """Public URLs should be allowed."""
        from local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # Mock DNS resolution to return a public IP
            mock_getaddrinfo.return_value = [
                (2, 1, 6, "", ("142.250.185.206", 0))
            ]
            assert validate_url("https://google.com") is True

    def test_localhost_url_blocked_by_default(self):
        """Localhost URLs should be blocked by default."""
        from local_deep_research.security.ssrf_validator import validate_url

        assert validate_url("http://127.0.0.1:8080") is False
        assert validate_url("http://localhost:8080") is False

    def test_localhost_url_allowed_with_allow_localhost(self):
        """Localhost URLs should be allowed with allow_localhost=True."""
        from local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("127.0.0.1", 0))]
            assert (
                validate_url("http://localhost:8080", allow_localhost=True)
                is True
            )

        assert (
            validate_url("http://127.0.0.1:8080", allow_localhost=True) is True
        )

    def test_private_ip_url_blocked_with_allow_localhost(self):
        """Private IP URLs should still be blocked with allow_localhost=True."""
        from local_deep_research.security.ssrf_validator import validate_url

        assert (
            validate_url("http://192.168.1.100:8080", allow_localhost=True)
            is False
        )
        assert (
            validate_url("http://10.0.0.5:8080", allow_localhost=True) is False
        )

    def test_private_ip_url_allowed_with_allow_private_ips(self):
        """Private IP URLs should be allowed with allow_private_ips=True."""
        from local_deep_research.security.ssrf_validator import validate_url

        # 192.168.x.x - typical home network
        assert (
            validate_url("http://192.168.1.100:8080", allow_private_ips=True)
            is True
        )
        assert (
            validate_url("http://192.168.0.1:80", allow_private_ips=True)
            is True
        )

        # 10.x.x.x - typical corporate network
        assert (
            validate_url("http://10.0.0.5:8080", allow_private_ips=True) is True
        )
        assert (
            validate_url("http://10.10.10.10:3000", allow_private_ips=True)
            is True
        )

        # 172.16-31.x.x - Docker default network etc.
        assert (
            validate_url("http://172.16.0.1:8080", allow_private_ips=True)
            is True
        )
        assert (
            validate_url("http://172.20.0.2:5000", allow_private_ips=True)
            is True
        )

    def test_aws_metadata_url_always_blocked(self):
        """AWS metadata URL should ALWAYS be blocked."""
        from local_deep_research.security.ssrf_validator import validate_url

        aws_metadata_url = "http://169.254.169.254/latest/meta-data"

        # Without any allowlist
        assert validate_url(aws_metadata_url) is False

        # With allow_localhost
        assert validate_url(aws_metadata_url, allow_localhost=True) is False

        # With allow_private_ips - CRITICAL: Must still be blocked!
        assert validate_url(aws_metadata_url, allow_private_ips=True) is False

    def test_invalid_scheme_blocked(self):
        """Invalid schemes should be blocked."""
        from local_deep_research.security.ssrf_validator import validate_url

        assert validate_url("ftp://example.com") is False
        assert validate_url("file:///etc/passwd") is False
        assert validate_url("javascript:alert(1)") is False

    def test_hostname_resolving_to_private_ip_blocked(self):
        """Hostnames that resolve to private IPs should be blocked."""
        from local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # Simulate a hostname resolving to a private IP (DNS rebinding attack)
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("192.168.1.1", 0))]
            assert validate_url("http://evil.com") is False

    def test_hostname_resolving_to_private_ip_allowed_with_allow_private_ips(
        self,
    ):
        """Hostnames resolving to private IPs allowed with allow_private_ips=True."""
        from local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            mock_getaddrinfo.return_value = [
                (2, 1, 6, "", ("192.168.1.100", 0))
            ]
            assert (
                validate_url("http://my-searxng.local", allow_private_ips=True)
                is True
            )


class TestSearXNGUseCase:
    """Test the specific SearXNG use case that motivated allow_private_ips."""

    def test_searxng_on_localhost(self):
        """SearXNG on localhost should work with allow_private_ips."""
        from local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("127.0.0.1", 0))]
            assert (
                validate_url("http://localhost:8080", allow_private_ips=True)
                is True
            )

        assert (
            validate_url("http://127.0.0.1:8080", allow_private_ips=True)
            is True
        )

    def test_searxng_on_lan(self):
        """SearXNG on LAN should work with allow_private_ips."""
        from local_deep_research.security.ssrf_validator import validate_url

        # Home network
        assert (
            validate_url("http://192.168.1.100:8080", allow_private_ips=True)
            is True
        )

        # NAS or server on network
        assert (
            validate_url("http://10.0.0.50:8888", allow_private_ips=True)
            is True
        )

        # Docker network
        assert (
            validate_url("http://172.17.0.2:8080", allow_private_ips=True)
            is True
        )

    def test_searxng_hostname_on_lan(self):
        """SearXNG with hostname on LAN should work with allow_private_ips."""
        from local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # Simulate local DNS or /etc/hosts entry
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("192.168.1.50", 0))]
            assert (
                validate_url(
                    "http://searxng.local:8080", allow_private_ips=True
                )
                is True
            )


class TestContainerNetworking:
    """Test container networking scenarios (Podman, Docker, etc.)."""

    def test_podman_host_containers_internal(self):
        """Podman's host.containers.internal (resolves to CGNAT) should work with allow_private_ips."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # Podman rootless containers typically resolve host.containers.internal to 100.64.x.x
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("100.64.1.1", 0))]
            assert (
                validate_url(
                    "http://host.containers.internal:11434",
                    allow_private_ips=True,
                )
                is True
            )

    def test_ollama_in_podman(self):
        """Ollama running on host accessible via Podman's CGNAT should work."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # Ollama on host via Podman CGNAT
            mock_getaddrinfo.return_value = [
                (2, 1, 6, "", ("100.100.100.100", 0))
            ]
            assert (
                validate_url(
                    "http://host.containers.internal:11434/api/generate",
                    allow_private_ips=True,
                )
                is True
            )

    def test_searxng_in_podman(self):
        """SearXNG running on host accessible via Podman's CGNAT should work."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            # SearXNG on host via Podman CGNAT
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("100.64.0.1", 0))]
            assert (
                validate_url(
                    "http://host.containers.internal:8080/search",
                    allow_private_ips=True,
                )
                is True
            )

    def test_cgnat_url_blocked_by_default(self):
        """CGNAT URLs should be blocked by default (without allow_private_ips)."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        # Direct CGNAT IP
        assert validate_url("http://100.64.0.1:8080") is False

        with patch("socket.getaddrinfo") as mock_getaddrinfo:
            mock_getaddrinfo.return_value = [(2, 1, 6, "", ("100.64.1.1", 0))]
            assert (
                validate_url("http://host.containers.internal:11434") is False
            )

    def test_docker_bridge_network(self):
        """Docker bridge network IPs should work with allow_private_ips."""
        from src.local_deep_research.security.ssrf_validator import validate_url

        # Docker typically uses 172.17.x.x for bridge network
        assert (
            validate_url("http://172.17.0.2:8080", allow_private_ips=True)
            is True
        )


class TestDocumentation:
    """Documentation tests explaining the security model."""

    @pytest.mark.skip(reason="documentation/placeholder test - not implemented")
    def test_security_model_documentation(self):
        """
        Document the SSRF protection security model.

        WHY THIS EXISTS:
        - SSRF attacks can be used to access internal services
        - Attackers can steal credentials from cloud metadata endpoints
        - Internal services often have weaker security (no auth required)

        THE allow_private_ips PARAMETER:
        - Designed for trusted self-hosted services like SearXNG, Ollama
        - Service URLs are admin-configured, not arbitrary user input
        - Users intentionally run services on their local network or in containers
        - This is NOT the classic SSRF vector (user submits URL)
        - Covers RFC1918, CGNAT (Podman), link-local, IPv6 private ranges

        CRITICAL: AWS METADATA IS ALWAYS BLOCKED:
        - 169.254.169.254 is the #1 SSRF target for credential theft
        - Even with allow_private_ips=True, this endpoint is blocked
        - This protects against credential theft in cloud environments

        SECURITY MODEL:
        | Parameter           | Localhost | RFC1918 | CGNAT (100.64.x) | Link-local | IPv6 Private | AWS Metadata |
        |---------------------|-----------|---------|------------------|------------|--------------|--------------|
        | (default)           | Blocked   | Blocked | Blocked          | Blocked    | Blocked      | Blocked      |
        | allow_localhost     | Allowed   | Blocked | Blocked          | Blocked    | Blocked      | Blocked      |
        | allow_private_ips   | Allowed   | Allowed | Allowed          | Allowed    | Allowed      | BLOCKED      |
        """
        assert True  # Documentation test
