"""
Schema and metadata checks for transfer JSON-RPC commands.

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

from __future__ import annotations

import hashlib
from pathlib import Path
from typing import Any, Dict, Tuple, Type

import pytest

from mcp_proxy_adapter.commands.base import Command
from mcp_proxy_adapter.commands.transfer_download_begin_command import (
    TransferDownloadBeginCommand,
)
from mcp_proxy_adapter.commands.transfer_download_status_command import (
    TransferDownloadStatusCommand,
)
from mcp_proxy_adapter.commands.transfer_upload_begin_command import (
    TransferUploadBeginCommand,
)
from mcp_proxy_adapter.commands.transfer_upload_complete_command import (
    TransferUploadCompleteCommand,
)
from mcp_proxy_adapter.commands.transfer_upload_status_command import (
    TransferUploadStatusCommand,
)

TRANSFER_COMMAND_CLASSES: Tuple[Type[Command], ...] = (
    TransferUploadBeginCommand,
    TransferUploadStatusCommand,
    TransferUploadCompleteCommand,
    TransferDownloadBeginCommand,
    TransferDownloadStatusCommand,
)

MIN_DESCR_LEN = 200


def _assert_example_params_validate(
    cmd: Command, example_params: Dict[str, object]
) -> None:
    inst = cmd.__class__()
    inst.validate_params(dict(example_params))


def _assert_schema_properties_have_descriptions(schema: Dict[str, Any]) -> None:
    props = schema.get("properties") or {}
    for key, sub in props.items():
        assert isinstance(sub, dict), f"property {key!r} must be object"
        desc = sub.get("description")
        assert (
            isinstance(desc, str) and desc.strip()
        ), f"missing description for {key!r}"


def _result_schema_covers_success_dict(
    schema: Dict[str, Any], result_dict: Dict[str, Any]
) -> None:
    data = result_dict.get("data")
    assert isinstance(data, dict), "success result must include data dict"
    top_props = schema.get("properties") or {}
    data_schema = top_props.get("data")
    assert isinstance(data_schema, dict), "result schema must document data"

    def walk(obj_schema: Dict[str, Any], payload: Dict[str, Any]) -> None:
        otype = obj_schema.get("type")
        if otype == "object":
            props = obj_schema.get("properties") or {}
            for key, val in payload.items():
                assert key in props, f"result data key {key!r} not in schema"
                sub = props[key]
                if isinstance(val, dict) and isinstance(sub, dict):
                    st = sub.get("type")
                    if st == "object" or (isinstance(st, list) and "object" in st):
                        walk(sub, val)
        elif isinstance(otype, list):
            if isinstance(payload, dict) and "object" in otype:
                walk(
                    {"type": "object", "properties": obj_schema.get("properties", {})},
                    payload,
                )

    walk(data_schema, data)


@pytest.mark.parametrize("cmd_cls", TRANSFER_COMMAND_CLASSES)
def test_transfer_commands_have_at_least_two_examples_each(
    cmd_cls: Type[Command],
) -> None:
    schema = cmd_cls.get_schema()
    examples = schema.get("examples")
    assert isinstance(examples, list) and len(examples) >= 2
    for ex in examples:
        assert ex.get("command") == cmd_cls.name
        assert isinstance(ex.get("params"), dict)
        assert isinstance(ex.get("description"), str) and ex["description"].strip()


@pytest.mark.parametrize("cmd_cls", TRANSFER_COMMAND_CLASSES)
def test_transfer_command_example_params_validate(cmd_cls: Type[Command]) -> None:
    schema = cmd_cls.get_schema()
    for ex in schema["examples"]:
        cmd = cmd_cls()
        _assert_example_params_validate(cmd, ex["params"])


@pytest.mark.parametrize("cmd_cls", TRANSFER_COMMAND_CLASSES)
def test_transfer_command_param_schema_descriptions(cmd_cls: Type[Command]) -> None:
    schema = cmd_cls.get_schema()
    desc = schema.get("description")
    assert isinstance(desc, str) and desc.strip()
    _assert_schema_properties_have_descriptions(schema)


@pytest.mark.parametrize("cmd_cls", TRANSFER_COMMAND_CLASSES)
def test_transfer_command_result_schema_non_empty(cmd_cls: Type[Command]) -> None:
    rs = cmd_cls.get_result_schema()
    assert isinstance(rs, dict)
    assert rs.get("type") == "object"
    assert rs.get("properties")


@pytest.mark.asyncio
async def test_transfer_upload_begin_smoke_result_matches_result_schema(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(
        "mcp_proxy_adapter.api.handlers.DEFAULT_STORAGE_DIR",
        str(tmp_path / "ts"),
        raising=False,
    )
    monkeypatch.setattr("mcp_proxy_adapter.api.handlers._CACHED_TRANSFER_STORE", None)
    content = b"hello-schema"
    digest = hashlib.sha256(content).hexdigest()
    cmd = TransferUploadBeginCommand()
    result = await cmd.execute(
        filename="blob.bin",
        size_bytes=len(content),
        checksum_value=digest,
        compression="identity",
        context={},
    )
    assert result.to_dict()["success"] is True
    _result_schema_covers_success_dict(
        TransferUploadBeginCommand.get_result_schema(), result.to_dict()
    )


@pytest.mark.asyncio
async def test_transfer_upload_status_smoke_result_matches_result_schema(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(
        "mcp_proxy_adapter.api.handlers.DEFAULT_STORAGE_DIR",
        str(tmp_path / "ts"),
        raising=False,
    )
    monkeypatch.setattr("mcp_proxy_adapter.api.handlers._CACHED_TRANSFER_STORE", None)
    content = b"hi-status"
    digest = hashlib.sha256(content).hexdigest()
    begin = TransferUploadBeginCommand()
    r1 = await begin.execute(
        filename="f.bin",
        size_bytes=len(content),
        checksum_value=digest,
        compression="identity",
        context={},
    )
    tid = r1.to_dict()["data"]["transfer_id"]
    cmd = TransferUploadStatusCommand()
    result = await cmd.execute(transfer_id=tid, context={})
    assert result.to_dict()["success"] is True
    _result_schema_covers_success_dict(
        TransferUploadStatusCommand.get_result_schema(), result.to_dict()
    )


@pytest.mark.asyncio
async def test_transfer_upload_complete_smoke_result_matches_result_schema(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(
        "mcp_proxy_adapter.api.handlers.DEFAULT_STORAGE_DIR",
        str(tmp_path / "ts"),
        raising=False,
    )
    monkeypatch.setattr("mcp_proxy_adapter.api.handlers._CACHED_TRANSFER_STORE", None)
    content = b"complete-me"
    digest = hashlib.sha256(content).hexdigest()
    begin = TransferUploadBeginCommand()
    r1 = await begin.execute(
        filename="c.bin",
        size_bytes=len(content),
        checksum_value=digest,
        compression="identity",
        context={},
    )
    tid = r1.to_dict()["data"]["transfer_id"]
    from fastapi.testclient import TestClient

    from mcp_proxy_adapter.api.app import create_app

    app = create_app(title="t", description="t", version="1.0.0")
    client = TestClient(app)
    put = client.put(
        f"/api/transfer/uploads/{tid}/chunks?offset=0",
        content=content,
    )
    assert put.status_code == 200, put.text
    cmd = TransferUploadCompleteCommand()
    result = await cmd.execute(transfer_id=tid, context={})
    assert result.to_dict()["success"] is True
    _result_schema_covers_success_dict(
        TransferUploadCompleteCommand.get_result_schema(), result.to_dict()
    )


@pytest.mark.asyncio
async def test_transfer_download_begin_smoke_result_matches_result_schema(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(
        "mcp_proxy_adapter.api.handlers.DEFAULT_STORAGE_DIR",
        str(tmp_path / "ts"),
        raising=False,
    )
    monkeypatch.setattr("mcp_proxy_adapter.api.handlers._CACHED_TRANSFER_STORE", None)
    src = tmp_path / "src.dat"
    src.write_bytes(b"hello-download")
    cmd = TransferDownloadBeginCommand()
    result = await cmd.execute(
        source_path=str(src.resolve()),
        filename="src.dat",
        compression="identity",
        context={},
    )
    assert result.to_dict()["success"] is True
    _result_schema_covers_success_dict(
        TransferDownloadBeginCommand.get_result_schema(), result.to_dict()
    )


@pytest.mark.asyncio
async def test_transfer_download_status_smoke_result_matches_result_schema(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(
        "mcp_proxy_adapter.api.handlers.DEFAULT_STORAGE_DIR",
        str(tmp_path / "ts"),
        raising=False,
    )
    monkeypatch.setattr("mcp_proxy_adapter.api.handlers._CACHED_TRANSFER_STORE", None)
    src = tmp_path / "src2.dat"
    src.write_bytes(b"xyz")
    begin = TransferDownloadBeginCommand()
    r1 = await begin.execute(
        source_path=str(src.resolve()),
        filename="src2.dat",
        compression="identity",
        context={},
    )
    tid = r1.to_dict()["data"]["transfer_id"]
    cmd = TransferDownloadStatusCommand()
    result = await cmd.execute(transfer_id=tid, context={})
    assert result.to_dict()["success"] is True
    _result_schema_covers_success_dict(
        TransferDownloadStatusCommand.get_result_schema(), result.to_dict()
    )


@pytest.mark.parametrize("cmd_cls", TRANSFER_COMMAND_CLASSES)
def test_transfer_descr_minimum_length(cmd_cls: Type[Command]) -> None:
    assert len(cmd_cls.descr.strip()) >= MIN_DESCR_LEN
