"""ヘッダの中継規則。書き換えてよいのは Authorization と Host だけ。"""

from __future__ import annotations

import httpx

from .conftest import INTERNAL_KEY, AsyncChunks, auth, create_key, send_raw

HOP_BY_HOP_HEADERS = {
    "connection": "keep-alive, x-hop-marker",
    "keep-alive": "timeout=5, max=1000",
    "te": "trailers",
    "trailer": "Expires",
    "upgrade": "websocket",
    "proxy-authorization": "Basic Zm9vOmJhcg==",
    "proxy-connection": "keep-alive",
    "x-hop-marker": "should-be-dropped",
}

FORWARDED_HEADERS = {
    "x-forwarded-for": "203.0.113.9, 198.51.100.7",
    "x-forwarded-proto": "https",
    "x-forwarded-host": "evil.example",
}


async def test_hop_by_hop_and_forwarded_headers_are_dropped(db, keys, settings, build_proxy, recorder):
    secret = await create_key(db, keys, settings)
    client, _ = build_proxy()
    headers = {**HOP_BY_HOP_HEADERS, **FORWARDED_HEADERS, **auth(secret), "x-keep-me": "kept"}

    async with client:
        response = await client.post("/v1/chat/completions", headers=headers, content=b'{"model":"m"}')

    assert response.status_code == 200
    forwarded = recorder.header_names()
    for name in HOP_BY_HOP_HEADERS:
        assert name not in forwarded, f"{name} が上流へ漏れています"
    for name in FORWARDED_HEADERS:
        assert name not in forwarded, f"{name} が上流へ漏れています"
    assert recorder.header("x-keep-me") == "kept"


async def test_authorization_is_replaced_with_internal_key(db, keys, settings, build_proxy, recorder):
    secret = await create_key(db, keys, settings)
    client, _ = build_proxy()
    async with client:
        await client.post("/v1/chat/completions", headers=auth(secret), content=b'{"model":"m"}')

    assert recorder.header("authorization") == f"Bearer {INTERNAL_KEY}"
    assert secret not in (recorder.header("authorization") or "")


async def test_host_is_rewritten_to_upstream(db, keys, settings, build_proxy, recorder):
    secret = await create_key(db, keys, settings)
    client, _ = build_proxy()
    async with client:
        await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "host": "gateway.test"},
            content=b'{"model":"m"}',
        )

    assert recorder.header("host") == "upstream.test"


async def test_accept_encoding_defaults_to_identity(db, keys, settings, build_proxy, recorder):
    """クライアントが送っていなければ identity を明示する。

    httpx の既定は ``gzip, deflate, br`` であり、そのままだとクライアントが
    解釈できない圧縮応答を返しかねない。
    """

    secret = await create_key(db, keys, settings)
    client, _ = build_proxy()
    async with client:
        await send_raw(
            client,
            "POST",
            "/v1/chat/completions",
            headers=auth(secret),
            content=b'{"model":"m"}',
            drop=["accept-encoding"],
        )

    assert recorder.header("accept-encoding") == "identity"


async def test_accept_encoding_from_client_is_preserved(db, keys, settings, build_proxy, recorder):
    secret = await create_key(db, keys, settings)
    client, _ = build_proxy()
    async with client:
        await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "accept-encoding": "gzip"},
            content=b'{"model":"m"}',
        )

    assert recorder.header("accept-encoding") == "gzip"


async def test_httpx_default_headers_are_not_injected(db, keys, settings, build_proxy, recorder):
    """クライアントが送っていないヘッダを httpx が補うことを防ぐ。"""

    secret = await create_key(db, keys, settings)
    client, _ = build_proxy()
    async with client:
        await send_raw(
            client,
            "POST",
            "/v1/chat/completions",
            headers=auth(secret),
            content=b'{"model":"m"}',
            drop=["user-agent", "accept", "connection"],
        )

    forwarded = recorder.header_names()
    assert "user-agent" not in forwarded
    assert "accept" not in forwarded
    assert "connection" not in forwarded


async def test_client_user_agent_is_forwarded(db, keys, settings, build_proxy, recorder):
    secret = await create_key(db, keys, settings)
    client, _ = build_proxy()
    async with client:
        await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "user-agent": "my-client/1.0"},
            content=b'{"model":"m"}',
        )

    assert recorder.header("user-agent") == "my-client/1.0"


async def test_response_hop_by_hop_headers_are_dropped(db, keys, settings, build_proxy):
    secret = await create_key(db, keys, settings)

    def respond(request: httpx.Request) -> httpx.Response:
        return httpx.Response(
            200,
            headers=[
                (b"content-type", b"application/json"),
                (b"connection", b"keep-alive, x-resp-hop"),
                (b"keep-alive", b"timeout=5"),
                (b"transfer-encoding", b"chunked"),
                (b"x-resp-hop", b"dropped"),
                (b"x-resp-keep", b"kept"),
            ],
            stream=AsyncChunks([b'{"ok":true}']),
        )

    client, _ = build_proxy(respond)
    async with client:
        response = await client.post(
            "/v1/chat/completions", headers=auth(secret), content=b'{"model":"m"}'
        )

    assert response.status_code == 200
    assert "keep-alive" not in response.headers
    assert "x-resp-hop" not in response.headers
    assert response.headers.get("x-resp-keep") == "kept"


async def test_client_ip_uses_rightmost_forwarded_value(db, keys, settings, build_proxy, logs):
    """最左端はクライアントが詐称できるため、最右端を採用する。"""

    secret = await create_key(db, keys, settings)
    client, _ = build_proxy()
    async with client:
        await client.post(
            "/v1/chat/completions",
            headers={**auth(secret), "x-forwarded-for": "1.1.1.1, 203.0.113.9"},
            content=b'{"model":"m"}',
        )
    await logs.stop()

    row = await db.fetchone("SELECT client_ip FROM request_logs ORDER BY id DESC LIMIT 1")
    assert row is not None
    assert row["client_ip"] == "203.0.113.9"
