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

import json
from pathlib import Path
from urllib.parse import urlparse

import pytest

from navi.mcp import ui_server
from navi.mcp.ui_server.components import CardGrid, Form
from navi.mcp.ui_server.components.base import UIComponent
from navi.mcp.ui_server.components.registry import ComponentRegistry, discover_components


class TestMcpConfigPointsAtTheServedEndpoint:
    """navi reaches its own navi_ui server the way it reaches any other MCP server:
    through ``mcp_servers.d/navi_ui.json``. Nothing keeps that file in step with the
    port ``server.py`` binds, and it drifted to the pre-8099/8098 defaults — the
    server then failed to connect on every start and ``render_component`` surfaced
    only as an unregistered phantom in list_tools. Assert the file against the live
    FastMCP object, not against a second copy of the same setting.
    """

    @staticmethod
    def _config() -> dict:
        root = Path(__file__).resolve().parents[3]
        return json.loads((root / "mcp_servers.d" / "navi_ui.json").read_text())

    def test_url_is_the_endpoint_the_server_actually_binds(self):
        from navi.mcp.ui_server import server as server_module

        settings = server_module.mcp.settings
        expected = f"http://{settings.host}:{settings.port}{settings.streamable_http_path}"

        assert self._config()["url"] == expected

    def test_host_is_the_loopback_literal_the_server_binds(self):
        """``localhost`` may resolve to ::1; the server binds IPv4 only."""
        assert urlparse(self._config()["url"]).hostname == "127.0.0.1"

    def test_transport_matches_the_mounted_asgi_app(self):
        assert self._config()["transport"] == "streamable_http"


class TestComponentDiscovery:
    def test_discovers_nested_subclasses(self):
        """Intermediate subclasses of UIComponent must be discovered."""
        registry = ComponentRegistry()

        class BaseCard(UIComponent):
            name = "base_card"
            description = "base"
            schema = None

        class RealtorCard(BaseCard):
            name = "realtor_card"
            description = "realtor"
            schema = None

        # Register the intermediate class so it is loaded; discovery should still
        # find the leaf class even though it is not a direct subclass of UIComponent.
        registry.register(BaseCard)
        discover_components(registry)

        assert "realtor_card" in registry.list_names()

    def test_instructions_use_compact_schema(self):
        instructions = CardGrid.instructions()
        assert "### card_grid" in instructions
        assert "Payload schema:" in instructions
        assert "`cards`" in instructions
        assert "(required)" in instructions
        # Raw JSON Schema title boilerplate should be gone.
        assert '"title":' not in instructions


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("card_grid", [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("card_grid", {"x": 1}, None)
        assert "session_id" in result.lower()
        assert "Error" in result

    async def test_rejects_unknown_component(self):
        result = await ui_server.render_component("table", {"rows": 3}, "sess-123")
        assert "unknown component" in result
        assert "card_grid" in result

    async def test_card_grid_rejects_missing_cards(self):
        result = await ui_server.render_component("card_grid", {}, "s1")
        assert "Invalid payload" in result
        assert "cards" in result

    async def test_card_grid_rejects_card_without_id(self):
        result = await ui_server.render_component(
            "card_grid", {"cards": [{"title": "Only title"}]}, "s1"
        )
        assert "Invalid payload" in result
        assert "cards.0.id" in result

    async def test_card_grid_returns_metadata_json(self):
        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
        parsed = json.loads(result)
        assert parsed["metadata"]["ui_component"]["component"] == "card_grid"
        assert parsed["metadata"]["ui_component"]["payload"]["title"] == "Results"
        assert parsed["metadata"]["ui_component"]["payload"]["cards"][0]["id"] == "c1"

    async def test_card_grid_validates_url_fields(self):
        payload = {
            "cards": [
                {
                    "id": "c1",
                    "title": "Flat A",
                    "image": "not-a-url",
                    "actions": [{"label": "Open", "url": "also-not-a-url"}],
                }
            ],
        }
        result = await ui_server.render_component("card_grid", payload, "s1")
        assert "Invalid payload" in result
        assert "cards.0.image" in result
        assert "cards.0.actions.0.url" in result


class TestCardGridComponent:
    def test_schema_rejects_empty_card_list(self):
        ok, error, _ = CardGrid.validate({"cards": []})
        assert not ok
        assert "cards" in error

    def test_schema_accepts_minimal_payload(self):
        ok, error, validated = CardGrid.validate(
            {"cards": [{"id": "c1", "title": "Flat A"}]}
        )
        assert ok
        assert error == ""
        assert validated["cards"][0]["id"] == "c1"

    def test_schema_rejects_too_many_cards(self):
        cards = [{"id": f"c{i}", "title": f"Card {i}"} for i in range(9)]
        ok, error, _ = CardGrid.validate({"cards": cards})
        assert not ok
        assert "cards" in error


class TestFormComponent:
    def test_form_requires_form_id_and_fields(self):
        ok, error, _ = Form.validate({"fields": []})
        assert not ok
        assert "form_id" in error

    def test_form_requires_non_empty_fields(self):
        ok, error, _ = Form.validate({"form_id": "f1", "fields": []})
        assert not ok
        assert "fields" in error

    def test_form_rejects_invalid_field_name(self):
        payload = {
            "form_id": "f1",
            "fields": [
                {"name": "field name with spaces", "label": "Field", "type": "text"}
            ],
        }
        ok, error, _ = Form.validate(payload)
        assert not ok
        assert "field name" in error or "name" in error

    def test_form_select_requires_options(self):
        payload = {
            "form_id": "f1",
            "fields": [{"name": "color", "label": "Color", "type": "select", "options": None}],
        }
        ok, error, _ = Form.validate(payload)
        assert not ok
        assert "options" in error.lower() or "select" in error.lower()

    def test_form_accepts_valid_payload(self):
        payload = {
            "form_id": "f1",
            "title": "Preferences",
            "fields": [
                {
                    "name": "email",
                    "label": "Email",
                    "type": "email",
                    "required": True,
                },
                {
                    "name": "budget",
                    "label": "Budget",
                    "type": "number",
                    "min": 0,
                    "max": 1000000,
                },
            ],
        }
        ok, error, validated = Form.validate(payload)
        assert ok
        assert validated["form_id"] == "f1"
        assert validated["fields"][0]["name"] == "email"

    async def test_render_component_returns_metadata(self):
        payload = {
            "form_id": "f1",
            "fields": [{"name": "name", "label": "Name", "type": "text", "required": True}],
        }
        result = await ui_server.render_component("form", payload, "s1")
        parsed = json.loads(result)
        assert parsed["metadata"]["ui_component"]["component"] == "form"
        assert parsed["metadata"]["ui_component"]["payload"]["form_id"] == "f1"
