"""Tests for JsonRpcTransport transfer HTTP helpers (mocked httpx).

Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com
"""

from __future__ import annotations

from unittest.mock import AsyncMock

import httpx
import pytest

from mcp_proxy_adapter.client.jsonrpc_client.exceptions import (
    TransferResumeConflictClientError,
    TransferSessionNotFoundClientError,
)
from mcp_proxy_adapter.client.jsonrpc_client.transport import JsonRpcTransport


@pytest.mark.asyncio
async def test_create_upload_session_uses_dedicated_transfer_endpoint() -> None:
    transport = JsonRpcTransport(host="127.0.0.1", port=7777)
    mock_http = AsyncMock()
    req = httpx.Request("POST", "http://127.0.0.1:7777/api/transfer/uploads")
    receipt = {
        "transfer_id": "tr_x",
        "filename": "a.bin",
        "size_bytes": 10,
        "checksum_algorithm": "sha256",
        "checksum_value": "0" * 64,
        "compression": "identity",
        "chunk_size": 4,
        "offset": 0,
        "status": "created",
        "completed": False,
        "expires_at": None,
    }
    mock_http.post = AsyncMock(
        return_value=httpx.Response(201, json=receipt, request=req)
    )

    async def _fake_client() -> AsyncMock:
        return mock_http

    transport._get_client = _fake_client  # type: ignore[method-assign]

    out = await transport.create_upload_session(
        "a.bin",
        10,
        "sha256",
        "0" * 64,
        "identity",
        chunk_size=4,
    )
    assert out["transfer_id"] == "tr_x"
    mock_http.post.assert_awaited_once()
    url = str(mock_http.post.call_args[0][0])
    assert url.endswith("/api/transfer/uploads")
    body = mock_http.post.call_args[1]["json"]
    assert body["filename"] == "a.bin"
    assert body["chunk_size"] == 4


@pytest.mark.asyncio
async def test_upload_chunk_sends_raw_bytes_not_json() -> None:
    transport = JsonRpcTransport(host="127.0.0.1", port=7778)
    mock_http = AsyncMock()
    req = httpx.Request(
        "PUT", "http://127.0.0.1:7778/api/transfer/uploads/tr_1/chunks?offset=0"
    )
    mock_http.put = AsyncMock(
        return_value=httpx.Response(
            200,
            json={
                "transfer_id": "tr_1",
                "filename": "a.bin",
                "size_bytes": 5,
                "checksum_algorithm": "sha256",
                "checksum_value": "0" * 64,
                "compression": "identity",
                "chunk_size": 5,
                "offset": 3,
                "status": "uploading",
                "completed": False,
                "expires_at": None,
            },
            request=req,
        )
    )

    async def _fake_client() -> AsyncMock:
        return mock_http

    transport._get_client = _fake_client  # type: ignore[method-assign]

    raw = b"\x01\x02\x03"
    await transport.upload_chunk("tr_1", 0, raw)
    mock_http.put.assert_awaited_once()
    kwargs = mock_http.put.call_args[1]
    assert kwargs["content"] == raw
    assert "json" not in kwargs or kwargs.get("json") is None


@pytest.mark.asyncio
async def test_download_chunk_returns_bytes_and_header_metadata() -> None:
    transport = JsonRpcTransport(host="127.0.0.1", port=7779)
    mock_http = AsyncMock()
    req = httpx.Request(
        "GET",
        "http://127.0.0.1:7779/api/transfer/downloads/tr_d/chunks",
    )
    mock_http.get = AsyncMock(
        return_value=httpx.Response(
            200,
            content=b"abc",
            headers={
                "X-Transfer-Id": "tr_d",
                "X-Transfer-Offset": "3",
                "X-Transfer-Completed": "false",
                "X-Transfer-Status": "downloading",
            },
            request=req,
        )
    )

    async def _fake_client() -> AsyncMock:
        return mock_http

    transport._get_client = _fake_client  # type: ignore[method-assign]

    data, meta = await transport.download_chunk("tr_d", 0, 1024)
    assert data == b"abc"
    assert meta["transfer_id"] == "tr_d"
    assert meta["offset"] == 3
    assert meta["completed"] is False
    assert meta["status"] == "downloading"


def test_raise_for_transfer_error_maps_error_types() -> None:
    with pytest.raises(TransferSessionNotFoundClientError):
        JsonRpcTransport.raise_for_transfer_error(
            404,
            {
                "success": False,
                "error_type": "TransferSessionNotFoundError",
                "message": "missing",
                "details": {"transfer_id": "x"},
            },
        )

    with pytest.raises(TransferResumeConflictClientError) as ctx:
        JsonRpcTransport.raise_for_transfer_error(
            409,
            {
                "success": False,
                "error_type": "TransferResumeConflictError",
                "message": "bad offset",
                "details": {"expected_offset": 8, "actual_offset": 0},
            },
        )
    assert ctx.value.details.get("expected_offset") == 8
