"""Tests for the standalone ``sanitize_error_message`` function.

Verifies that the regex-based sanitizer catches common credential formats
in exception messages. This function was extracted from
``BaseSearchEngine._sanitize_error_message`` so it can be used outside the
search-engine inheritance tree (e.g. in LLM config and error handling).
"""

from local_deep_research.security.log_sanitizer import sanitize_error_message


class TestSanitizeErrorMessageStandalone:
    """Unit tests for the pattern-based credential sanitizer."""

    def test_bearer_token_redacted(self):
        msg = 'Error: Connection refused, token="Bearer sk-proj-abc123xyz456def789ghi012"'
        result = sanitize_error_message(msg)
        assert "sk-proj-abc123xyz456def789ghi012" not in result
        assert "Bearer [REDACTED]" in result

    def test_url_query_api_key_redacted(self):
        msg = "HTTPSConnectionPool: Max retries exceeded with url: /v1/models?api_key=sk-secret-key-value-12345&q=test"
        result = sanitize_error_message(msg)
        assert "sk-secret-key-value-12345" not in result
        assert "api_key=[REDACTED]" in result

    def test_url_query_apikey_no_underscore_redacted(self):
        msg = "?apikey=my-secret-api-key-value-here&format=json"
        result = sanitize_error_message(msg)
        assert "my-secret-api-key-value-here" not in result
        assert "apikey=[REDACTED]" in result

    def test_url_query_api_key_hyphenated_redacted(self):
        """Guardian uses ``api-key`` (with hyphen) as the param name."""
        msg = "?api-key=guardian-secret-key-value-here&q=test"
        result = sanitize_error_message(msg)
        assert "guardian-secret-key-value-here" not in result
        assert "api-key=[REDACTED]" in result

    def test_url_query_key_redacted(self):
        """Google PSE uses ``key`` as the param name."""
        msg = "?key=google-pse-secret-key-value&q=test"
        result = sanitize_error_message(msg)
        assert "google-pse-secret-key-value" not in result
        assert "key=[REDACTED]" in result

    def test_url_query_token_redacted(self):
        msg = "?token=secret-token-value-1234567890&page=1"
        result = sanitize_error_message(msg)
        assert "secret-token-value-1234567890" not in result
        assert "token=[REDACTED]" in result

    def test_url_query_secret_redacted(self):
        msg = "&secret=super-secret-value-abcde12345&format=json"
        result = sanitize_error_message(msg)
        assert "super-secret-value-abcde12345" not in result
        assert "secret=[REDACTED]" in result

    def test_url_credentials_redacted(self):
        msg = "Connection to https://admin:supersecretpassword@api.example.com failed"
        result = sanitize_error_message(msg)
        assert "admin" not in result
        assert "supersecretpassword" not in result
        assert "[REDACTED]:[REDACTED]@" in result

    def test_url_credentials_inside_api_key_param_value(self):
        """URL-embedded credentials must survive being an api-key param value.

        Regression test for pattern ordering: when the param value is itself
        a URL with embedded credentials, the param pattern used to consume
        the ``https`` scheme first (``api-key=[REDACTED]://user:pass@...``),
        leaving the credentials unredacted. The URL-credentials pattern now
        runs before the URL-param pattern.
        """
        msg = "Request failed for ?api-key=https://admin:supersecret@internal.example.com/v1"
        result = sanitize_error_message(msg)
        assert "supersecret" not in result
        assert "admin" not in result

    def test_url_credentials_inside_key_param_value(self):
        """Same ordering interaction via the ``key=`` spelling."""
        msg = "?key=https://user:hunter2@host.example.com/path"
        result = sanitize_error_message(msg)
        assert "hunter2" not in result
        assert "user:" not in result

    def test_url_credentials_inside_token_param_value(self):
        """Same ordering interaction via the ``token=`` spelling."""
        msg = "retry failed: &token=http://svc:p4ssw0rd@10.0.0.5:8080/auth"
        result = sanitize_error_message(msg)
        assert "p4ssw0rd" not in result
        assert "svc:" not in result

    def test_sk_prefix_redacted(self):
        """OpenAI-style key with modern hyphenated format (sk-proj-...)."""
        msg = "Invalid API key: sk-proj-abc123xyz456def789ghi012jkl"
        result = sanitize_error_message(msg)
        assert "sk-proj-abc123xyz456def789ghi012jkl" not in result
        assert "[REDACTED_KEY]" in result

    def test_sk_prefix_pure_alphanumeric(self):
        """Old-style OpenAI key (pure alphanumeric after sk-)."""
        msg = "key: sk-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        result = sanitize_error_message(msg)
        assert "sk-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" not in result
        assert "[REDACTED_KEY]" in result

    def test_pk_prefix_redacted(self):
        msg = "public key pk-test-abc123xyz456def789ghi012jkl345"
        result = sanitize_error_message(msg)
        assert "pk-test-abc123xyz456def789ghi012jkl345" not in result
        assert "[REDACTED_KEY]" in result

    def test_empty_string_passthrough(self):
        assert sanitize_error_message("") == ""

    def test_no_secrets_unchanged(self):
        msg = (
            "Connection timeout after 10s for https://api.example.com/v1/models"
        )
        assert sanitize_error_message(msg) == msg

    def test_multiple_secrets_in_one_message(self):
        msg = (
            "Bearer sk-proj-abc123xyz456def789ghi012 "
            "with ?api_key=my-secret-key-12345&token=another-secret-value-67890"
        )
        result = sanitize_error_message(msg)
        assert "sk-proj-abc123xyz456def789ghi012" not in result
        assert "my-secret-key-12345" not in result
        assert "another-secret-value-67890" not in result
        assert "Bearer [REDACTED]" in result
        assert "api_key=[REDACTED]" in result
        assert "token=[REDACTED]" in result

    def test_sk_prefix_too_short_not_redacted(self):
        """Short strings like ``sk-abc`` should NOT be redacted (false positive risk)."""
        msg = "prefix: sk-abc"
        assert sanitize_error_message(msg) == msg

    def test_delegate_preserves_backward_compat(self):
        """The BaseSearchEngine method delegates to the standalone function,
        so this test verifies the output matches the old inline behavior
        for a representative message."""
        from local_deep_research.web_search_engines.search_engine_base import (
            BaseSearchEngine,
        )

        msg = "Error: ?api_key=secret12345678&key=mykey1234567890123456"
        expected = sanitize_error_message(msg)

        # Create a minimal subclass instance to call the instance method.
        class _MinimalEngine(BaseSearchEngine):
            def _get_previews(self, query, *args, **kwargs):
                return []

        engine = _MinimalEngine.__new__(_MinimalEngine)
        actual = engine._sanitize_error_message(msg)
        assert actual == expected
