"""
X402 Tool Data Provider - Cached, rate-limited data access for all x402 tools.

Replace raw aiohttp/httpx calls with this provider. One import, everything cached.

Usage in x402 routers:
    from app.caching_shield.tool_data import td

    # Instead of: async with aiohttp.ClientSession() as s: r = await s.get(url)
    # Use: result = await td.token_price(mint="So111...")
    # Returns: {"price_usd": 79.5, "source": "jupiter", "cached": False}
"""

from app.caching_shield.unified_layer import get_data_layer


class ToolData:
    """Cached, rate-limited data provider for x402 tool routers."""

    def __init__(self):
        self._layer = get_data_layer()

    async def token_price(self, mint: str) -> dict:
        r = await self._layer.fetch("token_price", mint=mint)
        return r.to_dict() if r else {"error": "no data"}

    async def token_meta(self, mint: str) -> dict:
        r = await self._layer.fetch("token_meta", mint=mint)
        return r.to_dict() if r else {"error": "no data"}

    async def wallet_balance(self, address: str) -> dict:
        r = await self._layer.fetch("wallet_balance", address=address)
        return r.to_dict() if r else {"error": "no data"}

    async def risk_scan(self, address: str, chain: str = "solana") -> dict:
        r = await self._layer.fetch("risk_scan", address=address, chain=chain)
        return r.to_dict() if r else {"error": "no data"}

    async def tx_history(self, address: str) -> dict:
        r = await self._layer.fetch("tx_history", address=address)
        return r.to_dict() if r else {"error": "no data"}

    async def funding_source(self, address: str, chain_id: int = 1) -> dict:
        r = await self._layer.fetch("funding_source", address=address, chain_id=chain_id)
        return r.to_dict() if r else {"error": "no data"}

    async def call_tool(self, tool_id: str, params: dict | None = None) -> dict:
        """Generic tool dispatcher - calls unified_layer.fetch with tool_id and params.

        This is the primary method for trial execution and MCP tool calls.
        Falls back to specific ToolData methods for known tools, or uses
        the unified layer's generic fetch for all 127 tools.

        Returns None if the tool execution fails (so middleware can fall back
        to POST route handlers).
        """
        params = params or {}

        # Fallback: try specific methods for common tools
        method_map = {
            "risk_scan": self.risk_scan,
            "token_price": self.token_price,
            "token_meta": self.token_meta,
            "wallet_balance": self.wallet_balance,
            "tx_history": self.tx_history,
            "funding_source": self.funding_source,
        }
        if tool_id in method_map:
            try:
                result = await method_map[tool_id](**params)
                # If the result has only an error key, it's not real data - return None
                if isinstance(result, dict) and set(result.keys()) <= {"error"}:
                    return None
                return result
            except Exception:
                pass

        try:
            r = await self._layer.fetch(tool_id, **params)
            if r:
                result = r.to_dict()
                result["tool"] = tool_id
                # If result has only an error key, it's not real data - return None
                if set(result.keys()) <= {"error", "tool"}:
                    return None
                return result
        except Exception:
            pass

        # Tool not available via DataBus - return None so middleware falls back
        return None

    def stats(self) -> dict:
        return self._layer.stats()


# Singleton
td = ToolData()
