"""Unit tests for the internal navi_ui MCP server."""

import pytest
from unittest.mock import AsyncMock

from navi.mcp import ui_server


@pytest.fixture(autouse=True)
def reset_ui_server_state():
    """Reset global orchestrator holder before/after every test."""
    ui_server.clear_orchestrator()
    yield
    ui_server.clear_orchestrator()


class TestRenderComponent:
    async def test_requires_component_name(self):
        result = await ui_server.render_component("", {"x": 1}, "s1")
        assert "component_name" in result.lower()
        assert "Error" in result

    async def test_requires_dict_payload(self):
        result = await ui_server.render_component("table", [1, 2, 3], "s1")
        assert "payload must be a JSON object" in result

    async def test_requires_session_id(self):
        result = await ui_server.render_component("table", {"x": 1}, None)
        assert "session_id" in result.lower()
        assert "Error" in result

    async def test_sends_event_when_orchestrator_ready(self):
        mock_orchestrator = AsyncMock()
        mock_orchestrator._notify_session = AsyncMock()
        ui_server.set_orchestrator(mock_orchestrator)

        result = await ui_server.render_component("table", {"rows": 3}, "sess-123")

        assert "table" in result
        assert "sess-123" in result
        assert "Error" not in result
        mock_orchestrator._notify_session.assert_awaited_once_with(
            "sess-123",
            {
                "type": "ui_component",
                "component": "table",
                "payload": {"rows": 3},
            },
        )

    async def test_times_out_when_orchestrator_never_set(self):
        original_timeout = ui_server._ORCHESTRATOR_TIMEOUT
        ui_server._ORCHESTRATOR_TIMEOUT = 0.05
        try:
            result = await ui_server.render_component("table", {"x": 1}, "s1")
            assert "not ready" in result.lower() or "unavailable" in result.lower()
            assert "Error" in result
        finally:
            ui_server._ORCHESTRATOR_TIMEOUT = original_timeout

    async def test_returns_error_when_notify_session_fails(self):
        mock_orchestrator = AsyncMock()
        mock_orchestrator._notify_session.side_effect = RuntimeError("boom")
        ui_server.set_orchestrator(mock_orchestrator)

        result = await ui_server.render_component("table", {"x": 1}, "s1")

        assert "failed to send" in result.lower()
        assert "Error" in result

    async def test_card_grid_rejects_missing_cards(self):
        ui_server.set_orchestrator(AsyncMock())
        result = await ui_server.render_component("card_grid", {}, "s1")
        assert "'cards' list" in result

    async def test_card_grid_rejects_card_without_id_or_title(self):
        ui_server.set_orchestrator(AsyncMock())
        result = await ui_server.render_component(
            "card_grid", {"cards": [{"title": "Only title"}]}, "s1"
        )
        assert "must have a non-empty string 'id'" in result

    async def test_card_grid_accepts_valid_payload(self):
        mock_orchestrator = AsyncMock()
        mock_orchestrator._notify_session = AsyncMock()
        ui_server.set_orchestrator(mock_orchestrator)

        payload = {
            "title": "Results",
            "cards": [
                {
                    "id": "c1",
                    "title": "Flat A",
                    "subtitle": "Center",
                    "meta": [{"label": "Price", "value": "$80k"}],
                }
            ],
        }
        result = await ui_server.render_component("card_grid", payload, "s1")
        assert "Error" not in result
        mock_orchestrator._notify_session.assert_awaited_once()


class TestOrchestratorHolder:
    def test_set_and_clear(self):
        mock_orchestrator = AsyncMock()
        ui_server.set_orchestrator(mock_orchestrator)
        assert ui_server._orchestrator is mock_orchestrator
        assert ui_server._orchestrator_ready.is_set()

        ui_server.clear_orchestrator()
        assert ui_server._orchestrator is None
        assert not ui_server._orchestrator_ready.is_set()
