"""
ToolContextSnapshot: versioned immutable model-facing tools view (TZ section 7, FR-8).

Built only from CatalogSnapshot; version-aligned with catalog snapshot (NFR-1).
Provides read-only tool descriptors for model consumption: command name, server
binding, parameter definitions, metadata, schema.

Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com
"""

from __future__ import annotations

from dataclasses import dataclass
from types import MappingProxyType
from typing import Any, Mapping, Optional, Sequence, Tuple

from mcp_proxy_adapter.core.orchestration.catalog_snapshot import CatalogSnapshot
from mcp_proxy_adapter.core.orchestration.command_record import CommandRecord
from mcp_proxy_adapter.core.orchestration.server_record import ServerRecord


@dataclass(frozen=True)
class ToolDescriptor:
    """
    Immutable descriptor for one tool in the model-facing context.

    Contains command name, server binding, parameter definitions, metadata,
    and full schema as required for model consumption (FR-8). One descriptor
    per (server_id, command_name) from the active catalog.
    """

    command_name: str
    """Command identifier (e.g. method name)."""

    server_id: str
    """Server binding: server identifier (UUID4)."""

    host: str
    """Server host from ServerRecord."""

    port: int
    """Server port from ServerRecord."""

    summary: Optional[str]
    """Short human-readable description of the command."""

    parameter_definitions: Mapping[str, Any]
    """Parameter definitions (name -> type/constraint info)."""

    metadata: Optional[Mapping[str, Any]]
    """Optional command metadata (author, version, category, etc.)."""

    schema: Mapping[str, Any]
    """Full JSON schema for the command."""

    required_parameters: Sequence[str]
    """Required parameter names (from schema/graph)."""


def _freeze_mapping(value: Optional[Mapping[str, Any]]) -> Optional[Mapping[str, Any]]:
    """Return immutable view of mapping; None remains None."""
    if value is None:
        return None
    if isinstance(value, MappingProxyType):
        return value
    return MappingProxyType(dict(value))


def _build_descriptor(
    server: ServerRecord,
    command: CommandRecord,
) -> ToolDescriptor:
    """Build one ToolDescriptor from ServerRecord and CommandRecord."""
    return ToolDescriptor(
        command_name=command.command_name,
        server_id=server.server_id,
        host=server.host,
        port=server.port,
        summary=command.summary,
        parameter_definitions=_freeze_mapping(command.parameter_definitions)
        or MappingProxyType({}),
        metadata=_freeze_mapping(command.metadata),
        schema=_freeze_mapping(command.schema) or MappingProxyType({}),
        required_parameters=tuple(command.required_parameters),
    )


def _build_tools_from_catalog(catalog: CatalogSnapshot) -> Tuple[ToolDescriptor, ...]:
    """
    Build immutable sequence of tool descriptors from catalog snapshot.

    Iterates over all servers in the catalog and all commands per server;
    only commands from current active filtered server list are included (FR-8).
    """
    tools: list[ToolDescriptor] = []
    servers = catalog.get_servers()
    for server_id, server in servers.items():
        commands = catalog.get_commands(server_id)
        for _cmd_name, command in commands.items():
            tools.append(_build_descriptor(server, command))
    return tuple(tools)


@dataclass(frozen=True)
class ToolContextSnapshot:
    """
    Versioned immutable model-facing tools view derived from CatalogSnapshot (TZ §7, FR-8).

    Holds the same monotonic version as the source catalog snapshot (NFR-1:
    model-visible tool context and internal catalog snapshot are version-aligned).
    Exposes a read-only sequence of tool descriptors. No mutable state; built
    only from orchestration-layer CatalogSnapshot, not from raw proxy payloads.
    """

    version: int
    """Monotonic version aligned with catalog snapshot; for logging and diagnostics."""

    _tools: Tuple[ToolDescriptor, ...]
    """Immutable sequence of tool descriptors. Do not mutate."""

    def get_version(self) -> int:
        """Return the snapshot version (aligned with catalog snapshot)."""
        return self.version

    def get_tools(self) -> Sequence[ToolDescriptor]:
        """
        Return read-only sequence of tool descriptors for model consumption.

        Callers must not modify the returned sequence; it is immutable.
        """
        return self._tools

    def get_tools_by_server(self, server_id: str) -> Sequence[ToolDescriptor]:
        """
        Return read-only sequence of tool descriptors for the given server_id.

        Returns empty tuple if server_id is not present in the snapshot.
        """
        return tuple(t for t in self._tools if t.server_id == server_id)

    def get_tool(self, server_id: str, command_name: str) -> Optional[ToolDescriptor]:
        """Return the tool descriptor for (server_id, command_name), or None."""
        for t in self._tools:
            if t.server_id == server_id and t.command_name == command_name:
                return t
        return None

    @classmethod
    def from_catalog(
        cls,
        catalog: CatalogSnapshot,
        version: Optional[int] = None,
    ) -> ToolContextSnapshot:
        """
        Build ToolContextSnapshot from a CatalogSnapshot.

        Version is taken from the catalog unless overridden. Tool descriptors
        are built only from the catalog's servers and commands (FR-8);
        no direct proxy or raw catalog access.
        """
        use_version = version if version is not None else catalog.version
        tools = _build_tools_from_catalog(catalog)
        return cls(version=use_version, _tools=tools)
