import json
from dataclasses import replace
from types import SimpleNamespace

from cognite.client.data_classes.data_modeling import DataModelId

from cog_mcp.resources import (
    EXCLUDED_VIEWS,
    FILTER_DOCS,
    QUERY_DOCS,
    fetch_view_schema,
    fetch_views,
    register_resources,
)


def test_fetch_views_continues_when_one_data_model_lookup_fails(
    cognite_client_mock, sample_config, caplog
) -> None:
    config = replace(
        sample_config,
        data_models=[
            DataModelId(space="model-space", external_id="ModelA", version="1"),
            DataModelId(space="model-space", external_id="ModelB", version="1"),
        ],
    )
    model = SimpleNamespace(
        space="model-space",
        external_id="ModelA",
        version="1",
        name="Model A",
        description="model",
        views=[],
    )
    cognite_client_mock.data_modeling.data_models.retrieve.side_effect = [
        [model],
        RuntimeError("boom"),
    ]

    with caplog.at_level("ERROR"):
        payload = json.loads(fetch_views(cognite_client_mock, config))

    assert len(payload) == 1
    assert payload[0]["externalId"] == "ModelA"
    assert "Failed to retrieve data model" in caplog.text


def test_fetch_views_excludes_abstract_and_3d_views(cognite_client_mock, sample_config) -> None:
    views = [
        SimpleNamespace(
            space="cdf_cdm", external_id="CogniteAsset", version="v1", description=None, name=None
        ),
        SimpleNamespace(
            space="cdf_cdm",
            external_id="CogniteDescribable",
            version="v1",
            description=None,
            name=None,
        ),
        SimpleNamespace(
            space="cdf_cdm",
            external_id="Cognite3DModel",
            version="v1",
            description=None,
            name=None,
        ),
        SimpleNamespace(
            space="custom",
            external_id="CogniteDescribable",
            version="v1",
            description=None,
            name=None,
        ),
    ]
    model = SimpleNamespace(
        space="model-space",
        external_id="Model",
        version="1",
        name="Model",
        description=None,
        views=views,
    )
    cognite_client_mock.data_modeling.data_models.retrieve.return_value = [model]

    payload = json.loads(fetch_views(cognite_client_mock, sample_config))

    view_ids = [(v["space"], v["externalId"]) for v in payload[0]["views"]]
    assert ("cdf_cdm", "CogniteAsset") in view_ids
    assert ("cdf_cdm", "CogniteDescribable") not in view_ids
    assert ("cdf_cdm", "Cognite3DModel") not in view_ids
    assert ("custom", "CogniteDescribable") in view_ids


def test_excluded_views_contains_all_expected_entries() -> None:
    expected = {
        ("cdf_cdm", "CogniteDescribable", "v1"),
        ("cdf_cdm", "CogniteSourceable", "v1"),
        ("cdf_cdm", "CogniteSchedulable", "v1"),
        ("cdf_cdm", "CogniteVisualizable", "v1"),
        ("cdf_cdm", "Cognite3DTransformation", "v1"),
        ("cdf_cdm", "Cognite3DObject", "v1"),
        ("cdf_cdm", "Cognite3DModel", "v1"),
        ("cdf_cdm", "Cognite3DRevision", "v1"),
        ("cdf_cdm", "CogniteCADModel", "v1"),
        ("cdf_cdm", "CogniteCADRevision", "v1"),
        ("cdf_cdm", "CogniteCADNode", "v1"),
        ("cdf_cdm", "CognitePointCloudModel", "v1"),
        ("cdf_cdm", "CognitePointCloudRevision", "v1"),
        ("cdf_cdm", "CognitePointCloudVolume", "v1"),
        ("cdf_cdm", "Cognite360ImageModel", "v1"),
        ("cdf_cdm", "Cognite360ImageCollection", "v1"),
        ("cdf_cdm", "Cognite360Image", "v1"),
        ("cdf_cdm", "Cognite360ImageStation", "v1"),
        ("cdf_cdm", "Cognite360ImageAnnotation", "v1"),
        ("cdf_cdm", "CogniteCubeMap", "v1"),
    }
    assert expected == EXCLUDED_VIEWS


def test_fetch_view_schema_returns_formatted_schema_when_view_exists(cognite_client_mock) -> None:
    view = SimpleNamespace(
        space="asset-space",
        external_id="AssetView",
        version="v1",
        name="Asset View",
        description="Assets",
        properties={},
        implements=[],
    )
    cognite_client_mock.data_modeling.views.retrieve.return_value = [view]

    payload = json.loads(fetch_view_schema(cognite_client_mock, "asset-space", "AssetView", "v1"))

    assert payload["space"] == "asset-space"
    assert payload["externalId"] == "AssetView"
    assert payload["version"] == "v1"


def test_fetch_view_schema_returns_error_when_view_not_found(cognite_client_mock) -> None:
    cognite_client_mock.data_modeling.views.retrieve.return_value = []

    payload = json.loads(fetch_view_schema(cognite_client_mock, "s", "missing", "v1"))

    assert payload["error"] == "View s/missing/v1 not found"


def test_register_resources_registers_all_handlers_and_returns_docs(
    fake_mcp, cognite_client_mock, sample_config
) -> None:
    register_resources(fake_mcp, cognite_client_mock, sample_config)

    assert set(fake_mcp.resources) == {
        "cdf://views",
        "cdf://views/{space}/{external_id}/{version}",
        "cdf://docs/filters",
        "cdf://docs/querying",
    }

    cognite_client_mock.data_modeling.data_models.retrieve.return_value = []
    views_payload = json.loads(fake_mcp.resources["cdf://views"]())
    assert views_payload == []

    cognite_client_mock.data_modeling.views.retrieve.return_value = []
    schema_payload = json.loads(
        fake_mcp.resources["cdf://views/{space}/{external_id}/{version}"]("s", "missing", "v1")
    )
    assert "not found" in schema_payload["error"]

    assert fake_mcp.resources["cdf://docs/filters"]() == FILTER_DOCS
    assert fake_mcp.resources["cdf://docs/querying"]() == QUERY_DOCS
