"""Tests for notification_validator module - notification service URL validation."""

import pytest

from local_deep_research.security.notification_validator import (
    NotificationURLValidationError,
    NotificationURLValidator,
)


class TestNotificationURLValidationError:
    """Tests for NotificationURLValidationError exception."""

    def test_inherits_from_value_error(self):
        """Should inherit from ValueError."""
        assert issubclass(NotificationURLValidationError, ValueError)

    def test_can_be_raised_with_message(self):
        """Should be raisable with a message."""
        with pytest.raises(
            NotificationURLValidationError, match="test message"
        ):
            raise NotificationURLValidationError("test message")


class TestIsPrivateIP:
    """Tests for _is_private_ip static method."""

    def test_localhost_string(self):
        """Should detect 'localhost' as private."""
        assert NotificationURLValidator._is_private_ip("localhost") is True

    def test_localhost_uppercase(self):
        """Should detect 'LOCALHOST' as private (case-insensitive)."""
        assert NotificationURLValidator._is_private_ip("LOCALHOST") is True

    def test_loopback_ipv4(self):
        """Should detect 127.0.0.1 as private."""
        assert NotificationURLValidator._is_private_ip("127.0.0.1") is True

    def test_loopback_ipv6(self):
        """Should detect ::1 as private."""
        assert NotificationURLValidator._is_private_ip("::1") is True

    def test_all_zeros_ipv4(self):
        """Should detect 0.0.0.0 as private."""
        assert NotificationURLValidator._is_private_ip("0.0.0.0") is True

    def test_all_zeros_ipv6(self):
        """Should detect :: as private."""
        assert NotificationURLValidator._is_private_ip("::") is True

    def test_private_10_range(self):
        """Should detect 10.x.x.x as private."""
        assert NotificationURLValidator._is_private_ip("10.0.0.1") is True
        assert NotificationURLValidator._is_private_ip("10.255.255.255") is True

    def test_private_172_range(self):
        """Should detect 172.16-31.x.x as private."""
        assert NotificationURLValidator._is_private_ip("172.16.0.1") is True
        assert NotificationURLValidator._is_private_ip("172.31.255.255") is True

    def test_private_192_range(self):
        """Should detect 192.168.x.x as private."""
        assert NotificationURLValidator._is_private_ip("192.168.0.1") is True
        assert (
            NotificationURLValidator._is_private_ip("192.168.255.255") is True
        )

    def test_link_local_ipv4(self):
        """Should detect link-local 169.254.x.x as private."""
        assert NotificationURLValidator._is_private_ip("169.254.1.1") is True

    def test_public_ipv4(self):
        """Should not detect public IPs as private."""
        assert NotificationURLValidator._is_private_ip("8.8.8.8") is False
        assert NotificationURLValidator._is_private_ip("1.1.1.1") is False
        assert NotificationURLValidator._is_private_ip("93.184.216.34") is False

    def test_hostname_not_resolved(self):
        """Should return False for hostnames (not resolved for security)."""
        assert NotificationURLValidator._is_private_ip("example.com") is False
        assert NotificationURLValidator._is_private_ip("internal.corp") is False


class TestValidateServiceUrl:
    """Tests for validate_service_url static method."""

    def test_empty_url_rejected(self):
        """Should reject empty URLs."""
        is_valid, error = NotificationURLValidator.validate_service_url("")
        assert is_valid is False
        assert "non-empty string" in error

    def test_none_url_rejected(self):
        """Should reject None URLs."""
        is_valid, error = NotificationURLValidator.validate_service_url(None)
        assert is_valid is False
        assert "non-empty string" in error

    def test_non_string_url_rejected(self):
        """Should reject non-string URLs."""
        is_valid, error = NotificationURLValidator.validate_service_url(123)
        assert is_valid is False
        assert "non-empty string" in error

    def test_url_without_scheme_rejected(self):
        """Should reject URLs without protocol scheme."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "example.com/webhook"
        )
        assert is_valid is False
        assert "must have a protocol" in error

    def test_file_scheme_blocked(self):
        """Should block file:// scheme."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "file:///etc/passwd"
        )
        assert is_valid is False
        assert "Blocked unsafe protocol" in error
        assert "file" in error

    def test_ftp_scheme_blocked(self):
        """Should block ftp:// scheme."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "ftp://ftp.example.com"
        )
        assert is_valid is False
        assert "Blocked unsafe protocol" in error

    def test_javascript_scheme_blocked(self):
        """Should block javascript: scheme."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "javascript:alert(1)"
        )
        assert is_valid is False
        assert "Blocked unsafe protocol" in error

    def test_data_scheme_blocked(self):
        """Should block data: scheme."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "data:text/html,<script>alert(1)</script>"
        )
        assert is_valid is False
        assert "Blocked unsafe protocol" in error

    def test_unknown_scheme_rejected(self):
        """Should reject unknown/unsupported schemes."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "custom://example.com"
        )
        assert is_valid is False
        assert "Unsupported protocol" in error

    def test_https_valid(self):
        """Should accept https:// URLs to public hosts."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "https://webhook.example.com/notify"
        )
        assert is_valid is True
        assert error is None

    def test_http_valid(self):
        """Should accept http:// URLs to public hosts."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "http://webhook.example.com/notify"
        )
        assert is_valid is True
        assert error is None

    def test_discord_scheme_valid(self):
        """Should accept discord:// URLs."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "discord://webhook_id/webhook_token"
        )
        assert is_valid is True
        assert error is None

    def test_slack_scheme_valid(self):
        """Should accept slack:// URLs."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "slack://token_a/token_b/token_c"
        )
        assert is_valid is True
        assert error is None

    def test_telegram_scheme_valid(self):
        """Should accept telegram:// URLs."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "telegram://bot_token/chat_id"
        )
        assert is_valid is True
        assert error is None

    def test_mailto_scheme_valid(self):
        """Should accept mailto: URLs."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "mailto://user@example.com"
        )
        assert is_valid is True
        assert error is None

    def test_ntfy_scheme_valid(self):
        """Should accept ntfy:// URLs."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "ntfy://topic"
        )
        assert is_valid is True
        assert error is None

    def test_http_localhost_blocked(self):
        """Should block http://localhost by default."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "http://localhost:5000/webhook"
        )
        assert is_valid is False
        assert "Blocked private/internal IP" in error

    def test_http_127_blocked(self):
        """Should block http://127.0.0.1 by default."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "http://127.0.0.1/webhook"
        )
        assert is_valid is False
        assert "Blocked private/internal IP" in error

    def test_http_private_ip_blocked(self):
        """Should block http to private IPs by default."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "http://192.168.1.100/webhook"
        )
        assert is_valid is False
        assert "Blocked private/internal IP" in error

    def test_http_localhost_allowed_with_flag(self):
        """Should allow localhost when allow_private_ips=True."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "http://localhost:5000/webhook", allow_private_ips=True
        )
        assert is_valid is True
        assert error is None

    def test_http_private_ip_allowed_with_flag(self):
        """Should allow private IPs when allow_private_ips=True."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "http://192.168.1.100/webhook", allow_private_ips=True
        )
        assert is_valid is True
        assert error is None

    def test_whitespace_stripped(self):
        """Should strip whitespace from URL."""
        is_valid, error = NotificationURLValidator.validate_service_url(
            "  https://example.com/webhook  "
        )
        assert is_valid is True
        assert error is None


class TestValidateServiceUrlStrict:
    """Tests for validate_service_url_strict static method."""

    def test_valid_url_returns_true(self):
        """Should return True for valid URLs."""
        result = NotificationURLValidator.validate_service_url_strict(
            "https://example.com/webhook"
        )
        assert result is True

    def test_invalid_url_raises_exception(self):
        """Should raise NotificationURLValidationError for invalid URLs."""
        with pytest.raises(NotificationURLValidationError) as exc_info:
            NotificationURLValidator.validate_service_url_strict(
                "file:///etc/passwd"
            )
        assert "validation failed" in str(exc_info.value)

    def test_private_ip_raises_exception(self):
        """Should raise exception for private IPs by default."""
        with pytest.raises(NotificationURLValidationError) as exc_info:
            NotificationURLValidator.validate_service_url_strict(
                "http://localhost/webhook"
            )
        assert "Blocked private/internal IP" in str(exc_info.value)

    def test_private_ip_allowed_with_flag(self):
        """Should not raise when allow_private_ips=True."""
        result = NotificationURLValidator.validate_service_url_strict(
            "http://localhost/webhook", allow_private_ips=True
        )
        assert result is True


class TestValidateMultipleUrls:
    """Tests for validate_multiple_urls static method."""

    def test_empty_urls_rejected(self):
        """Should reject empty URL string."""
        is_valid, error = NotificationURLValidator.validate_multiple_urls("")
        assert is_valid is False
        assert "non-empty string" in error

    def test_none_urls_rejected(self):
        """Should reject None."""
        is_valid, error = NotificationURLValidator.validate_multiple_urls(None)
        assert is_valid is False
        assert "non-empty string" in error

    def test_only_separators_rejected(self):
        """Should reject string with only separators."""
        is_valid, error = NotificationURLValidator.validate_multiple_urls(",,,")
        assert is_valid is False
        assert "No valid URLs found" in error

    def test_single_valid_url(self):
        """Should accept single valid URL."""
        is_valid, error = NotificationURLValidator.validate_multiple_urls(
            "https://example.com/webhook"
        )
        assert is_valid is True
        assert error is None

    def test_multiple_valid_urls(self):
        """Should accept multiple valid URLs."""
        urls = "https://example.com/webhook,discord://id/token,slack://token"
        is_valid, error = NotificationURLValidator.validate_multiple_urls(urls)
        assert is_valid is True
        assert error is None

    def test_one_invalid_url_fails_all(self):
        """Should fail if any URL is invalid."""
        urls = "https://example.com/webhook,file:///etc/passwd"
        is_valid, error = NotificationURLValidator.validate_multiple_urls(urls)
        assert is_valid is False
        assert "file" in error.lower()

    def test_whitespace_in_urls_stripped(self):
        """Should handle whitespace around URLs."""
        urls = "  https://example.com/webhook  ,  discord://id/token  "
        is_valid, error = NotificationURLValidator.validate_multiple_urls(urls)
        assert is_valid is True
        assert error is None

    def test_custom_separator(self):
        """Should support custom separator."""
        urls = "https://example.com/webhook|discord://id/token"
        is_valid, error = NotificationURLValidator.validate_multiple_urls(
            urls, separator="|"
        )
        assert is_valid is True
        assert error is None

    def test_private_ip_in_multiple_blocked(self):
        """Should block private IPs in multiple URLs."""
        urls = "https://example.com/webhook,http://localhost/webhook"
        is_valid, error = NotificationURLValidator.validate_multiple_urls(urls)
        assert is_valid is False
        assert "Blocked private/internal IP" in error

    def test_private_ip_allowed_with_flag(self):
        """Should allow private IPs when flag is set."""
        urls = "https://example.com/webhook,http://localhost/webhook"
        is_valid, error = NotificationURLValidator.validate_multiple_urls(
            urls, allow_private_ips=True
        )
        assert is_valid is True
        assert error is None


class TestClassConstants:
    """Tests for class constants."""

    def test_blocked_schemes_contains_dangerous_protocols(self):
        """BLOCKED_SCHEMES should contain dangerous protocols."""
        blocked = NotificationURLValidator.BLOCKED_SCHEMES
        assert "file" in blocked
        assert "ftp" in blocked
        assert "javascript" in blocked
        assert "data" in blocked

    def test_allowed_schemes_contains_common_services(self):
        """ALLOWED_SCHEMES should contain common notification services."""
        allowed = NotificationURLValidator.ALLOWED_SCHEMES
        assert "http" in allowed
        assert "https" in allowed
        assert "discord" in allowed
        assert "slack" in allowed
        assert "telegram" in allowed
        assert "mailto" in allowed

    def test_private_ip_ranges_exist(self):
        """PRIVATE_IP_RANGES should contain RFC1918 and other private ranges."""
        ranges = NotificationURLValidator.PRIVATE_IP_RANGES
        assert len(ranges) > 0
        # Check some expected ranges are present
        range_strings = [str(r) for r in ranges]
        assert "127.0.0.0/8" in range_strings
        assert "10.0.0.0/8" in range_strings
        assert "192.168.0.0/16" in range_strings
