"""
MCP Tools for All Our Keyed Services - Efficient API usage through caching shield.

Every tool routes through: cache → rate limit → provider → result.
All keys from vault, all calls cached.

Services wrapped:
  GMGN - token analytics, holder distribution, sniper detection
  Helius DAS - token metadata, wallet holdings, asset search
  Birdeye - token overview, price, volume
  Solscan - token metadata, holders, transactions
  Etherscan - contract verification, ABIs, gas
  CoinGecko - prices, market data, trending
  GoPlus - token security, honeypot detection
  Moralis - EVM wallet balances, token holdings, NFTs
  QuickNode - Solana RPC (backup)
  Alchemy - Solana RPC (backup)
"""

import hashlib
import json
import logging
import os
import time

import httpx

logger = logging.getLogger("service_mcp")

# Cache
_l1: dict[str, tuple] = {}
_hits = 0
_misses = 0


def _cache_key(service: str, method: str, params: dict) -> str:
    raw = f"{service}:{method}:{json.dumps(params, sort_keys=True, default=str)}"
    return hashlib.sha256(raw.encode()).hexdigest()[:24]


async def _cached_call(service: str, method: str, params: dict, fn, ttl: int = 60) -> dict | None:
    global _hits, _misses
    key = _cache_key(service, method, params)
    entry = _l1.get(key)
    if entry:
        expiry, data = entry
        if time.monotonic() < expiry:
            _hits += 1
            return data
        del _l1[key]
    _misses += 1
    result = await fn(**params)
    if result:
        _l1[key] = (time.monotonic() + ttl, result)
    return result


# ═══════════════════════════════════════════════════════════════════════════
# GMGN - Token Analytics, Holder Distribution, Sniper Detection
# ═══════════════════════════════════════════════════════════════════════════


class GMGNTools:
    """GMGN token analytics - holder distribution, sniper detection, bundle analysis."""

    def __init__(self):
        self._key = os.getenv("GMGN_API_KEY", "")
        self._base = "https://gmgn.ai/api/v1"

    async def token_security(self, chain: str, address: str) -> dict | None:
        async def fn(chain, address):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/token_security/{chain}/{address}",
                    headers={"Authorization": f"Bearer {self._key}"},
                )
                if r.status_code == 200:
                    d = r.json().get("data", {})
                    return {
                        "risk_score": d.get("risk_score"),
                        "honeypot": d.get("is_honeypot"),
                        "renounced": d.get("renounced_mint"),
                        "lp_burned": d.get("lp_burned"),
                        "top10_holder_pct": d.get("top10_holder_rate"),
                    }

        return await _cached_call("gmgn", "token_security", {"chain": chain, "address": address}, fn, 60)

    async def holder_distribution(self, chain: str, address: str) -> dict | None:
        async def fn(chain, address):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/token_holders/{chain}/{address}",
                    headers={"Authorization": f"Bearer {self._key}"},
                )
                if r.status_code == 200:
                    holders = r.json().get("data", {}).get("holders", [])
                    return {"holder_count": len(holders), "top_holders": holders[:10]}

        return await _cached_call("gmgn", "holders", {"chain": chain, "address": address}, fn, 120)

    async def sniper_check(self, chain: str, address: str) -> dict | None:
        async def fn(chain, address):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/token_snipers/{chain}/{address}",
                    headers={"Authorization": f"Bearer {self._key}"},
                )
                if r.status_code == 200:
                    d = r.json().get("data", {})
                    return {
                        "sniper_count": d.get("sniper_count", 0),
                        "snipers": d.get("snipers", [])[:5],
                    }

        return await _cached_call("gmgn", "snipers", {"chain": chain, "address": address}, fn, 60)


# ═══════════════════════════════════════════════════════════════════════════
# BIRDEYE - Token Overview, Price, Volume
# ═══════════════════════════════════════════════════════════════════════════


class BirdeyeTools:
    def __init__(self):
        self._key = os.getenv("BIRDEYE_API_KEY", "")
        self._base = "https://public-api.birdeye.so"

    async def token_overview(self, address: str) -> dict | None:
        async def fn(address):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/defi/token_overview",
                    params={"address": address},
                    headers={"X-API-KEY": self._key, "x-chain": "solana"},
                )
                if r.status_code == 200:
                    d = r.json().get("data", {})
                    return {
                        "price": d.get("price"),
                        "liquidity": d.get("liquidity"),
                        "volume_24h": d.get("volume24hUSD"),
                        "mc": d.get("mc"),
                    }

        return await _cached_call("birdeye", "overview", {"address": address}, fn, 30)

    async def token_price(self, address: str) -> dict | None:
        async def fn(address):
            async with httpx.AsyncClient(timeout=8) as c:
                r = await c.get(
                    f"{self._base}/defi/price",
                    params={"address": address},
                    headers={"X-API-KEY": self._key, "x-chain": "solana"},
                )
                if r.status_code == 200:
                    d = r.json().get("data", {})
                    return {"price": d.get("value"), "change_24h": d.get("priceChange24h")}

        return await _cached_call("birdeye", "price", {"address": address}, fn, 8)


# ═══════════════════════════════════════════════════════════════════════════
# SOLSCAN - Token Metadata, Holders, Transactions
# ═══════════════════════════════════════════════════════════════════════════


class SolscanTools:
    def __init__(self):
        self._key = os.getenv("SOLSCAN_API_KEY", "")
        self._base = "https://pro-api.solscan.io/v2.0"

    async def token_meta(self, address: str) -> dict | None:
        async def fn(address):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/token/meta",
                    params={"address": address},
                    headers={"token": self._key},
                )
                if r.status_code == 200:
                    d = r.json().get("data", {})
                    return {
                        "name": d.get("name"),
                        "symbol": d.get("symbol"),
                        "decimals": d.get("decimals"),
                        "supply": d.get("supply"),
                    }

        return await _cached_call("solscan", "meta", {"address": address}, fn, 120)

    async def token_holders(self, address: str, limit: int = 10) -> dict | None:
        async def fn(address, limit):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/token/holders",
                    params={"address": address, "limit": limit},
                    headers={"token": self._key},
                )
                if r.status_code == 200:
                    return {"holders": r.json().get("data", [])}

        return await _cached_call("solscan", "holders", {"address": address, "limit": limit}, fn, 60)


# ═══════════════════════════════════════════════════════════════════════════
# COINGECKO - Prices, Market Data, Trending
# ═══════════════════════════════════════════════════════════════════════════


class CoinGeckoTools:
    def __init__(self):
        self._key = os.getenv("COINGECKO_API_KEY", "")
        self._base = "https://pro-api.coingecko.com/api/v3"

    async def price(self, token_id: str = "solana") -> dict | None:
        async def fn(token_id):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/simple/price",
                    params={
                        "ids": token_id,
                        "vs_currencies": "usd",
                        "include_market_cap": "true",
                        "include_24hr_vol": "true",
                    },
                    headers={"x-cg-pro-api-key": self._key},
                )
                if r.status_code == 200:
                    return r.json().get(token_id, {})

        return await _cached_call("coingecko", "price", {"token_id": token_id}, fn, 30)

    async def trending(self) -> dict | None:
        async def fn():
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(f"{self._base}/search/trending", headers={"x-cg-pro-api-key": self._key})
                if r.status_code == 200:
                    coins = r.json().get("coins", [])
                    return {"trending": [c.get("item", {}).get("name") for c in coins[:10]]}

        return await _cached_call("coingecko", "trending", {}, fn, 300)


# ═══════════════════════════════════════════════════════════════════════════
# ETHERSCAN - Contract Verification, ABI, Gas
# ═══════════════════════════════════════════════════════════════════════════


class EtherscanTools:
    def __init__(self):
        self._key = os.getenv("ETHERSCAN_API_KEY", "")
        self._base = "https://api.etherscan.io/api"

    async def contract_abi(self, address: str) -> dict | None:
        async def fn(address):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    self._base,
                    params={
                        "module": "contract",
                        "action": "getabi",
                        "address": address,
                        "apikey": self._key,
                    },
                )
                if r.status_code == 200 and r.json().get("status") == "1":
                    return {"abi": r.json().get("result")}

        return await _cached_call("etherscan", "abi", {"address": address}, fn, 3600)

    async def gas_oracle(self) -> dict | None:
        async def fn():
            async with httpx.AsyncClient(timeout=8) as c:
                r = await c.get(
                    self._base,
                    params={"module": "gastracker", "action": "gasoracle", "apikey": self._key},
                )
                if r.status_code == 200:
                    d = r.json().get("result", {})
                    if isinstance(d, str):
                        return {"gas_data": d}
                    return {
                        "safe_gas": d.get("SafeGasPrice"),
                        "propose_gas": d.get("ProposeGasPrice"),
                        "fast_gas": d.get("FastGasPrice"),
                    }

        return await _cached_call("etherscan", "gas", {}, fn, 15)


# ═══════════════════════════════════════════════════════════════════════════
# MORALIS - EVM Wallet Balances, Token Holdings, NFTs
# ═══════════════════════════════════════════════════════════════════════════


class MoralisTools:
    def __init__(self):
        self._keys = [os.getenv(f"MORALIS_API_KEY{s}", "") for s in ("", "_2", "_3")]
        self._keys = [k for k in self._keys if k]
        self._idx = 0
        self._base = "https://deep-index.moralis.io/api/v2.2"

    def _next_key(self):
        key = self._keys[self._idx % len(self._keys)] if self._keys else ""
        self._idx += 1
        return key

    async def wallet_balance(self, address: str, chain: str = "eth") -> dict | None:
        async def fn(address, chain):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/{address}/balance",
                    params={"chain": chain},
                    headers={"X-API-Key": self._next_key()},
                )
                if r.status_code == 200:
                    return {"balance_wei": r.json().get("balance")}

        return await _cached_call("moralis", "coinmarketcap", "balance", {"address": address, "chain": chain}, fn, 15)

    async def wallet_tokens(self, address: str, chain: str = "eth") -> dict | None:
        async def fn(address, chain):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/{address}/erc20",
                    params={"chain": chain},
                    headers={"X-API-Key": self._next_key()},
                )
                if r.status_code == 200:
                    tokens = r.json()
                    return {"token_count": len(tokens), "tokens": tokens[:20]}

        return await _cached_call("moralis", "coinmarketcap", "tokens", {"address": address, "chain": chain}, fn, 30)


# ═══════════════════════════════════════════════════════════════════════════
# UNIFIED SERVICE MCP
# ═══════════════════════════════════════════════════════════════════════════


class ServiceMCP:
    """All our keyed services as MCP-compatible cached tools."""

    def __init__(self):
        self.gmgn = GMGNTools()
        self.birdeye = BirdeyeTools()
        self.solscan = SolscanTools()
        self.coingecko = CoinGeckoTools()
        self.etherscan = EtherscanTools()
        self.moralis = MoralisTools()
        self.coinmarketcap = CoinMarketCapTools()

    def stats(self) -> dict:
        return {
            "cache_hits": _hits,
            "cache_misses": _misses,
            "services": [
                "gmgn",
                "birdeye",
                "solscan",
                "coingecko",
                "etherscan",
                "moralis",
                "coinmarketcap",
            ],
            "l1_size": len(_l1),
        }


_service_mcp: ServiceMCP | None = None


def get_service_mcp() -> ServiceMCP:
    global _service_mcp
    if _service_mcp is None:
        _service_mcp = ServiceMCP()
    return _service_mcp


# ═══════════════════════════════════════════════════════════════════════════
# COINMARKETCAP — Market data, listings, trends, OHLCV (10K free/mo)
# ═══════════════════════════════════════════════════════════════════════════


class CoinMarketCapTools:
    def __init__(self):
        self._key = os.getenv("COINMARKETCAP_API_KEY", "")
        self._base = "https://pro-api.coinmarketcap.com/v1"

    async def latest_listings(self, limit: int = 10) -> dict | None:
        async def fn(limit):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/cryptocurrency/listings/latest",
                    params={"limit": limit, "convert": "USD"},
                    headers={"X-CMC_PRO_API_KEY": self._key},
                )
                if r.status_code == 200:
                    coins = r.json().get("data", [])
                    return {
                        "count": len(coins),
                        "top": [
                            {
                                "name": c["name"],
                                "symbol": c["symbol"],
                                "price": c["quote"]["USD"]["price"],
                                "market_cap": c["quote"]["USD"]["market_cap"],
                                "volume_24h": c["quote"]["USD"]["volume_24h"],
                                "change_24h": c["quote"]["USD"]["percent_change_24h"],
                            }
                            for c in coins[:limit]
                        ],
                    }

        return await _cached_call("cmc", "listings", {"limit": limit}, fn, 60)

    async def quotes(self, symbols: list) -> dict | None:
        async def fn(symbols):
            async with httpx.AsyncClient(timeout=10) as c:
                r = await c.get(
                    f"{self._base}/cryptocurrency/quotes/latest",
                    params={"symbol": ",".join(symbols), "convert": "USD"},
                    headers={"X-CMC_PRO_API_KEY": self._key},
                )
                if r.status_code == 200:
                    data = r.json().get("data", {})
                    return {s: {"price": data[s]["quote"]["USD"]["price"]} for s in symbols if s in data}

        return await _cached_call("cmc", "quotes", {"symbols": tuple(symbols)}, fn, 30)
