"""URL redaction for logs + OTel spans (SH-04).

Closes the gap where `_log_response` and `_client_span` emitted the raw
request URL — a query-string (`?api_key=secret`) or userinfo
(`user:pass@host`) credential would land in logs / span attributes
unredacted, despite the library already defending the error-message
path against exactly that (`_categorise_exc`).
"""

from __future__ import annotations

import logging

import pytest

from mgf.http._client import _log_response, _safe_url


class _FakeResponse:
    status_code = 200

    class _Elapsed:
        @staticmethod
        def total_seconds() -> float:
            return 0.01

    elapsed = _Elapsed()


class TestSafeUrl:
    def test_query_values_masked_keys_kept(self) -> None:
        out = _safe_url("https://api.example.com/v1/charge?api_key=sk-secret&page=2")
        assert "sk-secret" not in out
        assert out == "https://api.example.com/v1/charge?api_key=%3Credacted%3E&page=%3Credacted%3E"

    def test_userinfo_dropped(self) -> None:
        out = _safe_url("https://alice:hunter2@api.example.com/data")
        assert "hunter2" not in out
        assert "alice" not in out
        assert out == "https://api.example.com/data"

    def test_userinfo_and_query_both_handled(self) -> None:
        out = _safe_url("https://u:p@host/path?token=abc123")
        assert "p@" not in out
        assert "abc123" not in out
        assert out.startswith("https://host/path?token=")

    def test_no_query_passthrough(self) -> None:
        url = "https://api.example.com/v1/health"
        assert _safe_url(url) == url

    def test_blank_query_values_masked(self) -> None:
        out = _safe_url("https://h/p?secret=&keep=x")
        assert out == "https://h/p?secret=%3Credacted%3E&keep=%3Credacted%3E"

    @pytest.mark.parametrize("bad", ["", "://nonsense", "not a url at all"])
    def test_never_raises(self, bad: str) -> None:
        # Must degrade safely, never raise (it runs on every request).
        _safe_url(bad)


class TestLogResponseRedactsUrl:
    def test_secret_query_param_absent_from_log(
        self,
        caplog: pytest.LogCaptureFixture,
    ) -> None:
        with caplog.at_level(logging.INFO, logger="mgf.http"):
            _log_response(
                "GET",
                "https://api.example.com/v1/x?api_key=sk-supersecret-token",
                _FakeResponse(),  # type: ignore[arg-type]
                {"Authorization": "Bearer abc"},
            )
        blob = "\n".join(r.getMessage() for r in caplog.records)
        assert "sk-supersecret-token" not in blob
        assert "<redacted>" in blob
