"""Tests for TransferUploader and execute_command_with_file orchestration.

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

from __future__ import annotations

from unittest.mock import AsyncMock, patch

import pytest

from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient
from mcp_proxy_adapter.client.jsonrpc_client.exceptions import TransferClientError
from mcp_proxy_adapter.transfer.checksums import compute_file_checksum
from mcp_proxy_adapter.transfer.client_uploader import TransferUploader
from mcp_proxy_adapter.transfer.models import TransferReceipt


def _receipt_dict(
    *,
    tid: str = "tr_300",
    filename: str = "f.bin",
    size: int = 6,
    offset: int = 6,
    status: str = "uploaded",
    completed: bool = True,
    chunk_size: int = 2,
    checksum_value: str | None = None,
) -> dict[str, object]:
    cs = checksum_value if checksum_value is not None else "0" * 64
    return {
        "transfer_id": tid,
        "filename": filename,
        "size_bytes": size,
        "checksum_algorithm": "sha256",
        "checksum_value": cs,
        "compression": "identity",
        "chunk_size": chunk_size,
        "offset": offset,
        "status": status,
        "completed": completed,
        "expires_at": None,
    }


@pytest.mark.asyncio
async def test_upload_file_creates_session_sends_chunks_and_completes(
    tmp_path,
) -> None:
    src = tmp_path / "in.bin"
    payload = b"abcdef"
    src.write_bytes(payload)
    checksum = compute_file_checksum(str(src), algorithm="sha256", chunk_size=2)

    transport = AsyncMock()
    tid = "tr_u1"
    transport.create_upload_session = AsyncMock(
        return_value=_receipt_dict(
            tid=tid,
            filename="in.bin",
            size=len(payload),
            offset=0,
            status="created",
            completed=False,
        )
    )

    async def _chunk_side_effect(t_id: str, off: int, chunk: bytes) -> dict[str, object]:
        assert t_id == tid
        new_off = off + len(chunk)
        d = _receipt_dict(
            tid=tid,
            filename="in.bin",
            size=len(payload),
            offset=new_off,
            status="uploading",
            completed=False,
            chunk_size=2,
        )
        d["checksum_value"] = checksum
        return d

    transport.upload_chunk = AsyncMock(side_effect=_chunk_side_effect)
    transport.complete_upload_session = AsyncMock(
        return_value=_receipt_dict(
            tid=tid,
            filename="in.bin",
            size=len(payload),
            offset=len(payload),
            checksum_value=checksum,
        )
    )

    up = TransferUploader(transport, default_chunk_size=2)
    r = await up.upload_file(str(src), filename="in.bin", chunk_size=2)
    assert r.transfer_id == tid
    assert r.offset == len(payload)
    assert r.completed is True
    transport.create_upload_session.assert_awaited_once()
    assert transport.upload_chunk.await_count == 3
    transport.complete_upload_session.assert_awaited_once_with(tid)


@pytest.mark.asyncio
async def test_resume_upload_uses_server_offset(tmp_path) -> None:
    src = tmp_path / "r.bin"
    payload = b"abcdef"
    src.write_bytes(payload)
    checksum = compute_file_checksum(str(src), algorithm="sha256", chunk_size=2)
    tid = "tr_r2"

    transport = AsyncMock()
    transport.get_upload_session = AsyncMock(
        return_value={
            "transfer_id": tid,
            "offset": 4,
            "size_bytes": 6,
            "filename": "r.bin",
            "checksum_algorithm": "sha256",
            "checksum_value": checksum,
            "compression": "identity",
            "chunk_size": 2,
            "status": "uploading",
        }
    )

    async def _chunk_side_effect(t_id: str, off: int, chunk: bytes) -> dict[str, object]:
        new_off = off + len(chunk)
        d = _receipt_dict(
            tid=tid,
            filename="r.bin",
            size=6,
            offset=new_off,
            status="uploading",
            completed=False,
            chunk_size=2,
        )
        d["checksum_value"] = checksum
        return d

    transport.upload_chunk = AsyncMock(side_effect=_chunk_side_effect)
    transport.complete_upload_session = AsyncMock(
        return_value=_receipt_dict(
            tid=tid,
            filename="r.bin",
            size=6,
            offset=6,
            checksum_value=checksum,
        )
    )

    up = TransferUploader(transport, default_chunk_size=2)
    r = await up.resume_upload(str(src), tid, chunk_size=2)
    assert r.offset == 6
    first_call = transport.upload_chunk.await_args_list[0]
    assert first_call[0][1] == 4
    assert transport.upload_chunk.await_count == 1


@pytest.mark.asyncio
async def test_execute_command_with_file_injects_transfer_id_and_calls_command() -> (
    None
):
    client = JsonRpcClient(host="127.0.0.1", port=8080)
    receipt = TransferReceipt(
        transfer_id="tr_400",
        filename="input.md",
        size_bytes=1,
        checksum_algorithm="sha256",
        checksum_value="a" * 64,
        compression="identity",
        chunk_size=1024,
        offset=1,
        status="uploaded",
        completed=True,
        expires_at=None,
    )
    cmd_result = {"ok": True}

    with patch.object(
        client, "upload_file", new_callable=AsyncMock, return_value=receipt
    ) as mu, patch.object(
        client,
        "execute_command_unified",
        new_callable=AsyncMock,
        return_value=cmd_result,
    ) as me:
        out = await client.execute_command_with_file(
            "process_file",
            {"filter_name": "markdown"},
            "/tmp/input.md",
            filename="input.md",
        )

    assert out == cmd_result
    mu.assert_awaited_once()
    me.assert_awaited_once()
    _name, merged, *_ = me.call_args[0]
    assert _name == "process_file"
    assert merged["transfer_id"] == "tr_400"
    assert merged["filename"] == "input.md"
    assert merged["compression"] == "identity"
    assert merged["checksum_algorithm"] == "sha256"
    assert merged["filter_name"] == "markdown"


@pytest.mark.asyncio
async def test_execute_command_with_file_does_not_dispatch_when_upload_fails() -> (
    None
):
    client = JsonRpcClient(host="127.0.0.1", port=8080)
    bad = TransferReceipt(
        transfer_id="tr_bad",
        filename="x",
        size_bytes=1,
        checksum_algorithm="sha256",
        checksum_value="b" * 64,
        compression="identity",
        chunk_size=1,
        offset=0,
        status="uploading",
        completed=False,
        expires_at=None,
    )

    with patch.object(
        client, "upload_file", new_callable=AsyncMock, return_value=bad
    ) as mu, patch.object(
        client, "execute_command_unified", new_callable=AsyncMock
    ) as me:
        with pytest.raises(TransferClientError):
            await client.execute_command_with_file(
                "process_file", {}, "/tmp/x", filename="x"
            )

    mu.assert_awaited_once()
    me.assert_not_awaited()
