"""Comprehensive tests for a2a_client.py — 100% statement coverage.

Tests every async function:  discover_peer, send_a2a_message, get_peers,
get_workspace_info.  Each test covers exactly one execution path so failures
are easy to diagnose.
"""

import sys
import os
import importlib
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_mock_client(*, get_resp=None, post_resp=None, get_exc=None, post_exc=None):
    """Build a reusable AsyncClient context-manager mock."""
    mock_client = AsyncMock()
    mock_client.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client.__aexit__ = AsyncMock(return_value=False)

    if get_exc is not None:
        mock_client.get = AsyncMock(side_effect=get_exc)
    elif get_resp is not None:
        mock_client.get = AsyncMock(return_value=get_resp)

    if post_exc is not None:
        mock_client.post = AsyncMock(side_effect=post_exc)
    elif post_resp is not None:
        mock_client.post = AsyncMock(return_value=post_resp)

    return mock_client


def _make_response(status_code, json_data):
    resp = MagicMock()
    resp.status_code = status_code
    resp.json = MagicMock(return_value=json_data)
    return resp


# ---------------------------------------------------------------------------
# Module-level constants (just ensure they exist and have sensible types)
# ---------------------------------------------------------------------------

def test_constants_exist():
    import a2a_client
    assert isinstance(a2a_client.PLATFORM_URL, str)
    assert isinstance(a2a_client.WORKSPACE_ID, str)
    assert isinstance(a2a_client._A2A_ERROR_PREFIX, str)
    assert isinstance(a2a_client._peer_names, dict)


# ---------------------------------------------------------------------------
# discover_peer
# ---------------------------------------------------------------------------

class TestDiscoverPeer:

    async def test_success_returns_json_on_200(self):
        """200 response → returns the JSON body."""
        import a2a_client

        peer_data = {"id": "ws-abc", "url": "http://ws-abc.svc", "name": "Alpha"}
        resp = _make_response(200, peer_data)
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.discover_peer("ws-abc")

        assert result == peer_data

    async def test_non_200_returns_none(self):
        """Non-200 response → returns None."""
        import a2a_client

        resp = _make_response(404, {"detail": "not found"})
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.discover_peer("ws-missing")

        assert result is None

    async def test_403_returns_none(self):
        """403 forbidden → returns None (any non-200 code)."""
        import a2a_client

        resp = _make_response(403, {"detail": "forbidden"})
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.discover_peer("ws-forbidden")

        assert result is None

    async def test_exception_returns_none(self):
        """Network exception → returns None (exception swallowed)."""
        import a2a_client

        mock_client = _make_mock_client(get_exc=ConnectionError("host unreachable"))

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.discover_peer("ws-down")

        assert result is None

    async def test_request_uses_correct_url_and_header(self):
        """GET is called with the right URL and X-Workspace-ID header."""
        import a2a_client

        resp = _make_response(200, {"url": "http://target"})
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            await a2a_client.discover_peer("ws-xyz")

        mock_client.get.assert_called_once()
        positional_url = mock_client.get.call_args.args[0]
        assert "ws-xyz" in positional_url
        # X-Workspace-ID must be present; bearer token also merged in when available
        headers_sent = mock_client.get.call_args.kwargs.get("headers", {})
        assert headers_sent.get("X-Workspace-ID") == a2a_client.WORKSPACE_ID


# ---------------------------------------------------------------------------
# send_a2a_message
# ---------------------------------------------------------------------------

class TestSendA2AMessage:

    async def test_result_with_text_part_returns_text(self):
        """'result' key with text parts → returns the text."""
        import a2a_client

        resp = _make_response(200, {
            "result": {"parts": [{"kind": "text", "text": "Hello!"}]}
        })
        mock_client = _make_mock_client(post_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.send_a2a_message("http://target/a2a", "ping")

        assert result == "Hello!"

    async def test_result_with_empty_parts_returns_no_response(self):
        """'result' key with empty parts list → returns '(no response)'."""
        import a2a_client

        resp = _make_response(200, {"result": {"parts": []}})
        mock_client = _make_mock_client(post_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.send_a2a_message("http://target/a2a", "ping")

        assert result == "(no response)"

    async def test_result_text_starts_with_agent_error_gets_prefix(self):
        """Text starting with 'Agent error:' gets the _A2A_ERROR_PREFIX prepended."""
        import a2a_client

        resp = _make_response(200, {
            "result": {"parts": [{"kind": "text", "text": "Agent error: something bad"}]}
        })
        mock_client = _make_mock_client(post_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.send_a2a_message("http://target/a2a", "task")

        assert result.startswith(a2a_client._A2A_ERROR_PREFIX)
        assert "Agent error: something bad" in result

    async def test_error_key_returns_error_prefix_and_message(self):
        """'error' key in response → returns _A2A_ERROR_PREFIX + error message."""
        import a2a_client

        resp = _make_response(200, {
            "error": {"code": -32603, "message": "Internal error occurred"}
        })
        mock_client = _make_mock_client(post_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.send_a2a_message("http://target/a2a", "task")

        assert result.startswith(a2a_client._A2A_ERROR_PREFIX)
        assert "Internal error occurred" in result

    async def test_error_key_missing_message_returns_unknown(self):
        """'error' key without 'message' → falls back to 'unknown'."""
        import a2a_client

        resp = _make_response(200, {"error": {"code": -32600}})
        mock_client = _make_mock_client(post_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.send_a2a_message("http://target/a2a", "task")

        assert result.startswith(a2a_client._A2A_ERROR_PREFIX)
        assert "unknown" in result

    async def test_neither_result_nor_error_returns_str_of_data(self):
        """Response with neither 'result' nor 'error' → str(data)."""
        import a2a_client

        payload = {"jsonrpc": "2.0", "id": "abc123"}
        resp = _make_response(200, payload)
        mock_client = _make_mock_client(post_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.send_a2a_message("http://target/a2a", "task")

        assert result == str(payload)

    async def test_exception_returns_error_prefix_and_message(self):
        """Network exception → returns _A2A_ERROR_PREFIX + exception text."""
        import a2a_client

        mock_client = _make_mock_client(post_exc=ConnectionError("connection refused"))

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.send_a2a_message("http://target/a2a", "task")

        assert result.startswith(a2a_client._A2A_ERROR_PREFIX)
        assert "connection refused" in result

    async def test_result_text_part_missing_text_key_returns_empty(self):
        """Part dict without 'text' key → falls back to '' (empty string returned)."""
        import a2a_client

        resp = _make_response(200, {
            "result": {"parts": [{"kind": "text"}]}  # no "text" key
        })
        mock_client = _make_mock_client(post_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.send_a2a_message("http://target/a2a", "task")

        # Returns "" (empty string — does not start with _A2A_ERROR_PREFIX)
        assert result == ""


# ---------------------------------------------------------------------------
# get_peers
# ---------------------------------------------------------------------------

class TestGetPeers:

    async def test_success_returns_list_on_200(self):
        """200 response → returns the JSON list."""
        import a2a_client

        peers = [{"id": "ws-1", "name": "Alpha"}, {"id": "ws-2", "name": "Beta"}]
        resp = _make_response(200, peers)
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.get_peers()

        assert result == peers

    async def test_non_200_returns_empty_list(self):
        """Non-200 response → returns []."""
        import a2a_client

        resp = _make_response(503, {"detail": "service unavailable"})
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.get_peers()

        assert result == []

    async def test_404_returns_empty_list(self):
        """404 response → returns []."""
        import a2a_client

        resp = _make_response(404, {"detail": "not found"})
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.get_peers()

        assert result == []

    async def test_exception_returns_empty_list(self):
        """Network exception → returns [] (exception swallowed)."""
        import a2a_client

        mock_client = _make_mock_client(get_exc=TimeoutError("timed out"))

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.get_peers()

        assert result == []

    async def test_request_url_includes_workspace_id(self):
        """GET URL contains the WORKSPACE_ID."""
        import a2a_client

        resp = _make_response(200, [])
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            await a2a_client.get_peers()

        url = mock_client.get.call_args.args[0]
        assert "peers" in url

    async def test_request_sends_workspace_id_header(self):
        """GET /registry/:id/peers must send X-Workspace-ID header (Phase 30.6)."""
        import a2a_client

        resp = _make_response(200, [])
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            await a2a_client.get_peers()

        headers_sent = mock_client.get.call_args.kwargs.get("headers", {})
        assert headers_sent.get("X-Workspace-ID") == a2a_client.WORKSPACE_ID


# ---------------------------------------------------------------------------
# get_workspace_info
# ---------------------------------------------------------------------------

class TestGetWorkspaceInfo:

    async def test_success_returns_dict_on_200(self):
        """200 response → returns the JSON dict."""
        import a2a_client

        info = {"id": "ws-test", "name": "Test Workspace", "status": "online"}
        resp = _make_response(200, info)
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.get_workspace_info()

        assert result == info

    async def test_non_200_returns_error_dict(self):
        """Non-200 response → returns {'error': 'not found'}."""
        import a2a_client

        resp = _make_response(404, {"detail": "no such workspace"})
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.get_workspace_info()

        assert result == {"error": "not found"}

    async def test_500_returns_error_dict(self):
        """500 response → returns {'error': 'not found'}."""
        import a2a_client

        resp = _make_response(500, {"detail": "server error"})
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.get_workspace_info()

        assert result == {"error": "not found"}

    async def test_exception_returns_error_dict_with_message(self):
        """Network exception → returns {'error': '<exception message>'}."""
        import a2a_client

        exc = RuntimeError("network failure")
        mock_client = _make_mock_client(get_exc=exc)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            result = await a2a_client.get_workspace_info()

        assert "error" in result
        assert "network failure" in result["error"]

    async def test_request_url_includes_workspaces_path(self):
        """GET URL contains /workspaces/."""
        import a2a_client

        resp = _make_response(200, {})
        mock_client = _make_mock_client(get_resp=resp)

        with patch("a2a_client.httpx.AsyncClient", return_value=mock_client):
            await a2a_client.get_workspace_info()

        url = mock_client.get.call_args.args[0]
        assert "/workspaces/" in url
