"""
Moralis Connector — Data API + Auth API.
Key 1: Data API (wallets, tokens, NFTs, streams, whale tracking)
Key 2: Auth API (Sign-In With Ethereum/Solana — Phantom, MetaMask, etc.)

Free tier: 40,000 API credits/month (~1,333/day).
Rate-limited to 3 req/sec with caching.
"""

import asyncio
import logging
import os
import time
from typing import Any

import httpx

logger = logging.getLogger(__name__)

# ── API Keys ────────────────────────────────────────────────

MORALIS_API_KEY = os.getenv("MORALIS_API_KEY", "")
MORALIS_API_KEY_2 = os.getenv("MORALIS_API_KEY_2", "")  # Data+Auth (org 510042)
MORALIS_API_KEY_3 = os.getenv("MORALIS_API_KEY_3", "")  # Data+Auth (org 516732)

DATA_URL = "https://deep-index.moralis.io/api/v2.2"
AUTH_URL = "https://authapi.moralis.io"
STREAM_URL = "https://api.moralis.io/stream"

SUPPORTED_EVM_CHAINS = {
    "eth": "0x1",
    "goerli": "0x5",
    "sepolia": "0xaa36a7",
    "polygon": "0x89",
    "mumbai": "0x13881",
    "bsc": "0x38",
    "bsc_testnet": "0x61",
    "avalanche": "0xa86a",
    "fantom": "0xfa",
    "arbitrum": "0xa4b1",
    "optimism": "0xa",
    "base": "0x2105",
    "gnosis": "0x64",
    "celo": "0xa4ec",
    "palm": "0x2a15c308d",
}

CHAIN_ID_MAP = {
    "eth": "1",
    "goerli": "5",
    "sepolia": "11155111",
    "polygon": "137",
    "mumbai": "80001",
    "bsc": "56",
    "bsc_testnet": "97",
    "avalanche": "43114",
    "fantom": "250",
    "arbitrum": "42161",
    "optimism": "10",
    "base": "8453",
    "gnosis": "100",
}


class MoralisConnector:
    """Moralis Data + Auth API connector."""

    def __init__(self):
        # Key rotation: spread requests across 2 orgs for 2x rate limit
        self._data_keys = [k for k in [MORALIS_API_KEY, MORALIS_API_KEY_2, MORALIS_API_KEY_3] if k]
        self._auth_keys = [k for k in [MORALIS_API_KEY_2, MORALIS_API_KEY_3] if k]
        self.api_key = self._data_keys[0] if self._data_keys else ""
        self.auth_key = self._auth_keys[0] if self._auth_keys else ""
        self._data_idx = 0
        self._auth_idx = 0
        self._cache: dict[str, tuple] = {}  # key → (data, timestamp)
        self._cache_ttl = 300  # 5 min
        self._last_request = 0.0
        self._min_interval = 0.33  # 3 req/sec

    def _chain_param(self, chain: str) -> str:
        """Convert chain name to Moralis chain param."""
        return chain if chain in SUPPORTED_EVM_CHAINS else "eth"

    def _chain_id(self, chain: str) -> str:
        """Convert chain name to chainId for auth."""
        return CHAIN_ID_MAP.get(chain, "1")

    async def _rate_limit(self):
        now = time.monotonic()
        elapsed = now - self._last_request
        if elapsed < self._min_interval:
            await asyncio.sleep(self._min_interval - elapsed)
        self._last_request = time.monotonic()

    def _cached(self, key: str) -> Any | None:
        if key in self._cache:
            data, ts = self._cache[key]
            if time.time() - ts < self._cache_ttl:
                return data
        return None

    def _set_cache(self, key: str, data: Any):
        self._cache[key] = (data, time.time())
        # Evict old entries
        if len(self._cache) > 500:
            oldest = min(self._cache, key=lambda k: self._cache[k][1])
            del self._cache[oldest]

    async def _get(self, url: str, key: str | None = None, use_auth_key: bool = False) -> dict | None:
        """Rate-limited GET with caching and key rotation."""
        cache_key = f"get:{url}"
        if key is None:
            key = cache_key
        cached = self._cached(key)
        if cached is not None:
            return cached

        await self._rate_limit()
        # Rotate keys for load balancing (2 orgs = 2x rate limit)
        keys = self._auth_keys if use_auth_key else self._data_keys
        if not keys:
            return None
        idx = self._auth_idx if use_auth_key else self._data_idx
        api_key = keys[idx % len(keys)]
        if use_auth_key:
            self._auth_idx = idx + 1
        else:
            self._data_idx = idx + 1

        try:
            async with httpx.AsyncClient(timeout=15.0) as client:
                r = await client.get(
                    url,
                    headers={
                        "X-API-Key": api_key,
                        "Accept": "application/json",
                    },
                )
                if r.status_code == 200:
                    data = r.json()
                    self._set_cache(key, data)
                    return data
                elif r.status_code == 429:
                    logger.warning("Moralis rate limited")
                    return None
                else:
                    logger.debug(f"Moralis {r.status_code}: {url[:80]}")
                    return None
        except Exception as e:
            logger.debug(f"Moralis request failed: {e}")
            return None

    # ── Auth API (Key 2) ──────────────────────────────────

    async def request_evm_challenge(self, address: str, chain: str = "eth", domain: str = "rugmunch.io") -> dict | None:
        """Request SIWE (Sign-In With Ethereum) challenge for MetaMask etc."""
        chain_id = self._chain_id(chain)
        url = f"{AUTH_URL}/challenge/request/evm"
        try:
            async with httpx.AsyncClient(timeout=15.0) as client:
                r = await client.post(
                    url,
                    headers={
                        "X-API-Key": self.auth_key,
                        "Content-Type": "application/json",
                    },
                    json={
                        "address": address,
                        "chainId": chain_id,
                        "domain": domain,
                        "uri": f"https://{domain}",
                        "timeout": 60,
                    },
                )
                if r.status_code in (200, 201):
                    return r.json()
                logger.debug(f"EVM challenge failed: {r.status_code} {r.text[:100]}")
                return None
        except Exception as e:
            logger.debug(f"EVM challenge error: {e}")
            return None

    async def request_solana_challenge(self, address: str, domain: str = "rugmunch.io") -> dict | None:
        """Request SIWS (Sign-In With Solana) challenge for Phantom etc."""
        url = f"{AUTH_URL}/challenge/request/solana"
        try:
            async with httpx.AsyncClient(timeout=15.0) as client:
                r = await client.post(
                    url,
                    headers={
                        "X-API-Key": self.auth_key,
                        "Content-Type": "application/json",
                    },
                    json={
                        "address": address,
                        "network": "mainnet",
                        "domain": domain,
                        "uri": f"https://{domain}",
                        "timeout": 60,
                    },
                )
                if r.status_code in (200, 201):
                    return r.json()
                logger.debug(f"Solana challenge failed: {r.status_code} {r.text[:100]}")
                return None
        except Exception as e:
            logger.debug(f"Solana challenge error: {e}")
            return None

    async def verify_evm_signature(self, message: str, signature: str) -> dict | None:
        """Verify EVM wallet signature, get JWT token."""
        url = f"{AUTH_URL}/challenge/verify/evm"
        try:
            async with httpx.AsyncClient(timeout=15.0) as client:
                r = await client.post(
                    url,
                    headers={
                        "X-API-Key": self.auth_key,
                        "Content-Type": "application/json",
                    },
                    json={"message": message, "signature": signature},
                )
                if r.status_code in (200, 201):
                    return r.json()  # Contains JWT token
                return None
        except Exception:
            return None

    async def verify_solana_signature(self, message: str, signature: str) -> dict | None:
        """Verify Solana wallet signature, get JWT token."""
        url = f"{AUTH_URL}/challenge/verify/solana"
        try:
            async with httpx.AsyncClient(timeout=15.0) as client:
                r = await client.post(
                    url,
                    headers={
                        "X-API-Key": self.auth_key,
                        "Content-Type": "application/json",
                    },
                    json={"message": message, "signature": signature},
                )
                if r.status_code in (200, 201):
                    return r.json()  # Contains JWT token
                return None
        except Exception:
            return None

    # ── Data API (Key 1) ──────────────────────────────────

    async def get_wallet_tokens(self, address: str, chain: str = "eth") -> list[dict]:
        """Get ERC-20 tokens for a wallet."""
        url = f"{DATA_URL}/{address}/erc20?chain={self._chain_param(chain)}&limit=100"
        result = await self._get(url)
        if isinstance(result, list):
            return result
        return result.get("result", result.get("data", [])) if result else []

    async def get_wallet_nfts(self, address: str, chain: str = "eth", limit: int = 20) -> list[dict]:
        """Get NFTs owned by wallet."""
        url = f"{DATA_URL}/{address}/nft?chain={self._chain_param(chain)}&limit={limit}&normalizeMetadata=true"
        result = await self._get(url)
        if isinstance(result, list):
            return result
        return result.get("result", result.get("data", [])) if result else []

    async def get_wallet_native_balance(self, address: str, chain: str = "eth") -> dict:
        """Get native token balance (ETH/MATIC/BNB etc)."""
        url = f"{DATA_URL}/{address}/balance?chain={self._chain_param(chain)}"
        return await self._get(url) or {}

    async def get_wallet_token_transfers(self, address: str, chain: str = "eth", limit: int = 20) -> list[dict]:
        """Get token transfer history for whale tracking."""
        url = f"{DATA_URL}/{address}/erc20/transfers?chain={self._chain_param(chain)}&limit={limit}"
        result = await self._get(url)
        if isinstance(result, list):
            return result
        return result.get("result", result.get("data", [])) if result else []

    async def get_token_price(self, token_address: str, chain: str = "eth") -> dict:
        """Get token price in USD."""
        url = f"{DATA_URL}/erc20/{token_address}/price?chain={self._chain_param(chain)}"
        return await self._get(url) or {}

    async def get_token_metadata(self, token_address: str, chain: str = "eth") -> dict:
        """Get token metadata (name, symbol, decimals)."""
        url = f"{DATA_URL}/erc20/{token_address}?chain={self._chain_param(chain)}"
        return await self._get(url) or {}

    async def get_block(self, block_number: int, chain: str = "eth") -> dict:
        """Get block data by number."""
        url = f"{DATA_URL}/block/{block_number}?chain={self._chain_param(chain)}"
        return await self._get(url) or {}

    async def get_token_holders(self, token_address: str, chain: str = "eth", limit: int = 20) -> list[dict]:
        """Get top token holders (EVM chains)."""
        url = f"{DATA_URL}/erc20/{token_address}/owners?chain={self._chain_param(chain)}&limit={limit}"
        result = await self._get(url)
        if isinstance(result, list):
            return result
        return result.get("result", result.get("data", [])) if result else []

    # ── Stream Management ──────────────────────────────────

    async def list_streams(self) -> list[dict]:
        """List all Moralis streams."""
        url = f"{STREAM_URL}/streams"
        result = await self._get(url, use_auth_key=False)
        if isinstance(result, dict) and "result" in result:
            return result["result"]
        return result if isinstance(result, list) else []

    async def create_stream(
        self,
        webhook_url: str,
        description: str,
        chains: list[str] | None = None,
        address: str | None = None,
        topic0: list[str] | None = None,
    ) -> dict | None:
        """Create a Moralis stream (webhook for EVM events)."""
        if chains is None:
            chains = ["0x1"]  # Ethereum mainnet

        payload = {
            "webhookUrl": webhook_url,
            "description": description,
            "chainIds": chains,
            "tag": description.replace(" ", "_").lower(),
        }
        if address:
            payload["address"] = address
        if topic0:
            payload["topic0"] = topic0

        try:
            async with httpx.AsyncClient(timeout=15.0) as client:
                r = await client.post(
                    f"{STREAM_URL}/stream",
                    headers={"X-API-Key": self.api_key, "Content-Type": "application/json"},
                    json=payload,
                )
                if r.status_code in (200, 201):
                    return r.json()
                logger.debug(f"Create stream failed: {r.status_code} {r.text[:200]}")
                return None
        except Exception as e:
            logger.debug(f"Create stream error: {e}")
            return None

    def status(self) -> dict:
        """Return connector status."""
        return {
            "data_api": bool(self.api_key),
            "auth_api": bool(self.auth_key),
            "data_key_prefix": self.api_key[:20] + "..." if self.api_key else "NOT SET",
            "auth_key_prefix": self.auth_key[:20] + "..." if self.auth_key else "NOT SET",
            "supported_chains": list(SUPPORTED_EVM_CHAINS.keys()),
            "cache_entries": len(self._cache),
        }


# Singleton
_moralis: MoralisConnector | None = None


def get_moralis_connector() -> MoralisConnector:
    global _moralis
    if _moralis is None:
        _moralis = MoralisConnector()
    return _moralis
