# retoor <retoor@molodetz.nl>

import httpx
import pytest
from curl_cffi import CurlHttpVersion
from curl_cffi.requests.exceptions import RequestException, Timeout

import devplacepy.curl_transport as ct
from devplacepy.curl_transport import CurlTransport, http_version_for, resolve_timeout
from tests.conftest import run_async


class FakeCurlResponse:
    def __init__(self, status=200, headers=None, chunks=None, http_version=3):
        self.status_code = status
        self.headers = headers or {}
        self.http_version = http_version
        self._chunks = chunks if chunks is not None else [b"hello"]
        self.closed = False

    async def aiter_content(self):
        for chunk in self._chunks:
            yield chunk

    async def aclose(self):
        self.closed = True


class FakeSession:
    def __init__(self, response=None, raise_exc=None):
        self.calls = []
        self.closed = False
        self.response = response if response is not None else FakeCurlResponse()
        self.raise_exc = raise_exc

    async def request(self, method, url, **kwargs):
        self.calls.append({"method": method, "url": url, **kwargs})
        if self.raise_exc is not None:
            raise self.raise_exc
        return self.response

    async def close(self):
        self.closed = True


def make_transport(monkeypatch, response=None, raise_exc=None):
    fake = FakeSession(response=response, raise_exc=raise_exc)
    monkeypatch.setattr(ct, "AsyncSession", lambda *a, **k: fake)
    return CurlTransport(), fake


def test_request_headers_stripped_and_forwarded(monkeypatch):
    transport, fake = make_transport(monkeypatch)
    request = httpx.Request(
        "POST",
        "https://api.example.com/v1/x",
        headers={
            "user-agent": "leak/1.0",
            "accept-encoding": "identity",
            "authorization": "Bearer k",
            "content-type": "application/json",
        },
        content=b'{"a":1}',
    )
    run_async(transport.handle_async_request(request))
    sent = {key.lower(): value for key, value in fake.calls[0]["headers"].items()}
    assert "user-agent" not in sent
    assert "accept-encoding" not in sent
    assert "host" not in sent
    assert "content-length" not in sent
    assert sent["authorization"] == "Bearer k"
    assert sent["content-type"] == "application/json"
    assert fake.calls[0]["data"] == b'{"a":1}'
    assert fake.calls[0]["allow_redirects"] is False
    assert fake.calls[0]["stream"] is True
    assert fake.calls[0]["impersonate"] == ct.IMPERSONATE_TARGET
    run_async(transport.aclose())


def test_response_headers_stripped_and_body_streamed(monkeypatch):
    response = FakeCurlResponse(
        headers={
            "content-type": "text/html",
            "content-encoding": "gzip",
            "content-length": "5",
            "x-foo": "bar",
        },
        chunks=[b"he", b"llo"],
    )
    transport, fake = make_transport(monkeypatch, response=response)
    request = httpx.Request("GET", "https://example.com/")
    out = run_async(transport.handle_async_request(request))
    assert out.status_code == 200
    assert "content-encoding" not in out.headers
    assert "content-length" not in out.headers
    assert out.headers["x-foo"] == "bar"
    body = run_async(out.aread())
    assert body == b"hello"
    assert response.closed is True


@pytest.mark.parametrize(
    "curl_value,label",
    [(2, "HTTP/1.1"), (3, "HTTP/2"), (4, "HTTP/2"), (30, "HTTP/3"), (999, "HTTP/2")],
)
def test_http_version_mapping(monkeypatch, curl_value, label):
    response = FakeCurlResponse(http_version=curl_value)
    transport, _fake = make_transport(monkeypatch, response=response)
    out = run_async(transport.handle_async_request(httpx.Request("GET", "https://example.com/")))
    assert out.http_version == label


def test_resolve_timeout_prefers_read_then_connect_then_default():
    read = httpx.Request("GET", "https://x/", extensions={"timeout": {"read": 12.0, "connect": 3.0}})
    assert resolve_timeout(read) == 12.0
    connect = httpx.Request("GET", "https://x/", extensions={"timeout": {"connect": 3.0}})
    assert resolve_timeout(connect) == 3.0
    none = httpx.Request("GET", "https://x/")
    assert resolve_timeout(none) == ct.DEFAULT_TIMEOUT_SECONDS


def test_curl_timeout_maps_to_httpx_connecttimeout(monkeypatch):
    transport, _fake = make_transport(monkeypatch, raise_exc=Timeout("slow"))
    with pytest.raises(httpx.ConnectTimeout):
        run_async(transport.handle_async_request(httpx.Request("GET", "https://example.com/")))


def test_curl_error_maps_to_httpx_connecterror(monkeypatch):
    transport, _fake = make_transport(monkeypatch, raise_exc=RequestException("boom"))
    with pytest.raises(httpx.ConnectError):
        run_async(transport.handle_async_request(httpx.Request("GET", "https://example.com/")))


def test_aclose_closes_session(monkeypatch):
    transport, fake = make_transport(monkeypatch)
    run_async(transport.aclose())
    assert fake.closed is True


def test_http_version_for_selects_http1_on_cleartext():
    assert http_version_for(httpx.URL("http://localhost:10500/x")) == CurlHttpVersion.V1_1
    assert http_version_for(httpx.URL("https://example.com/")) is None


def test_cleartext_request_forces_http1_1(monkeypatch):
    # A plaintext http:// origin (the internal localhost gateway hop) must be sent
    # as HTTP/1.1: curl impersonation otherwise attempts cleartext HTTP/2, which a
    # uvicorn (HTTP/1.1-only) server rejects as "Invalid HTTP request received."
    # once the request body is large (~128KB, e.g. the Devii tool list).
    transport, fake = make_transport(monkeypatch)
    run_async(
        transport.handle_async_request(
            httpx.Request("POST", "http://localhost:10500/openai/v1/x", content=b"{}")
        )
    )
    assert fake.calls[0]["http_version"] == CurlHttpVersion.V1_1


def test_tls_request_keeps_impersonation_default(monkeypatch):
    # https keeps the Chrome HTTP/2-over-TLS fingerprint: no version override.
    transport, fake = make_transport(monkeypatch)
    run_async(
        transport.handle_async_request(httpx.Request("GET", "https://api.example.com/v1/x"))
    )
    assert "http_version" not in fake.calls[0]
