"""ToolRegistry — gateway wrapper around DynamicToolRegistry.

Adds tier gating, rate limiting, and protocol-aware invocation on top
of the core DynamicToolRegistry that manages all 257 MCP tools.
"""

from __future__ import annotations

import json
import logging
from typing import Any, AsyncGenerator, Dict, List, Optional

from .context import RequestContext

logger = logging.getLogger(__name__)


class ToolRegistry:
    """Unified tool invocation layer for the Graph API Gateway.

    Wraps DynamicToolRegistry (from src/plugins/dynamic_registry.py) and adds:
    - Tier-based access control
    - Rate limiting hooks
    - OpenAPI and MCP manifest generation from tool metadata
    """

    def __init__(self, dynamic_registry: Any, tenant_registry: Any = None):
        self.dynamic = dynamic_registry
        self.tenants = tenant_registry
        self._tier_gate: Optional[Any] = None       # set by app.py after Phase 6
        self._rate_limiter: Optional[Any] = None     # set by app.py after Phase 6

    # ------------------------------------------------------------------
    # Invocation
    # ------------------------------------------------------------------

    async def invoke(self, tool_id: str, params: dict, context: RequestContext) -> dict:
        """Execute a tool through the gateway with tier + rate limit checks.

        Args:
            tool_id: Exact tool name (e.g. "hierarchy_graph", "rag_search").
            params: Tool arguments dict.
            context: Per-request context (tenant, auth, protocol).

        Returns:
            {"result": <tool output>, "tool": tool_id, "request_id": context.request_id}
        """
        # 1. Tier gate (if wired)
        if self._tier_gate is not None:
            meta = self.dynamic.get_tool_metadata(tool_id)
            if meta and not self._tier_gate.can_access(context.tenant_tier, meta.tier):
                return {
                    "error": f"Tier '{context.tenant_tier}' cannot access tool '{tool_id}' (requires '{meta.tier}')",
                    "code": 403,
                }

        # 2. Rate limiter (if wired)
        if self._rate_limiter is not None:
            exceeded = await self._rate_limiter.check(context.tenant_id, tool_id)
            if exceeded:
                return {
                    "error": f"Rate limit exceeded for tenant '{context.tenant_id}'",
                    "code": 429,
                }

        # 3. Execute via DynamicToolRegistry
        try:
            result = await self.dynamic.execute_tool(tool_id, params)
        except ValueError as exc:
            return {"error": str(exc), "code": 404}
        except Exception as exc:
            logger.exception("Tool execution failed: %s", tool_id)
            return {"error": f"Internal error: {exc}", "code": 500}

        return {
            "result": result,
            "tool": tool_id,
            "request_id": context.request_id,
        }

    async def stream(self, tool_id: str, params: dict, context: RequestContext) -> AsyncGenerator[dict, None]:
        """Stream tool output for SSE adapter.

        For tools that don't natively support streaming, yields a single
        result chunk followed by a done sentinel.
        """
        result = await self.invoke(tool_id, params, context)
        yield result
        yield {"done": True}

    # ------------------------------------------------------------------
    # Discovery / metadata
    # ------------------------------------------------------------------

    def get_all_tools(self) -> Dict[str, Any]:
        """Return metadata for all registered tools."""
        return self.dynamic.get_all_metadata()

    def get_tools_by_domain(self, domain: str) -> List[dict]:
        """Return tools filtered by domain name."""
        return self.dynamic.get_tools_by_domain(domain)

    def get_all_domains(self) -> List[dict]:
        """Return all domains with tool counts."""
        return self.dynamic.get_all_domains()

    def search_tools(self, query: str) -> List[dict]:
        """Fuzzy search across tool names and descriptions."""
        return self.dynamic.search_tools(query)

    # ------------------------------------------------------------------
    # Manifest generation
    # ------------------------------------------------------------------

    def generate_openapi_paths(self) -> dict:
        """Auto-generate OpenAPI path definitions from tool metadata.

        Groups tools by domain (using PREFIX_DOMAIN_MAP) and creates
        POST /v1/{domain}/{tool_name} paths with request body schemas.
        """
        paths: Dict[str, Any] = {}
        tags: List[dict] = []
        seen_tags: set = set()

        all_meta = self.dynamic.get_all_metadata()
        for tool_name, meta in sorted(all_meta.items(), key=lambda x: x[0]):
            domain = meta.domain
            tag = domain.replace("_", " ").title()

            if domain not in seen_tags:
                seen_tags.add(domain)
                desc = ""
                if hasattr(self.dynamic, "_get_domain_description"):
                    desc = self.dynamic._get_domain_description(domain)
                tags.append({"name": tag, "description": desc})

            path = f"/v1/{domain}/{tool_name}"
            paths[path] = {
                "post": {
                    "summary": meta.description[:120] if meta.description else tool_name,
                    "operationId": tool_name,
                    "tags": [tag],
                    "requestBody": {
                        "required": True,
                        "content": {
                            "application/json": {
                                "schema": {"type": "object"},
                            }
                        },
                    },
                    "responses": {
                        "200": {
                            "description": "Tool execution result",
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "type": "object",
                                        "properties": {
                                            "result": {"type": "string"},
                                            "tool": {"type": "string"},
                                            "request_id": {"type": "string"},
                                        },
                                    }
                                }
                            },
                        },
                        "403": {"description": "Tier access denied"},
                        "404": {"description": "Tool not found"},
                        "429": {"description": "Rate limit exceeded"},
                    },
                }
            }

        return {
            "openapi": "3.1.0",
            "info": {
                "title": "DataBridge Graph API",
                "description": "Unified API gateway for DataBridge MCP tools",
                "version": "0.43.0",
            },
            "paths": paths,
            "tags": sorted(tags, key=lambda t: t["name"]),
        }

    def generate_mcp_manifest(self) -> dict:
        """Auto-generate MCP tool discovery manifest.

        Returns the standard MCP tools/list response format.
        """
        tools = []
        all_meta = self.dynamic.get_all_metadata()
        for tool_name, meta in sorted(all_meta.items(), key=lambda x: x[0]):
            tools.append({
                "name": meta.name,
                "description": meta.description,
                "inputSchema": {"type": "object"},
                "annotations": {
                    "domain": meta.domain,
                    "tier": meta.tier,
                    "plugin": meta.plugin,
                },
            })
        return {"tools": tools}
