"""Tests for outbound SSRF prevention (F9)."""

from __future__ import annotations

import ipaddress
import os
from unittest.mock import patch


from qremotex.connection.ip_allowlist import (
    OutboundBlocklistConfig,
    _parse_networks,
    validate_outbound_target,
)


# ──────────────────────────────────────────────── Config ──


class TestOutboundBlocklistConfig:
    def test_default_blocks_metadata(self) -> None:
        with patch.dict(os.environ, {}, clear=False):
            os.environ.pop("QRAPTOR_BLOCKED_OUTBOUND_CIDRS", None)
            config = OutboundBlocklistConfig()
            assert config.enabled is True
            # Should include 169.254.169.254/32 by default
            assert len(config.blocked_networks) >= 1

    def test_custom_blocklist(self) -> None:
        with patch.dict(os.environ, {
            "QRAPTOR_BLOCKED_OUTBOUND_CIDRS": "10.0.0.0/8,172.16.0.0/12",
        }):
            config = OutboundBlocklistConfig()
            assert len(config.blocked_networks) == 2

    def test_empty_blocklist_disables(self) -> None:
        with patch.dict(os.environ, {"QRAPTOR_BLOCKED_OUTBOUND_CIDRS": ""}):
            config = OutboundBlocklistConfig()
            assert config.enabled is False


# ──────────────────────────────────────────────── Parse Networks ──


class TestParseNetworks:
    def test_single_ip(self) -> None:
        result = _parse_networks("10.0.0.1")
        assert len(result) == 1
        assert ipaddress.ip_address("10.0.0.1") in result[0]

    def test_cidr(self) -> None:
        result = _parse_networks("192.168.1.0/24")
        assert len(result) == 1
        assert ipaddress.ip_address("192.168.1.50") in result[0]

    def test_multiple(self) -> None:
        result = _parse_networks("10.0.0.1, 192.168.0.0/16, 172.16.0.0/12")
        assert len(result) == 3

    def test_ipv6(self) -> None:
        result = _parse_networks("::1, fd00::/8")
        assert len(result) == 2

    def test_invalid_entry_skipped(self) -> None:
        result = _parse_networks("10.0.0.1, not-an-ip, 192.168.1.0/24")
        assert len(result) == 2

    def test_empty_string(self) -> None:
        result = _parse_networks("")
        assert len(result) == 0

    def test_whitespace_handling(self) -> None:
        result = _parse_networks("  10.0.0.1  ,  10.0.0.2  ")
        assert len(result) == 2


# ──────────────────────────────────────────────── Outbound Target Validation ──


class TestValidateOutboundTarget:
    def test_disabled_allows_all(self) -> None:
        with patch.dict(os.environ, {"QRAPTOR_BLOCKED_OUTBOUND_CIDRS": ""}):
            config = OutboundBlocklistConfig()
            assert validate_outbound_target("https://api.example.com/data", config) is True

    def test_metadata_ip_blocked(self) -> None:
        with patch.dict(os.environ, {}, clear=False):
            os.environ.pop("QRAPTOR_BLOCKED_OUTBOUND_CIDRS", None)
            config = OutboundBlocklistConfig()
            assert validate_outbound_target("http://169.254.169.254/latest/meta-data", config) is False

    def test_safe_target_allowed(self) -> None:
        with patch.dict(os.environ, {
            "QRAPTOR_BLOCKED_OUTBOUND_CIDRS": "169.254.169.254/32",
        }):
            config = OutboundBlocklistConfig()
            assert validate_outbound_target("http://127.0.0.1:8080/api", config) is True

    def test_blocked_cidr(self) -> None:
        with patch.dict(os.environ, {
            "QRAPTOR_BLOCKED_OUTBOUND_CIDRS": "127.0.0.0/8",
        }):
            config = OutboundBlocklistConfig()
            assert validate_outbound_target("http://127.0.0.1:8080/api", config) is False

    def test_bad_url_rejected(self) -> None:
        with patch.dict(os.environ, {
            "QRAPTOR_BLOCKED_OUTBOUND_CIDRS": "10.0.0.0/8",
        }):
            config = OutboundBlocklistConfig()
            assert validate_outbound_target("not-a-url", config) is False
