import json
from typing import Any

from cognite.client.data_classes import filters
from cognite.client.data_classes.data_modeling.ids import EdgeId, NodeId, PropertyId


def parse_json_param(value: str | dict | list | None) -> Any | None:
    """Parse a JSON parameter that may arrive as a string or already-parsed object.

    Some MCP clients auto-parse JSON strings into dicts/lists before passing
    them to tool functions. This helper handles both cases.
    """
    if value is None:
        return None
    if isinstance(value, str):
        return json.loads(value)
    return value


def _parse_instance_id(value: Any, field_name: str, id_type: type[NodeId] | type[EdgeId]) -> Any:
    """Parse a single NodeId or EdgeId from a dict-like payload."""
    if isinstance(value, id_type):
        return value

    if not isinstance(value, dict):
        raise ValueError(f"{field_name} must be an object with 'space' and 'externalId'")

    space = value.get("space")
    external_id = value.get("externalId") or value.get("external_id")
    if not isinstance(space, str) or not space.strip():
        raise ValueError(f"{field_name} must include a non-empty 'space'")
    if not isinstance(external_id, str) or not external_id.strip():
        raise ValueError(f"{field_name} must include a non-empty 'externalId'")

    return id_type(space=space, external_id=external_id)


def parse_node_id(value: Any, field_name: str) -> NodeId:
    """Parse a single NodeId from a dict-like payload."""
    return _parse_instance_id(value, field_name, NodeId)


def parse_edge_id(value: Any, field_name: str) -> EdgeId:
    """Parse a single EdgeId from a dict-like payload."""
    return _parse_instance_id(value, field_name, EdgeId)


def parse_node_ids(
    raw: str | list | dict, *, field_name: str = "node_ids", max_items: int = 100
) -> list[NodeId]:
    """Parse and validate a list of node IDs."""
    parsed = parse_json_param(raw)
    if isinstance(parsed, dict):
        parsed = [parsed]

    if not isinstance(parsed, list) or not parsed:
        raise ValueError(f"{field_name} must be a non-empty list of instance IDs")
    if len(parsed) > max_items:
        raise ValueError(f"{field_name} supports a maximum of {max_items} items")

    return [parse_node_id(item, f"{field_name}[{index}]") for index, item in enumerate(parsed)]


def parse_through(through_raw: dict | None) -> PropertyId | None:
    """Convert a raw ``through`` dict to a ``PropertyId``.

    The SDK constructor only accepts ``PropertyId``, list, or tuple — not a
    plain dict.  ``PropertyId.load`` handles dicts but requires the ``source``
    to carry a ``"type"`` key (``"view"`` or ``"container"``).  We infer the
    type from the presence of ``"version"`` when it is not already set.
    """
    if through_raw is None:
        return None
    source = through_raw.get("source", {})
    if "type" not in source:
        source["type"] = "view" if "version" in source else "container"
    return PropertyId.load(through_raw)


def inject_space_filter(
    instance_type: str,
    user_filter: dict | None,
    instance_spaces: list[str],
) -> filters.Filter:
    """Build a space filter for the given instance type, merging with any user filter."""
    space_filter = filters.In([instance_type, "space"], instance_spaces)
    if user_filter:
        return filters.And(space_filter, filters.Filter.load(user_filter))
    return space_filter
