from __future__ import annotations

import json

import pytest

from a2a_agent.config import ShareConfig
from a2a_agent.protocol import build_file_part
from a2a_agent.protocol import parse_file_part
from a2a_agent.share import FileStationClient
from a2a_agent.share import HttpRequest
from a2a_agent.share import HttpResponse
from a2a_agent.share import ShareError


class FakeTransport:
    def __init__(self) -> None:
        self.calls: list[HttpRequest] = []

    def __call__(self, http_request: HttpRequest) -> HttpResponse:
        self.calls.append(http_request)
        # 업로드는 api/method가 멀티파트 form 필드에, 나머지는 params(쿼리)에 있다.
        fields = http_request.fields or {}
        api = http_request.params.get("api") or fields.get("api")
        method = http_request.params.get("method") or fields.get("method")
        if api == "SYNO.API.Auth" and method == "login":
            return _json_response({"success": True, "data": {"sid": "sid-123"}})
        if api == "SYNO.API.Auth" and method == "logout":
            return _json_response({"success": True})
        if api == "SYNO.FileStation.Upload":
            return _json_response({"success": True})
        if api == "SYNO.FileStation.Download":
            return HttpResponse(status=200, body=b"downloaded bytes")
        if api == "SYNO.FileStation.Delete":
            return _json_response({"success": True})
        raise AssertionError(f"unexpected API call: {api}.{method}")


def test_filestation_upload_download_delete_are_mockable(tmp_path) -> None:
    source = tmp_path / "photo.jpg"
    source.write_bytes(b"image bytes")
    destination = tmp_path / "downloads" / "photo.jpg"
    transport = FakeTransport()
    client = FileStationClient(
        ShareConfig(
            host="nas.example.com",
            port=5001,
            user="a2a-worker",
            password="secret",
            folder="/a2a-share",
            insecure=True,
        ),
        transport=transport,
    )

    remote_path = client.upload(source, "task-001")
    downloaded = client.download("task-001/photo.jpg", destination)
    client.delete("task-001/photo.jpg")
    client.logout()

    assert remote_path == "/a2a-share/task-001/photo.jpg"
    assert client.relative_path(remote_path) == "task-001/photo.jpg"
    assert downloaded == destination
    assert destination.read_bytes() == b"downloaded bytes"

    upload_request = _call_for(transport, "SYNO.FileStation.Upload")
    assert upload_request.method == "POST"
    assert upload_request.fields == {
        "api": "SYNO.FileStation.Upload",
        "version": "2",
        "method": "upload",
        "path": "/a2a-share/task-001",
        "create_parents": "true",
        "overwrite": "true",
    }
    # _sid는 본문이 아니라 URL 쿼리(params)로 전달되어야 한다.
    assert upload_request.params.get("_sid")
    assert upload_request.files[0].filename == "photo.jpg"
    assert upload_request.files[0].content == b"image bytes"
    assert upload_request.insecure is True

    download_request = _call_for(transport, "SYNO.FileStation.Download")
    assert download_request.params["path"] == "/a2a-share/task-001/photo.jpg"
    assert download_request.params["mode"] == "download"

    delete_request = _call_for(transport, "SYNO.FileStation.Delete")
    assert delete_request.params["path"] == "/a2a-share/task-001/photo.jpg"


def test_filestation_rejects_paths_outside_share() -> None:
    client = FileStationClient(
        ShareConfig(user="a2a-worker", password="secret", folder="/a2a-share"),
        transport=FakeTransport(),
    )

    with pytest.raises(ShareError):
        client.absolute_path("../outside.txt")
    with pytest.raises(ShareError):
        client.absolute_path("/other-share/file.txt")


def test_file_part_helpers_round_trip() -> None:
    part = build_file_part("report.pdf", "task-001/report.pdf", 42)

    parsed = parse_file_part(part)

    assert parsed.name == "report.pdf"
    assert parsed.path == "task-001/report.pdf"
    assert parsed.size == 42


def _json_response(payload: dict[str, object]) -> HttpResponse:
    return HttpResponse(status=200, body=json.dumps(payload).encode("utf-8"))


def _call_for(transport: FakeTransport, api: str) -> HttpRequest:
    def call_api(call: HttpRequest) -> str | None:
        return call.params.get("api") or (call.fields or {}).get("api")

    matches = [call for call in transport.calls if call_api(call) == api]
    assert matches
    return matches[-1]
