"""
Unified Label Service — Merges wallet_label_loader + address_labeler into one call.
===============================================================================
Sources:
  Static (loaded at startup into Redis):
    1. crypto-wallet-address-labels (105K+ Solana, 16K BSC/ETH, 15K phishing)
    2. OFAC crypto-sanctions-list
    3. Etherscan malicious labels CSV
    4. Internal wallet_database.json (Omega Forensic)

  Live (API calls on demand):
    5. Etherscan API — public address labels
    6. RolodETH — public GitHub label database
    7. walletLabels.xyz — public address label API
    8. bluepages.fyi — public address label API

  Built-in:
    9. CEX hot wallet labels from chain_registry

Returns a unified label report with deduplication and confidence scoring.
"""

import json
import logging
import os
from typing import Any

import httpx

from app.chain_registry import is_evm, is_solana

logger = logging.getLogger("wallet_memory.labels")

# ── RolodETH cache ──────────────────────────────────────────────

_ROLODETH_CACHE: dict[str, list[dict]] | None = None
_ROLODETH_LAST_FETCH: float = 0.0
_ROLODETH_TTL = 3600  # 1 hour


class LabelService:
    """Unified label resolution across static + live + built-in sources."""

    def __init__(self):
        self._http = httpx.AsyncClient(timeout=15.0)
        self._etherscan_key = os.getenv("ETHERSCAN_API_KEY", "")
        self._builtin_cex: dict[str, str] = {}
        self._load_builtin_cex()

    def _load_builtin_cex(self):
        """Load CEX hot wallet labels from chain_registry."""
        try:
            from app.chain_registry import CEX_HOT_WALLETS

            for _chain, wallets in CEX_HOT_WALLETS.items():
                for addr, label in wallets.items():
                    self._builtin_cex[addr.lower()] = label
        except Exception:
            pass

    async def resolve(self, address: str, chain: str) -> dict[str, Any] | None:
        """
        Resolve an address across all label sources.

        Returns:
            {
                "address": str,
                "chain": str,
                "labels": [{"source": str, "label": str, "category": str, "confidence": float}],
                "primary_label": str,
                "primary_category": str,
                "is_exchange": bool,
                "is_mev_bot": bool,
                "is_known_scam": bool,
                "is_defi_protocol": bool,
                "is_bridge": bool,
                "is_sanctioned": bool,
                "risk_contribution": float,
            }
        """
        addr = address.lower().strip()
        all_labels: list[dict[str, Any]] = []
        flags = {
            "is_exchange": False,
            "is_mev_bot": False,
            "is_known_scam": False,
            "is_defi_protocol": False,
            "is_bridge": False,
            "is_sanctioned": False,
        }

        # ── Source 1: Static Redis labels (wallet_label_loader) ───
        try:
            from .storage import get_storage

            storage = get_storage()
            redis = await storage._get_redis() if hasattr(storage, "_get_redis") else None
            # Try the direct redis method
            if not redis:
                await storage._ensure_connections()
                redis = storage._redis

            if redis:
                # Check chain-specific label
                label_json = await redis.get(f"rmi:label:{chain}:{addr}")
                if not label_json and not is_solana(chain):
                    # Try ethereum as fallback for EVM chains
                    label_json = await redis.get(f"rmi:label:ethereum:{addr}")

                if label_json:
                    label_data = json.loads(label_json)
                    all_labels.append(
                        {
                            "source": f"static_{label_data.get('chain', chain)}",
                            "label": label_data.get(
                                "address_name",
                                label_data.get(
                                    "dapp_name",
                                    label_data.get("protocol_name", label_data.get("label_type", "")),
                                ),
                            ),
                            "category": label_data.get("label_type", label_data.get("label_subtype", "")),
                            "confidence": 0.95,  # Static labels are high confidence
                        }
                    )

                # Check OFAC
                ofac_json = await redis.get(f"rmi:label:ofac:{addr}")
                if ofac_json:
                    ofac_data = json.loads(ofac_json)
                    flags["is_sanctioned"] = True
                    flags["is_known_scam"] = True
                    all_labels.append(
                        {
                            "source": "ofac",
                            "label": ofac_data.get("name", "OFAC Sanctioned"),
                            "category": "sanctioned",
                            "confidence": 1.0,
                        }
                    )

                # Check wallet database (Omega Forensic)
                wallet_json = await redis.get(f"rmi:wallet:labeled:{addr}")
                if wallet_json:
                    wallet_data = json.loads(wallet_json)
                    cat = wallet_data.get("category", "")
                    if cat in ("rug_puller", "scammer", "hacker", "phisher"):
                        flags["is_known_scam"] = True
                    all_labels.append(
                        {
                            "source": "omega_forensic",
                            "label": wallet_data.get("display_name", wallet_data.get("category", "")),
                            "category": cat,
                            "confidence": 0.9,
                        }
                    )
        except Exception as e:
            logger.debug(f"Static label lookup failed for {addr}: {e}")

        # ── Source 2: Built-in CEX labels ─────────────────────────
        if addr in self._builtin_cex:
            label = self._builtin_cex[addr]
            flags["is_exchange"] = True
            all_labels.append(
                {
                    "source": "builtin_cex",
                    "label": label,
                    "category": "exchange",
                    "confidence": 1.0,
                }
            )

        # ── Source 3: Etherscan API (live, EVM only) ──────────────
        if is_evm(chain) and self._etherscan_key:
            try:
                etherscan_labels = await self._query_etherscan(addr, chain)
                all_labels.extend(etherscan_labels)
                for lbl in etherscan_labels:
                    cat = lbl.get("category", "").lower()
                    if cat == "exchange":
                        flags["is_exchange"] = True
                    elif cat == "mev":
                        flags["is_mev_bot"] = True
                    elif cat in ("phishing", "scam", "hack"):
                        flags["is_known_scam"] = True
                    elif cat == "defi":
                        flags["is_defi_protocol"] = True
                    elif cat == "bridge":
                        flags["is_bridge"] = True
            except Exception as e:
                logger.debug(f"Etherscan label failed for {addr}: {e}")

        # ── Source 4: RolodETH (live, Ethereum, cached) ───────────
        if chain in ("ethereum", "eth"):
            try:
                rolodeth_labels = await self._query_rolodeth(addr)
                all_labels.extend(rolodeth_labels)
            except Exception as e:
                logger.debug(f"RolodETH failed for {addr}: {e}")

        # ── Source 5: walletLabels.xyz (live) ────────────────────
        try:
            wl_labels = await self._query_walletlabels_xyz(addr)
            all_labels.extend(wl_labels)
        except Exception as e:
            logger.debug(f"walletLabels.xyz failed for {addr}: {e}")

        # ── Source 6: bluepages.fyi (live) ────────────────────────
        try:
            bp_labels = await self._query_bluepages(addr)
            all_labels.extend(bp_labels)
        except Exception as e:
            logger.debug(f"bluepages.fyi failed for {addr}: {e}")

        # ── Deduplicate and rank ─────────────────────────────────
        deduped = self._deduplicate(all_labels)
        primary = self._pick_primary(deduped, flags)

        # Calculate risk contribution from labels
        risk_contrib = self._label_risk_contribution(deduped, flags)

        return {
            "address": addr,
            "chain": chain,
            "labels": deduped,
            "primary_label": primary.get("label", ""),
            "primary_category": primary.get("category", ""),
            **flags,
            "risk_contribution": risk_contrib,
        }

    # ── Live API methods ────────────────────────────────────────

    async def _query_etherscan(self, address: str, chain: str) -> list[dict]:
        """Query Etherscan account API for address labels."""
        from app.scanners.dev_reputation import ETHERSCAN_NETWORKS

        network = ETHERSCAN_NETWORKS.get(chain)
        if not network:
            return []

        url = network["url"]
        key = network["key"] or self._etherscan_key
        if not key:
            return []

        try:
            resp = await self._http.get(
                url,
                params={
                    "module": "account",
                    "action": "getaddressinfo",
                    "address": address,
                    "apikey": key,
                },
            )
            if resp.status_code != 200:
                return []
            data = resp.json()
            if data.get("status") != "1":
                return []

            result = data.get("result", {})
            labels = []
            if isinstance(result, dict):
                tag = result.get("tag", "")
                name = result.get("name", "")
                if name:
                    labels.append(
                        {
                            "source": "etherscan",
                            "label": name,
                            "category": self._infer_category(tag or name),
                            "confidence": 0.85,
                        }
                    )
            return labels
        except Exception:
            return []

    async def _query_rolodeth(self, address: str) -> list[dict]:
        """Query RolodETH GitHub label database."""
        global _ROLODETH_CACHE, _ROLODETH_LAST_FETCH
        import time

        now = time.time()
        if _ROLODETH_CACHE is None or (now - _ROLODETH_LAST_FETCH) > _ROLODETH_TTL:
            try:
                resp = await self._http.get("https://raw.githubusercontent.com/RoleTiker/rolodETH/main/rolodETH.json")
                if resp.status_code == 200:
                    _ROLODETH_CACHE = resp.json()
                    _ROLODETH_LAST_FETCH = now
            except Exception:
                pass

        if not _ROLODETH_CACHE:
            return []

        labels = []
        addr_lower = address.lower()
        for addr_key, entries in _ROLODETH_CACHE.items():
            if addr_lower == addr_key.lower():
                for entry in entries:
                    labels.append(
                        {
                            "source": "rolodeth",
                            "label": entry.get("name", entry.get("label", "")),
                            "category": self._infer_category(entry.get("tag", "")),
                            "confidence": 0.80,
                        }
                    )
                break

        return labels

    async def _query_walletlabels_xyz(self, address: str) -> list[dict]:
        """Query walletLabels.xyz API."""
        try:
            resp = await self._http.get(f"https://api.walletlabels.xyz/v1/address/{address}")
            if resp.status_code != 200:
                return []
            data = resp.json()
            labels = []
            for item in data.get("labels", data.get("result", [])):
                if isinstance(item, dict):
                    labels.append(
                        {
                            "source": "walletlabels.xyz",
                            "label": item.get("label", item.get("name", "")),
                            "category": self._infer_category(item.get("category", "")),
                            "confidence": 0.75,
                        }
                    )
            return labels
        except Exception:
            return []

    async def _query_bluepages(self, address: str) -> list[dict]:
        """Query bluepages.fyi API."""
        try:
            resp = await self._http.get(f"https://bluepages.fyi/api/v1/address/{address}")
            if resp.status_code != 200:
                return []
            data = resp.json()
            labels = []
            entry = data.get("label", data.get("name", ""))
            if entry:
                labels.append(
                    {
                        "source": "bluepages.fyi",
                        "label": entry,
                        "category": self._infer_category(data.get("category", "")),
                        "confidence": 0.70,
                    }
                )
            return labels
        except Exception:
            return []

    # ── Helpers ──────────────────────────────────────────────────

    def _infer_category(self, text: str) -> str:
        """Infer label category from text."""
        if not text:
            return "unknown"
        t = text.lower()
        if any(w in t for w in ("exchange", "binance", "coinbase", "kraken", "okx", "bitfinex")):
            return "exchange"
        if any(w in t for w in ("mev", "bot", "flashbot")):
            return "mev"
        if any(w in t for w in ("phish", "scam", "hack", "exploit", "rug", "drainer")):
            return "scam"
        if any(w in t for w in ("defi", "aave", "uniswap", "curve", "compound", "lido")):
            return "defi"
        if any(w in t for w in ("bridge", "hop", "across", "synapse", "stargate")):
            return "bridge"
        if any(w in t for w in ("deployer", "creator", "factory")):
            return "deployer"
        if any(w in t for w in ("sanction", "ofac")):
            return "sanctioned"
        return "wallet"

    def _deduplicate(self, labels: list[dict]) -> list[dict]:
        """Remove duplicate labels from different sources."""
        seen = set()
        deduped = []
        for lbl in labels:
            key = f"{lbl.get('label', '').lower()}:{lbl.get('category', '').lower()}"
            if key not in seen:
                seen.add(key)
                deduped.append(lbl)
        return deduped

    def _pick_primary(self, labels: list[dict], flags: dict) -> dict:
        """Pick the most informative primary label."""
        if not labels:
            return {"label": "", "category": "unknown"}

        # Priority: sanctioned > scam > exchange > defi > bridge > mev > wallet
        priority = {
            "sanctioned": 0,
            "scam": 1,
            "exchange": 2,
            "defi": 3,
            "bridge": 4,
            "mev": 5,
            "deployer": 6,
            "wallet": 7,
            "unknown": 8,
        }

        sorted_labels = sorted(
            labels,
            key=lambda line: (priority.get(line.get("category", "unknown"), 8), -line.get("confidence", 0)),
        )
        return sorted_labels[0] if sorted_labels else {"label": "", "category": "unknown"}

    def _label_risk_contribution(self, labels: list[dict], flags: dict) -> float:
        """Calculate risk score contribution from labels (0-40 of 100)."""
        score = 0.0

        # Flags contribute directly
        if flags.get("is_sanctioned"):
            score += 40
        elif flags.get("is_known_scam"):
            score += 30
        if flags.get("is_exchange"):
            score -= 5  # Exchanges are generally safe
        if flags.get("is_defi_protocol"):
            score -= 3  # Known DeFi is slightly positive

        # Label categories contribute
        for lbl in labels:
            cat = lbl.get("category", "").lower()
            if cat == "sanctioned":
                score = max(score, 40)
            elif cat == "scam":
                score += 15
            elif cat == "mev":
                score += 5

        return max(0, min(40, score))


# ── Global singleton ────────────────────────────────────────────

_label_service: LabelService | None = None


def get_label_service() -> LabelService:
    global _label_service
    if _label_service is None:
        _label_service = LabelService()
    return _label_service
