"""
Chain Data Cache — In-memory TTL cache for RPC results.
Minimizes Helius API calls on free tier (100k credits/month).
Includes startup warming for hot wallets.
"""

import asyncio
import logging
import time
from collections import OrderedDict
from typing import Any

logger = logging.getLogger(__name__)

# ── Hot Wallets to Pre-Warm ──────────────────────────────
# Top wallets by volume + known infrastructure that get hit on every scan.
# Pre-warming saves 5+ API calls per scan that would otherwise be cold misses.

HOT_WALLETS: list[str] = [
    # Top Solana DEX/infrastructure (hit on every RugMaps/scan)
    "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",  # Raydium V4 AMM
    "JUP6LkbZbjS1jKKwapdHNy7bKNkD5Ka6TqPnVGFVeYf",  # Jupiter V6
    "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",  # Orca Whirlpool
    "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",  # Binance 1
    "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9",  # Binance 2
    "2ojv9BAiHghv5o9y2k6k3m7o9TdsrKJqXvWyCdkUWNmB",  # Binance 3
    "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",  # Pump.fun
    "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",  # SPL Token Program
    # Top Ethereum infrastructure (cross-chain)
    "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",  # Uniswap V2
    "0xBE0eB53FC46b790099138e3d32C721856d41e865",  # Binance 7
    "0xF977814e90dA44bFA03b6295A0616a897441aceC",  # Binance 8
    "0x28C6c06298d514Db089934071355E5743bf21d60",  # Binance 14
]

HOT_TOKENS: list[str] = [
    # Top tokens scanned for rugs (Solana)
    "jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL",  # JTO
    "EPjFWdd5AufqSSqeM2qN1xzybapC8G5wUGxvZq2moaomYR",  # USDC
    "So11111111111111111111111111111111111111112",  # SOL wrapped
    "mSoLzYCxHdYgbz9JUv7WoxX3LEdh3K8VZJIQz3EhR9J",  # mSOL
    "JUPyiwrYJF8UP9YuYuB8mH3WGfnCfNJiQ3W9vHJ7kKo",  # JUP
    # Top tokens (Ethereum chain IDs)
    "1",  # ETH chain
    "56",  # BSC chain
    "8453",  # Base chain
]


class ChainCache:
    """LRU cache with TTL for chain data. Max 500 entries, 5-min default TTL."""

    def __init__(self, max_size: int = 500, default_ttl: int = 300):
        self.max_size = max_size
        self.default_ttl = default_ttl
        self._cache: OrderedDict[str, tuple[float, Any]] = OrderedDict()
        self._lock = asyncio.Lock()
        self._warmed = False

    def _make_key(self, method: str, *args) -> str:
        return f"{method}:{':'.join(str(a)[:100] for a in args)}"

    async def get(self, method: str, *args) -> Any | None:
        key = self._make_key(method, *args)
        async with self._lock:
            if key in self._cache:
                expiry, value = self._cache[key]
                if time.time() < expiry:
                    self._cache.move_to_end(key)
                    return value
                del self._cache[key]
        return None

    async def set(self, method: str, value: Any, *args, ttl: int | None = None):
        key = self._make_key(method, *args)
        async with self._lock:
            if key in self._cache:
                del self._cache[key]
            elif len(self._cache) >= self.max_size:
                self._cache.popitem(last=False)
            self._cache[key] = (time.time() + (ttl or self.default_ttl), value)

    async def stats(self) -> dict:
        async with self._lock:
            # Count how many are from warming vs live
            warmed = sum(1 for k in self._cache if k.startswith("warm:"))
            return {
                "size": len(self._cache),
                "max": self.max_size,
                "warmed_entries": warmed,
                "utilization_pct": round(len(self._cache) / self.max_size * 100, 1),
            }

    async def warm(self):
        """Pre-populate cache with known hot addresses to avoid cold-start misses."""
        if self._warmed:
            return

        logger.info(f"Warming cache with {len(HOT_WALLETS)} wallets and {len(HOT_TOKENS)} tokens...")

        # Mark entity registry entries as known infrastructure (avoids repeated lookups)
        try:
            from app.entity_registry import get_entity_registry

            registry = get_entity_registry()
            for addr in HOT_WALLETS:
                match = registry.classify_address(addr)
                if match and match.entity_type != "unknown":
                    await self.set("warm_entity", match.to_dict(), addr, ttl=600)
        except Exception as e:
            logger.debug(f"Entity warm skipped: {e}")

        # Mark hot tokens as known-safe to avoid spam registry checks
        for token in HOT_TOKENS:
            await self.set("warm_token", {"address": token, "preloaded": True}, token, ttl=600)

        # Mark spam registry as loaded (it loads on first check anyway)
        try:
            from app.spam_registry import get_spam_registry

            sr = get_spam_registry()
            sr._ensure_loaded()  # Force load
            await self.set("warm_spam", {"loaded": True, "total": sr.stats()["total"]}, ttl=600)
        except Exception as e:
            logger.debug(f"Spam warm skipped: {e}")

        # Mark GNN as available
        try:
            from app.fraud_gnn import get_fraud_gnn

            gnn = get_fraud_gnn()
            gnn._ensure_loaded()
            await self.set("warm_gnn", {"loaded": True, "model": gnn.model_type}, ttl=600)
        except Exception as e:
            logger.debug(f"GNN warm skipped: {e}")

        # Warm CoinGecko market data if available
        try:
            from app.coingecko_connector import get_coingecko_connector

            cg = get_coingecko_connector()
            trending = await cg.get_trending()
            if trending:
                await self.set("warm_trending", trending, ttl=300)
                logger.info(f"Warmed trending: {len(trending)} tokens")
            global_data = await cg.get_global_metrics()
            if global_data:
                await self.set("warm_global", global_data, ttl=300)
                logger.info(f"Warmed global: BTC dom {global_data.get('btc_dominance', '?')}%")
        except Exception as e:
            logger.debug(f"CoinGecko warm skipped: {e}")

        self._warmed = True
        logger.info(f"Cache warmed: {len(self._cache)}/{self.max_size} entries")


# Singleton
_cache: ChainCache | None = None


def get_chain_cache() -> ChainCache:
    global _cache
    if _cache is None:
        _cache = ChainCache()
    return _cache
