import json
from types import SimpleNamespace

from cog_mcp.formatting import (
    _clean_prop_info,
    format_aggregation,
    format_data_models_overview,
    format_document_answer,
    format_document_summary,
    format_instances,
    format_query_results,
    format_view_schema,
)


def test_clean_prop_info_removes_storage_specific_keys() -> None:
    raw = {
        "type": {"list": False, "container": "container-x"},
        "container": "container-y",
        "containerPropertyIdentifier": "prop-id",
        "immutable": True,
        "autoIncrement": True,
        "constraintState": "ok",
        "nullable": False,
    }

    cleaned = _clean_prop_info(raw)

    assert "container" not in cleaned
    assert "containerPropertyIdentifier" not in cleaned
    assert "immutable" not in cleaned
    assert "autoIncrement" not in cleaned
    assert "constraintState" not in cleaned
    assert cleaned["type"] == {"list": False}
    assert cleaned["nullable"] is False


def test_format_view_schema_includes_property_reference_and_implements() -> None:
    class PropertyWithDump:
        def dump(self, camel_case: bool = True) -> dict:
            assert camel_case is True
            return {
                "type": {"type": "text", "container": "drop-me"},
                "container": "drop-me",
                "containerPropertyIdentifier": "drop-me",
                "nullable": True,
            }

    view = SimpleNamespace(
        space="my-space",
        external_id="MyView",
        version="v1",
        name="My View",
        description="A test view",
        properties={"name": PropertyWithDump()},
        implements=[SimpleNamespace(space="base-space", external_id="BaseView", version="v1")],
    )

    payload = json.loads(format_view_schema(view))

    assert payload["space"] == "my-space"
    assert payload["externalId"] == "MyView"
    assert payload["version"] == "v1"
    assert payload["propertyReference"] == '["my-space", "MyView/v1", "<propertyName>"]'
    assert payload["properties"]["name"]["type"] == {"type": "text"}
    assert payload["implements"] == [
        {"space": "base-space", "externalId": "BaseView", "version": "v1"}
    ]


def test_format_data_models_overview_handles_optional_metadata() -> None:
    view = SimpleNamespace(
        space="view-space",
        external_id="PumpView",
        version="1",
        description="Pump details",
        name="Pump View",
    )
    model_with_views = SimpleNamespace(
        space="model-space",
        external_id="ModelA",
        version="1",
        name="Model A",
        description="Main model",
        views=[view],
    )
    model_without_views = SimpleNamespace(
        space="model-space",
        external_id="ModelB",
        version="1",
        name=None,
        description=None,
        views=None,
    )

    payload = json.loads(format_data_models_overview([model_with_views, model_without_views]))

    assert payload[0]["views"][0] == {
        "space": "view-space",
        "externalId": "PumpView",
        "version": "1",
        "description": "Pump details",
        "name": "Pump View",
    }
    assert payload[1]["views"] == []
    assert payload[1]["name"] is None


def test_format_instances_supports_multiple_input_shapes(dumpable_factory) -> None:
    dump_item = dumpable_factory({"externalId": "dumped", "space": "space-a"})
    source_key = ("my-space", "MyView/v1")
    prop_item = SimpleNamespace(
        space="space-a",
        external_id="fallback-item",
        instance_type="node",
        properties={source_key: {"name": "pump-1"}},
    )
    plain_item = "raw-item"

    payload = json.loads(format_instances([dump_item, prop_item, plain_item], total=10))

    assert payload["count"] == 3
    assert payload["total"] == 10
    assert payload["message"] == "Showing 3 of 10 instances."
    assert payload["items"][0]["externalId"] == "dumped"
    assert payload["items"][1]["externalId"] == "fallback-item"
    assert payload["items"][1]["properties"]["('my-space', 'MyView/v1')"] == {"name": "pump-1"}
    assert payload["items"][2] == "raw-item"


def test_format_query_results_handles_cursor_and_non_dump_iterables(dumpable_factory) -> None:
    class DumpList:
        def __init__(self, data: list[dict], cursor: str | None = None) -> None:
            self._data = data
            self.cursor = cursor

        def dump(self, camel_case: bool = True) -> list[dict]:
            assert camel_case is True
            return self._data

    query_result = {
        "orders": DumpList([{"externalId": "o-1"}], cursor="cursor-1"),
        "assets": [SimpleNamespace(external_id="a-1")],
    }

    payload = json.loads(format_query_results(query_result))

    assert payload["orders"]["count"] == 1
    assert payload["orders"]["cursor"] == "cursor-1"
    assert payload["orders"]["items"][0]["externalId"] == "o-1"
    assert payload["assets"]["count"] == 1
    assert payload["assets"]["items"] == ["namespace(external_id='a-1')"]


def test_format_aggregation_includes_hint_and_serializes_values(dumpable_factory) -> None:
    agg_result = [
        dumpable_factory({"value": 5}),
        {"group": {"status": "planned"}, "value": 2},
        SimpleNamespace(value=3),
    ]

    payload = json.loads(format_aggregation(agg_result, hint="diagnostic hint"))

    assert payload["hint"] == "diagnostic hint"
    assert payload["results"][0] == {"value": 5}
    assert payload["results"][1]["group"] == {"status": "planned"}
    assert payload["results"][2]["value"] == 3


def test_format_aggregation_wraps_scalar_result_in_list() -> None:
    payload = json.loads(format_aggregation(SimpleNamespace(value=42)))

    assert payload == [{"value": 42}]


def test_format_document_answer_formats_content_and_references() -> None:
    location = SimpleNamespace(page_number=4, left=0.1, right=0.9, top=0.2, bottom=0.8)
    reference = SimpleNamespace(
        file_id=123,
        external_id="file-ext",
        file_name="manual.pdf",
        locations=[location],
        instance_id=SimpleNamespace(space="docs", external_id="doc-1"),
    )
    part = SimpleNamespace(text="The pump requires maintenance.", references=[reference])
    answer = SimpleNamespace(content=[part])

    payload = json.loads(format_document_answer(answer))

    assert payload["content"][0]["text"] == "The pump requires maintenance."
    assert payload["content"][0]["references"][0]["fileName"] == "manual.pdf"
    assert payload["content"][0]["references"][0]["instanceId"] == {
        "space": "docs",
        "externalId": "doc-1",
    }
    assert payload["content"][0]["references"][0]["locations"][0]["pageNumber"] == 4


def test_format_document_answer_supports_dump_and_list_fallbacks(dumpable_factory) -> None:
    dumped = json.loads(format_document_answer(dumpable_factory({"answer": "ok"})))
    listed = json.loads(format_document_answer([dumpable_factory({"a": 1}), {"b": 2}]))

    assert dumped == {"answer": "ok"}
    assert listed == [{"a": 1}, {"b": 2}]


def test_format_document_summary_handles_string_dict_and_object() -> None:
    class SummaryWithDump:
        def dump(self, camel_case: bool = True) -> dict:
            assert camel_case is True
            return {"summary": "x"}

    from_dump = json.loads(format_document_summary(SummaryWithDump()))
    from_str = json.loads(format_document_summary("quick summary"))
    from_obj = json.loads(format_document_summary(SimpleNamespace(summary="object summary")))

    assert from_dump == {"summary": "x"}
    assert from_str == {"summary": "quick summary"}
    assert from_obj == {"summary": "object summary"}
