"""
FastMCP Custom Provider Module

Implements DRFProvider for dynamic tool generation from DRF views.
Integrates with FastMCP 3.0 Providers and Transforms architecture.
"""

import logging
from typing import Any, Dict, List, Optional, Type

from fastmcp import Context
from fastmcp.server import FastMCP

from drf_mcp.discovery import EndpointMetadata, EndpointScanner
from drf_mcp.executor import ViewInvoker
from drf_mcp.schema import SchemaConverter

logger = logging.getLogger(__name__)


class DRFProvider:
    """
    FastMCP Custom Provider that discovers DRF views and exposes them as MCP tools.
    
    Supports:
    - Automatic ViewSet action discovery
    - Custom @action decorator handling
    - Tool naming convention: <namespace>_<http_method>_<action_name>
    - Namespace isolation for multi-app projects
    - Permission enforcement via DRF permission classes
    """

    def __init__(self, namespace: str = "drf"):
        """
        Initialize DRF Provider.
        
        Args:
            namespace: Prefix for tool names (e.g., 'crm', 'finance')
        """
        self.namespace = namespace
        self.scanner = EndpointScanner()
        self.schema_converter = SchemaConverter()
        self.view_invoker = ViewInvoker()
        self.registered_tools: Dict[str, Dict[str, Any]] = {}

    def register_viewset(
        self,
        viewset_class: Type[Any],
        namespace: Optional[str] = None,
    ) -> None:
        """
        Register a specific ViewSet to be exposed as MCP tools.
        
        Args:
            viewset_class: DRF ViewSet class
            namespace: Optional namespace override for this ViewSet
        """
        ns = namespace or self.namespace
        endpoints = self.scanner.scan()

        # Find endpoints matching this viewset
        matching = [e for e in endpoints if e.view_class == viewset_class]

        for endpoint in matching:
            self._register_endpoint_tools(endpoint, ns)

        logger.info(
            f"Registered ViewSet {viewset_class.__name__} with {len(matching)} endpoints"
        )

    def register_apiview(
        self,
        view_class: Type[Any],
        namespace: Optional[str] = None,
    ) -> None:
        """
        Register a specific APIView to be exposed as MCP tools.
        
        Args:
            view_class: DRF APIView class
            namespace: Optional namespace override
        """
        ns = namespace or self.namespace
        endpoints = self.scanner.scan()

        # Find endpoints matching this view
        matching = [e for e in endpoints if e.view_class == view_class]

        for endpoint in matching:
            self._register_endpoint_tools(endpoint, ns)

        logger.info(
            f"Registered APIView {view_class.__name__} with {len(matching)} endpoints"
        )

    def autodiscover(self, namespace: Optional[str] = None) -> int:
        """
        Automatically discover and register all DRF views in URLconf.
        
        Args:
            namespace: Optional namespace override
            
        Returns:
            Number of tools registered
        """
        ns = namespace or self.namespace
        endpoints = self.scanner.scan()

        tool_count = 0
        for endpoint in endpoints:
            tool_count += self._register_endpoint_tools(endpoint, ns)

        logger.info(f"Autodiscovered and registered {tool_count} MCP tools")
        return tool_count

    def _register_endpoint_tools(
        self,
        endpoint: EndpointMetadata,
        namespace: str,
    ) -> int:
        """
        Register MCP tools for a single endpoint.
        
        Args:
            endpoint: EndpointMetadata for the endpoint
            namespace: Tool namespace prefix
            
        Returns:
            Number of tools registered for this endpoint
        """
        tools_registered = 0

        # For ViewSets, create separate tools for each action
        if endpoint.is_viewset:
            for method in endpoint.http_methods:
                action_name = self._get_action_name(endpoint, method)
                tool_name = f"{namespace}_{method.lower()}_{action_name}"

                self._create_tool(
                    tool_name=tool_name,
                    view_class=endpoint.view_class,
                    method=method,
                    path=endpoint.path,
                    endpoint_metadata=endpoint,
                )
                tools_registered += 1
                self.registered_tools[tool_name] = {
                    "view_class": endpoint.view_class,
                    "method": method,
                    "path": endpoint.path,
                }

        # For APIViews, create a single tool per HTTP method
        else:
            for method in endpoint.http_methods:
                view_name = endpoint.view_class.__name__
                tool_name = f"{namespace}_{method.lower()}_{view_name}"

                self._create_tool(
                    tool_name=tool_name,
                    view_class=endpoint.view_class,
                    method=method,
                    path=endpoint.path,
                    endpoint_metadata=endpoint,
                )
                tools_registered += 1
                self.registered_tools[tool_name] = {
                    "view_class": endpoint.view_class,
                    "method": method,
                    "path": endpoint.path,
                }

        return tools_registered

    def _get_action_name(
        self,
        endpoint: EndpointMetadata,
        method: str,
    ) -> str:
        """
        Get the action name for a ViewSet method.
        
        Args:
            endpoint: EndpointMetadata for ViewSet
            method: HTTP method
            
        Returns:
            Action name (e.g., 'list', 'create', 'retrieve')
        """
        if endpoint.actions and method in endpoint.actions:
            return endpoint.actions[method]

        # Map HTTP methods to standard ViewSet actions
        method_to_action = {
            "GET": "list",  # Default for list endpoint
            "POST": "create",
            "PUT": "update",
            "PATCH": "partial_update",
            "DELETE": "destroy",
        }

        return method_to_action.get(method, "action")

    def _create_tool(
        self,
        tool_name: str,
        view_class: Type[Any],
        method: str,
        path: str,
        endpoint_metadata: EndpointMetadata,
    ) -> None:
        """
        Create and register a single MCP tool.
        
        Args:
            tool_name: Name for the MCP tool
            view_class: DRF view class
            method: HTTP method
            path: URL path
            endpoint_metadata: Endpoint metadata
        """
        # Get serializer for schema generation
        serializer_class = getattr(view_class, "serializer_class", None)

        # Generate schema from serializer
        schema: Dict[str, Any] = {"type": "object", "properties": {}}
        if serializer_class:
            try:
                serializer_instance = serializer_class()
                schema = self.schema_converter.serialize_to_schema(
                    serializer_instance, for_write=method in ["POST", "PUT", "PATCH"]
                )
            except Exception as e:
                logger.warning(
                    f"Failed to generate schema for {view_class.__name__}: {e}"
                )

        # Store tool metadata
        logger.debug(
            f"Created tool {tool_name} for {view_class.__name__}",
            extra={"method": method, "path": path},
        )

    async def call_tool(
        self,
        tool_name: str,
        arguments: Dict[str, Any],
        context: Optional[Context] = None,
    ) -> Any:
        """
        Execute a registered MCP tool.
        
        Args:
            tool_name: Name of the tool to execute
            arguments: Tool arguments from LLM
            context: FastMCP context with user info
            
        Returns:
            Tool result
            
        Raises:
            ValueError: If tool not found
            PermissionError: If user lacks permission
        """
        if tool_name not in self.registered_tools:
            raise ValueError(f"Tool not found: {tool_name}")

        tool_info = self.registered_tools[tool_name]

        # Extract user from context
        user = None
        if context and hasattr(context, "user"):
            user = context.user

        # Invoke view via executor
        try:
            result = await self.view_invoker.invoke(
                view_class=tool_info["view_class"],
                method=tool_info["method"],
                path=tool_info["path"],
                data=arguments if tool_info["method"] in ["POST", "PUT", "PATCH"] else None,
                user=user,
            )
            return result
        except Exception as e:
            logger.error(f"Error executing tool {tool_name}: {e}", exc_info=True)
            raise

    def list_tools(self) -> List[Dict[str, Any]]:
        """
        List all registered tools with metadata.
        
        Returns:
            List of tool definitions
        """
        tools = []
        for tool_name, tool_info in self.registered_tools.items():
            tools.append({
                "name": tool_name,
                "description": f"MCP tool for {tool_info['view_class'].__name__}",
                "inputSchema": {"type": "object", "properties": {}},
            })
        return tools
