"""Namespace transform for prefixing component names."""

from __future__ import annotations

import re
from collections.abc import Sequence
from typing import TYPE_CHECKING

from fastmcp.server.transforms import (
    GetPromptNext,
    GetResourceNext,
    GetResourceTemplateNext,
    GetToolNext,
    Transform,
)
from fastmcp.utilities.versions import VersionSpec

if TYPE_CHECKING:
    from fastmcp.prompts.base import Prompt
    from fastmcp.resources.base import Resource
    from fastmcp.resources.template import ResourceTemplate
    from fastmcp.tools.base import Tool

# Pattern for matching URIs: protocol://path
_URI_PATTERN = re.compile(r"^([^:]+://)(.*?)$")


class Namespace(Transform):
    """Prefixes component names with a namespace.

    - Tools: name → namespace_name
    - Prompts: name → namespace_name
    - Resources: protocol://path → protocol://namespace/path
    - Resource Templates: same as resources

    Example:
        ```python
        transform = Namespace("math")
        # Tool "add" becomes "math_add"
        # Resource "file://data.txt" becomes "file://math/data.txt"
        ```
    """

    def __init__(self, prefix: str) -> None:
        """Initialize Namespace transform.

        Args:
            prefix: The namespace prefix to apply.
        """
        self._prefix = prefix
        self._name_prefix = f"{prefix}_"

    def __repr__(self) -> str:
        return f"Namespace({self._prefix!r})"

    # -------------------------------------------------------------------------
    # Name transformation helpers
    # -------------------------------------------------------------------------

    def _transform_name(self, name: str) -> str:
        """Apply namespace prefix to a name."""
        return f"{self._name_prefix}{name}"

    def _reverse_name(self, name: str) -> str | None:
        """Remove namespace prefix from a name, or None if no match."""
        if name.startswith(self._name_prefix):
            return name[len(self._name_prefix) :]
        return None

    # -------------------------------------------------------------------------
    # URI transformation helpers
    # -------------------------------------------------------------------------

    def _transform_uri(self, uri: str) -> str:
        """Apply namespace to a URI: protocol://path → protocol://namespace/path."""
        match = _URI_PATTERN.match(uri)
        if match:
            protocol, path = match.groups()
            return f"{protocol}{self._prefix}/{path}"
        return uri

    def _reverse_uri(self, uri: str) -> str | None:
        """Remove namespace from a URI, or None if no match."""
        match = _URI_PATTERN.match(uri)
        if match:
            protocol, path = match.groups()
            prefix = f"{self._prefix}/"
            if path.startswith(prefix):
                return f"{protocol}{path[len(prefix) :]}"
            return None
        return None

    # -------------------------------------------------------------------------
    # Tools
    # -------------------------------------------------------------------------

    async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
        """Prefix tool names with namespace."""
        return [
            t.model_copy(update={"name": self._transform_name(t.name)}) for t in tools
        ]

    async def get_tool(
        self, name: str, call_next: GetToolNext, *, version: VersionSpec | None = None
    ) -> Tool | None:
        """Get tool by namespaced name."""
        original = self._reverse_name(name)
        if original is None:
            return None
        tool = await call_next(original, version=version)
        if tool:
            return tool.model_copy(update={"name": name})
        return None

    # -------------------------------------------------------------------------
    # Resources
    # -------------------------------------------------------------------------

    async def list_resources(self, resources: Sequence[Resource]) -> Sequence[Resource]:
        """Add namespace path segment to resource URIs."""
        return [
            r.model_copy(update={"uri": self._transform_uri(str(r.uri))})
            for r in resources
        ]

    async def get_resource(
        self,
        uri: str,
        call_next: GetResourceNext,
        *,
        version: VersionSpec | None = None,
    ) -> Resource | None:
        """Get resource by namespaced URI."""
        original = self._reverse_uri(uri)
        if original is None:
            return None
        resource = await call_next(original, version=version)
        if resource:
            return resource.model_copy(update={"uri": uri})
        return None

    # -------------------------------------------------------------------------
    # Resource Templates
    # -------------------------------------------------------------------------

    async def list_resource_templates(
        self, templates: Sequence[ResourceTemplate]
    ) -> Sequence[ResourceTemplate]:
        """Add namespace path segment to template URIs."""
        return [
            t.model_copy(update={"uri_template": self._transform_uri(t.uri_template)})
            for t in templates
        ]

    async def get_resource_template(
        self,
        uri: str,
        call_next: GetResourceTemplateNext,
        *,
        version: VersionSpec | None = None,
    ) -> ResourceTemplate | None:
        """Get resource template by namespaced URI."""
        original = self._reverse_uri(uri)
        if original is None:
            return None
        template = await call_next(original, version=version)
        if template:
            return template.model_copy(
                update={"uri_template": self._transform_uri(template.uri_template)}
            )
        return None

    # -------------------------------------------------------------------------
    # Prompts
    # -------------------------------------------------------------------------

    async def list_prompts(self, prompts: Sequence[Prompt]) -> Sequence[Prompt]:
        """Prefix prompt names with namespace."""
        return [
            p.model_copy(update={"name": self._transform_name(p.name)}) for p in prompts
        ]

    async def get_prompt(
        self, name: str, call_next: GetPromptNext, *, version: VersionSpec | None = None
    ) -> Prompt | None:
        """Get prompt by namespaced name."""
        original = self._reverse_name(name)
        if original is None:
            return None
        prompt = await call_next(original, version=version)
        if prompt:
            return prompt.model_copy(update={"name": name})
        return None
