"""
Token Supply & Liquidity Analyzer
==================================
Comprehensive analysis of a token's supply mechanics, liquidity locks,
holder concentration, mint/burn capabilities, and hidden tax mechanisms.

Signals detected:
  - Circulating vs total supply ratio (dilution risk)
  - Mint functions (unlimited mint, capped mint, mint authorities)
  - Burn mechanisms (deflationary, manual burn, burn fee)
  - Top holder concentration (whale dominance, insider allocation)
  - Liquidity lock status (locked, unlocked, removed, partial lock)
  - Hidden buy/sell taxes (dynamic tax, blacklist-dependent tax)
  - Fake renounced ownership (admin key still active, proxy pattern)
  - Liquidity pool health (LP token concentration, pair age)
  - Multi-sig and timelock risks

Tier: Premium ($0.08-0.12)
Endpoint: POST /api/v1/x402-tools/token_supply_analyze
"""

import asyncio
import json
import logging
import re
import time
from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum
from typing import Any, cast
from urllib.parse import urlparse

logger = logging.getLogger("token_supply_analyzer")

# ── Free API sources ─────────────────────────────────────────────
DEXSCREENER_TOKENS = "https://api.dexscreener.com/latest/dex/tokens/{token_addr}"
DEXSCREENER_SEARCH = "https://api.dexscreener.com/latest/dex/search?q={}"
BIRDEYE_TOKEN = "https://public-api.birdeye.so/defi/v3/token/overview?address={token_addr}"
BIRDEYE_HOLDERS = "https://public-api.birdeye.so/defi/v3/token/holder?address={token_addr}"
SOLSCAN_TOKEN = "https://api.solscan.io/token/meta?token={token_addr}"
SOLSCAN_HOLDERS = "https://api.solscan.io/token/holders?token={token_addr}&limit=10"
ETHERSCAN_TOKEN = (
    "https://api.etherscan.io/api?module=account&action=tokeninfo&contractaddress={token_addr}"
)
BSCSCAN_TOKEN = (
    "https://api.bscscan.com/api?module=account&action=tokeninfo&contractaddress={token_addr}"
)

# Rate limiting
_RATE_LIMITERS: dict[str, float] = {}
_RATE_LIMIT_WINDOW = 0.5  # 500ms between requests to same host

# URL safety regex
_URL_SAFE = re.compile(
    r"^https?://[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*(?::\d{1,5})?(?:/[^\s\"<>]*)?$"
)

# ── Constants ────────────────────────────────────────────────────
KNOWN_BURN_ADDRESSES = {
    "0x0000000000000000000000000000000000000000",
    "0x000000000000000000000000000000000000dead",
    "0x000000000000000000000000000000000000dEaD",
    "dead00000000000000000000000000000000000000000000000000000000000000",
    "0x0000000000000000000000000000000000000001",
}

MAX_HOLDER_SAMPLE = 50  # Max top holders to analyze
CONCENTRATION_THRESHOLD_PCT = 90.0  # Top 10 holders > this = concentrated
SUPPLY_RISK_RATIO = 0.3  # Circ/total ratio below this = high dilution risk


class SupplyRisk(Enum):
    SAFE = "safe"
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class LockStatus(Enum):
    LOCKED = "locked"
    PARTIALLY_LOCKED = "partially_locked"
    UNLOCKED = "unlocked"
    REMOVED = "removed"
    UNKNOWN = "unknown"


@dataclass
class TokenSupplyProfile:
    """Complete supply and liquidity profile for a token."""

    # Token basics
    address: str
    chain: str = ""
    symbol: str = ""
    name: str = ""
    decimals: int = 18

    # Supply data
    total_supply_raw: int = 0
    circulating_supply_raw: int = 0
    max_supply_raw: int = 0
    total_supply: float = 0.0
    circulating_supply: float = 0.0
    max_supply: float = 0.0
    burned_supply: float = 0.0

    # Supply mechanics
    has_mint_function: bool = False
    mint_function_type: str = "none"  # none, unlimited, capped, ownable
    mint_authority: str = ""
    has_burn_function: bool = False
    burn_function_type: str = "none"  # none, manual, automated, fee
    is_deflationary: bool = False

    # Holder concentration
    top_10_holder_pct: float = 0.0
    top_50_holder_pct: float = 0.0
    deployer_hold_pct: float = 0.0
    holder_count: int = 0
    whale_holdings: list[dict] = field(default_factory=list)

    # Liquidity analysis
    lock_status: LockStatus = LockStatus.UNKNOWN
    liquidity_lock_address: str = ""
    liquidity_lock_expiry: str = ""
    lp_token_pct_held_by_deployer: float = 0.0
    pair_age_days: float = 0.0
    pair_liquidity_usd: float = 0.0

    # Tax analysis
    buy_tax_pct: float = 0.0
    sell_tax_pct: float = 0.0
    has_dynamic_tax: bool = False
    has_blacklist: bool = False
    has_max_wallet_limit: bool = False

    # Ownership
    is_renounced: bool = False
    owner_address: str = ""
    has_proxy_admin: bool = False
    has_multisig: bool = False
    has_timelock: bool = False

    # Scoring
    supply_risk: SupplyRisk = SupplyRisk.SAFE
    supply_risk_score: float = 0.0
    concentration_risk: SupplyRisk = SupplyRisk.SAFE
    concentration_score: float = 0.0
    liquidity_risk: SupplyRisk = SupplyRisk.SAFE
    liquidity_score: float = 0.0
    overall_risk_score: float = 0.0
    overall_risk: SupplyRisk = SupplyRisk.SAFE

    # Raw data
    patterns_detected: list[str] = field(default_factory=list)
    recommendations: list[str] = field(default_factory=list)


# ── Utility functions ────────────────────────────────────────────


def _validate_url(url: str) -> bool:
    """Basic URL validation to prevent SSRF."""
    return bool(_URL_SAFE.match(url))


async def _rate_limit_host(host: str) -> None:
    """Simple per-host rate limiter."""
    now = time.time()
    last = _RATE_LIMITERS.get(host, 0.0)
    wait = _RATE_LIMIT_WINDOW - (now - last)
    if wait > 0:
        await asyncio.sleep(wait)
    _RATE_LIMITERS[host] = time.time()


def _detect_chain(address: str) -> str:
    """Detect chain from address format."""
    addr = address.strip()
    if addr.startswith("0x"):
        return "ethereum"  # Could also be BSC, Polygon, Arbitrum, etc.
    elif len(addr) >= 32 and not addr.startswith("0x"):
        return "solana"
    return "unknown"


def _normalize_address(address: str) -> str:
    """Normalize address to lowercase for comparison."""
    return address.lower().strip()


def _classify_risk(score: float) -> SupplyRisk:
    """Convert numeric score to risk level."""
    if score <= 10:
        return SupplyRisk.SAFE
    elif score <= 25:
        return SupplyRisk.LOW
    elif score <= 50:
        return SupplyRisk.MEDIUM
    elif score <= 75:
        return SupplyRisk.HIGH
    else:
        return SupplyRisk.CRITICAL


# ── Supply Analysis ──────────────────────────────────────────────


def _analyze_supply_mechanics(profile: TokenSupplyProfile) -> None:
    """
    Analyze token supply data for dilution risk and manipulation patterns.
    """
    score = 0.0
    total = profile.total_supply
    circ = profile.circulating_supply
    max_sup = profile.max_supply
    burned = profile.burned_supply

    # ── Circulating vs total supply ratio ──────────────────────
    if total > 0 and circ > 0:
        ratio = circ / total
        profile.patterns_detected.append(f"circ_supply_ratio:{ratio:.2%}")

        if ratio < SUPPLY_RISK_RATIO:
            score += 35
            profile.patterns_detected.append(
                "high_dilution_risk:circ_supply_less_than_30%_of_total"
            )
            profile.recommendations.append(
                f"⚠ Only {ratio:.0%} of total supply is circulating - high dilution risk. "
                "Check vesting/unlock schedule."
            )
        elif ratio < 0.5:
            score += 20
            profile.patterns_detected.append(
                "moderate_dilution_risk:circ_supply_less_than_50%_of_total"
            )
            profile.recommendations.append(
                f"⚠ ~{ratio:.0%} of supply is circulating. Verify lockup/vesting terms."
            )
    elif total > 0 and circ == 0:
        score += 15
        profile.patterns_detected.append("no_circulating_supply_data")

    # ── Max supply vs total supply (capped vs uncapped) ────────
    if max_sup > 0:
        if total > max_sup:
            score += 40
            profile.patterns_detected.append(
                f"supply_exceeds_max:total_{total:.2f}_exceeds_max_{max_sup:.2f}"
            )
            profile.recommendations.append(
                "🚨 Total supply exceeds max supply - possible mint/printing."
            )
        else:
            remaining = max_sup - total
            remaining_pct = (remaining / max_sup) * 100
            if remaining_pct > 50:
                score += 25
                profile.patterns_detected.append(
                    f"large_mint_headroom:{remaining_pct:.0f}%_of_max_supply_unminted"
                )
                profile.recommendations.append(
                    f"🚨 {remaining_pct:.0f}% of max supply is unminted - significant dilution risk."
                )
    else:
        # No max supply = potentially unlimited mint
        if profile.has_mint_function:
            score += 30
            profile.patterns_detected.append("no_max_supply_with_mint:unlimited_mint_possible")
            profile.recommendations.append(
                "🚨 No max supply set AND mint function exists - potentially unlimited mint."
            )

    # ── Burn amount ─────────────────────────────────────────────
    if burned > 0 and total > 0:
        burn_pct = (burned / total) * 100
        profile.patterns_detected.append(f"burned_supply:{burn_pct:.1f}%")
        if burn_pct > 50:
            score -= 15  # Heavy burning is generally positive
            profile.recommendations.append(
                f"✅ {burn_pct:.0f}% of supply has been burned - strong deflationary pressure."
            )

    # ── Mint function analysis ──────────────────────────────────
    if profile.has_mint_function:
        if profile.mint_function_type == "unlimited":
            score += 40
            profile.patterns_detected.append("unlimited_mint_function")
            profile.recommendations.append(
                "🚨 Token has an unrestricted mint function - supply can be inflated at will."
            )
        elif profile.mint_function_type == "ownable":
            score += 25
            profile.patterns_detected.append("ownable_mint_function:owner_can_mint")
            if not profile.is_renounced:
                score += 10
                profile.recommendations.append(
                    "⚠ Owner-controlled mint function - supply can be increased if ownership is not renounced."
                )
        elif profile.mint_function_type == "capped":
            score += 5
            profile.patterns_detected.append("capped_mint_function")

    # ── Deflationary analysis ───────────────────────────────────
    if profile.is_deflationary:
        score -= 10
        profile.patterns_detected.append("deflationary_token")
        profile.recommendations.append(
            "✅ Deflationary mechanism active (auto-burn on transactions)."
        )

    profile.supply_risk_score = max(0.0, min(100.0, score))
    profile.supply_risk = _classify_risk(profile.supply_risk_score)


def _analyze_concentration(profile: TokenSupplyProfile) -> None:
    """
    Analyze holder concentration for whale dominance and insider risk.
    """
    score = 0.0
    top10 = profile.top_10_holder_pct
    top50 = profile.top_50_holder_pct
    deployer_hold = profile.deployer_hold_pct

    # ── Top 10 holder concentration ─────────────────────────────
    if top10 > 0:
        profile.patterns_detected.append(f"top_10_holder_pct:{top10:.1f}%")

        if top10 >= 99.0:
            score += 50
            profile.patterns_detected.append("extreme_concentration:top_10_holds_>99%")
            profile.recommendations.append(
                "🚨 Top 10 wallets hold >99% of supply - extremely concentrated, likely a scam."
            )
        elif top10 >= CONCENTRATION_THRESHOLD_PCT:
            score += 40
            profile.patterns_detected.append("critical_concentration:top_10_holds_>90%")
            profile.recommendations.append(
                f"🚨 Top 10 wallets hold {top10:.0f}% of supply - extreme concentration risk."
            )
        elif top10 >= 70:
            score += 25
            profile.patterns_detected.append("high_concentration:top_10_holds_>70%")
            profile.recommendations.append(
                f"⚠ Top 10 wallets hold {top10:.0f}% of supply - high concentration."
            )
        elif top10 >= 50:
            score += 15
            profile.patterns_detected.append("moderate_concentration:top_10_holds_>50%")
            profile.recommendations.append(
                f"⚠ Top 10 wallets hold {top10:.0f}% of supply - moderate concentration."
            )

    # ── Top 50 holder concentration ─────────────────────────────
    if top50 > 0:
        if top50 >= 99.0:
            score += 25
            profile.patterns_detected.append("extreme_concentration_top50")
        elif top50 >= 95:
            score += 15
            profile.patterns_detected.append("critical_concentration_top50")
        elif top50 >= 80:
            score += 10
            profile.patterns_detected.append("high_concentration_top50")

    # ── Deployer holding ────────────────────────────────────────
    if deployer_hold > 0:
        profile.patterns_detected.append(f"deployer_hold_pct:{deployer_hold:.1f}%")

        if deployer_hold > 50:
            score += 40
            profile.patterns_detected.append("deployer_holds_majority")
            profile.recommendations.append(
                f"🚨 Deployer holds {deployer_hold:.0f}% of supply - complete control, very high risk."
            )
        elif deployer_hold > 20:
            score += 20
            profile.patterns_detected.append("deployer_holds_significant")
            profile.recommendations.append(
                f"⚠ Deployer holds {deployer_hold:.0f}% of supply - significant insider allocation."
            )
        elif deployer_hold > 5:
            score += 10
            profile.patterns_detected.append("deployer_holds_moderate")
            profile.recommendations.append(
                f"(i) Deployer holds {deployer_hold:.0f}% of supply - moderate allocation."
            )

    # ── Whale holdings analysis ─────────────────────────────────
    for w in profile.whale_holdings:
        if w.get("pct", 0) > 5:
            score += 5
            profile.patterns_detected.append(
                f"whale_gt_5%:address_{w.get('address', '')[:8]}..._{w.get('pct', 0):.1f}%"
            )

    profile.concentration_score = max(0.0, min(100.0, score))
    profile.concentration_risk = _classify_risk(profile.concentration_score)


def _analyze_liquidity(profile: TokenSupplyProfile) -> None:
    """
    Analyze liquidity pool health and lock status.
    """
    score = 0.0

    # ── Lock status ─────────────────────────────────────────────
    if profile.lock_status == LockStatus.REMOVED:
        score += 50
        profile.patterns_detected.append("liquidity_removed")
        profile.recommendations.append(
            "🚨 Liquidity has been removed from the pool - rug pull risk."
        )
    elif profile.lock_status == LockStatus.UNLOCKED:
        score += 30
        profile.patterns_detected.append("liquidity_unlocked")
        profile.recommendations.append(
            "🚨 LP tokens are unlocked - deployer can remove liquidity at any time."
        )
    elif profile.lock_status == LockStatus.PARTIALLY_LOCKED:
        score += 15
        profile.patterns_detected.append("liquidity_partially_locked")
        profile.recommendations.append(
            "⚠ Only part of the liquidity is locked. Verify the locked portion."
        )
    elif profile.lock_status == LockStatus.LOCKED:
        score -= 15
        profile.patterns_detected.append("liquidity_locked")
        if profile.liquidity_lock_expiry:
            profile.recommendations.append(
                f"✅ Liquidity locked until {profile.liquidity_lock_expiry}. Good practice."
            )
        else:
            profile.recommendations.append("✅ Liquidity is locked.")

    # ── LP token concentration ──────────────────────────────────
    if profile.lp_token_pct_held_by_deployer > 50:
        score += 25
        profile.patterns_detected.append("deployer_holds_lp_majority")
        profile.recommendations.append("🚨 Deployer holds >50% of LP tokens - can drain pool.")
    elif profile.lp_token_pct_held_by_deployer > 10:
        score += 10
        profile.patterns_detected.append("deployer_holds_significant_lp")
        profile.recommendations.append(
            f"⚠ Deployer holds {profile.lp_token_pct_held_by_deployer:.0f}% of LP tokens."
        )

    # ── Pair age ────────────────────────────────────────────────
    if profile.pair_age_days > 0:
        if profile.pair_age_days < 1:
            score += 20
            profile.patterns_detected.append("very_new_pair:less_than_1_day_old")
            profile.recommendations.append(
                "⚠ Liquidity pair is less than 1 day old - very high risk."
            )
        elif profile.pair_age_days < 7:
            score += 10
            profile.patterns_detected.append(f"new_pair:{profile.pair_age_days:.0f}_days_old")
            profile.recommendations.append(
                f"⚠ Pair is only {profile.pair_age_days:.0f} days old - higher risk."
            )
        elif profile.pair_age_days > 30:
            score -= 10
            profile.patterns_detected.append("established_pair:>30_days_old")

    # ── Liquidity depth ─────────────────────────────────────────
    if profile.pair_liquidity_usd > 0:
        if profile.pair_liquidity_usd < 1000:
            score += 25
            profile.patterns_detected.append("very_low_liquidity:<$1k")
            profile.recommendations.append(
                "🚨 Very low liquidity (<$1K) - high slippage and manipulation risk."
            )
        elif profile.pair_liquidity_usd < 10000:
            score += 15
            profile.patterns_detected.append("low_liquidity:<$10k")
            profile.recommendations.append("⚠ Low liquidity (<$10K) - moderate slippage risk.")
        elif profile.pair_liquidity_usd > 100000:
            score -= 10
            profile.patterns_detected.append("healthy_liquidity:>$100k")

    profile.liquidity_score = max(0.0, min(100.0, score))
    profile.liquidity_risk = _classify_risk(profile.liquidity_score)


def _compute_overall_risk(profile: TokenSupplyProfile) -> None:
    """Compute overall risk score from all sub-scores."""
    weights = {
        "supply": 0.35,
        "concentration": 0.30,
        "liquidity": 0.20,
        "tax": 0.10,
        "ownership": 0.05,
    }

    tax_score = 0.0
    if profile.has_dynamic_tax:
        tax_score += 30
        profile.patterns_detected.append("dynamic_tax:tax_can_change")
        profile.recommendations.append(
            "⚠ Dynamic tax detected - buy/sell tax can change at any time."
        )
    if profile.has_blacklist:
        tax_score += 25
        profile.patterns_detected.append("blacklist_function")
        profile.recommendations.append(
            "⚠ Token has a blacklist function - holders can be blocked from selling."
        )
    if profile.buy_tax_pct > 10:
        tax_score += 15
        profile.patterns_detected.append(f"high_buy_tax:{profile.buy_tax_pct:.1f}%")
    if profile.sell_tax_pct > 15:
        tax_score += 20
        profile.patterns_detected.append(f"very_high_sell_tax:{profile.sell_tax_pct:.1f}%")
        profile.recommendations.append(
            f"🚨 Sell tax of {profile.sell_tax_pct:.0f}% is very high - may be a honeypot."
        )

    ownership_score = 0.0
    if not profile.is_renounced:
        ownership_score += 15
        profile.patterns_detected.append("ownership_not_renounced")
        profile.recommendations.append("⚠ Ownership is NOT renounced - contract can be modified.")
    if profile.has_proxy_admin:
        ownership_score += 20
        profile.patterns_detected.append("proxy_admin:contract_can_be_upgraded")
        profile.recommendations.append(
            "🚨 Token uses a proxy pattern with admin - contract can be upgraded (rug risk)."
        )
    if profile.has_multisig:
        ownership_score -= 10
        profile.patterns_detected.append("multisig_owner")
        profile.recommendations.append("✅ Ownership uses multi-sig - adds security layer.")

    profile.overall_risk_score = (
        profile.supply_risk_score * weights["supply"]
        + profile.concentration_score * weights["concentration"]
        + profile.liquidity_score * weights["liquidity"]
        + tax_score * weights["tax"]
        + ownership_score * weights["ownership"]
    )
    profile.overall_risk = _classify_risk(profile.overall_risk_score)


# ── Main analysis function ───────────────────────────────────────


async def analyze_token_supply(token_address: str, chain: str = "") -> TokenSupplyProfile:
    """
    Main entry point. Analyzes token supply, liquidity, and holder concentration.

    Args:
        token_address: The token contract address.
        chain: Optional chain hint (ethereum, solana, bsc, polygon, etc.).

    Returns:
        TokenSupplyProfile with all analysis results.
    """
    profile = TokenSupplyProfile(address=token_address)

    if not chain:
        chain = _detect_chain(token_address)
    profile.chain = chain

    logger.info(
        "Analyzing token supply for %s on %s",
        token_address[:16],
        chain,
    )

    # ── Fetch data from free APIs ───────────────────────────────
    dexscreener_data = await _fetch_dexscreener(token_address, chain)
    birdeye_data = await _fetch_birdeye(token_address)

    # ── Parse DexScreener data ──────────────────────────────────
    if dexscreener_data:
        pairs = dexscreener_data.get("pairs", [])
        if pairs:
            pair = pairs[0]
            profile.symbol = pair.get("baseToken", {}).get("symbol", "")
            profile.name = pair.get("baseToken", {}).get("name", "")
            profile.decimals = int(pair.get("baseToken", {}).get("decimals", 18))
            profile.pair_liquidity_usd = float(pair.get("liquidity", {}).get("usd", 0))
            profile.pair_age_days = _compute_pair_age_days(pair.get("pairCreatedAt", 0))

            # Tax data (if available from DexScreener)
            if "buyTax" in pair:
                profile.buy_tax_pct = float(pair.get("buyTax", 0))
            if "sellTax" in pair:
                profile.sell_tax_pct = float(pair.get("sellTax", 0))

            # LP holder info
            lp_holders = pair.get("liquidity", {}).get("holders", [])
            if lp_holders:
                for h in lp_holders:
                    if h.get("address", "").lower() in (
                        _normalize_address(profile.owner_address),
                        _normalize_address(profile.address),
                    ):
                        profile.lp_token_pct_held_by_deployer = float(h.get("percent", 0))
                        break

    # ── Parse Birdeye data ──────────────────────────────────────
    if birdeye_data:
        # Supply data
        profile.total_supply_raw = int(
            birdeye_data.get("totalSupply", birdeye_data.get("supply", 0))
        )
        profile.circulating_supply_raw = int(birdeye_data.get("circulatingSupply", 0))
        profile.max_supply_raw = int(birdeye_data.get("maxSupply", 0))

        # Convert based on decimals
        divider = 10**profile.decimals
        if profile.total_supply_raw > 0:
            profile.total_supply = profile.total_supply_raw / divider
        if profile.circulating_supply_raw > 0:
            profile.circulating_supply = profile.circulating_supply_raw / divider
        if profile.max_supply_raw > 0:
            profile.max_supply = profile.max_supply_raw / divider

        # Holder data
        holders = birdeye_data.get("holders", [])
        if holders:
            profile.holder_count = len(holders)
            top10_pct = 0.0
            top50_pct = 0.0
            for i, h in enumerate(holders):
                pct = float(h.get("percent", h.get("percentage", 0)))
                if i < 10:
                    top10_pct += pct
                if i < 50:
                    top50_pct += pct
                if i < MAX_HOLDER_SAMPLE:
                    profile.whale_holdings.append(
                        {
                            "address": h.get("address", ""),
                            "pct": pct,
                            "balance": h.get("balance", 0),
                        }
                    )

            profile.top_10_holder_pct = top10_pct
            profile.top_50_holder_pct = top50_pct

            # Check deployer holding
            if profile.owner_address:
                for h in holders:
                    if h.get("address", "").lower() == _normalize_address(profile.owner_address):
                        profile.deployer_hold_pct = float(h.get("percent", h.get("percentage", 0)))
                        break

        # Supply mechanics
        profile.has_mint_function = birdeye_data.get("mintable", False)
        profile.has_burn_function = birdeye_data.get("burnable", False)
        profile.is_deflationary = birdeye_data.get("deflationary", False)

        # Lock status
        lock_data = birdeye_data.get("liquidityLock", {})
        if lock_data:
            locked = lock_data.get("locked", False)
            expiry = lock_data.get("expiry", "")
            if locked:
                profile.lock_status = LockStatus.LOCKED
                profile.liquidity_lock_expiry = expiry
            elif lock_data.get("removed", False):
                profile.lock_status = LockStatus.REMOVED
            else:
                profile.lock_status = LockStatus.UNLOCKED

        # Ownership
        profile.is_renounced = birdeye_data.get("renounced", False)
        profile.owner_address = birdeye_data.get("owner", "")
        profile.has_proxy_admin = birdeye_data.get("proxy", False)
        profile.has_multisig = birdeye_data.get("multisig", False)

        # Tax
        if not profile.buy_tax_pct:
            profile.buy_tax_pct = float(birdeye_data.get("buyTax", 0))
        if not profile.sell_tax_pct:
            profile.sell_tax_pct = float(birdeye_data.get("sellTax", 0))
        profile.has_dynamic_tax = birdeye_data.get("dynamicTax", False)
        profile.has_blacklist = birdeye_data.get("blacklist", False)
        profile.has_max_wallet_limit = birdeye_data.get("maxWallet", False)

    # ── Run analysis ────────────────────────────────────────────
    _analyze_supply_mechanics(profile)
    _analyze_concentration(profile)
    _analyze_liquidity(profile)
    _compute_overall_risk(profile)

    # ── Summary recommendation ──────────────────────────────────
    if profile.overall_risk == SupplyRisk.CRITICAL:
        profile.recommendations.insert(
            0, "🚨 CRITICAL: Do NOT invest. Multiple severe risk factors detected."
        )
    elif profile.overall_risk == SupplyRisk.HIGH:
        profile.recommendations.insert(
            0, "⚠ HIGH RISK: Significant red flags. Proceed with extreme caution."
        )
    elif profile.overall_risk == SupplyRisk.MEDIUM:
        profile.recommendations.insert(
            0, "⚠ MEDIUM RISK: Some concerns. Verify all claims before investing."
        )
    elif profile.overall_risk == SupplyRisk.LOW:
        profile.recommendations.insert(
            0, "✅ LOW RISK: Reasonable tokenomics. Standard precautions advised."
        )
    else:
        profile.recommendations.insert(0, "✅ SAFE: Tokenomics appear healthy. Always DYOR.")

    return profile


# ── API fetch helpers ────────────────────────────────────────────


async def _fetch_dexscreener(token_address: str, chain: str) -> dict[str, Any]:
    """Fetch token data from DexScreener API."""
    url = DEXSCREENER_TOKENS.format(token_addr=token_address)
    if not _validate_url(url):
        logger.warning("Invalid DexScreener URL for %s", token_address[:16])
        return {}

    return await _fetch_json(url, "DexScreener")


async def _fetch_birdeye(token_address: str) -> dict[str, Any]:
    """Fetch token data from Birdeye API."""
    url = BIRDEYE_TOKEN.format(token_addr=token_address)
    if not _validate_url(url):
        logger.warning("Invalid Birdeye URL for %s", token_address[:16])
        return {}

    return await _fetch_json(url, "Birdeye")


async def _fetch_json(url: str, source: str) -> dict[str, Any]:
    """Fetch JSON from a URL with rate limiting."""
    parsed = urlparse(url)
    host = parsed.hostname or "unknown"

    try:
        await _rate_limit_host(host)

        import aiohttp

        timeout = aiohttp.ClientTimeout(total=10)
        async with (
            aiohttp.ClientSession(timeout=timeout) as session,
            session.get(
                url,
                headers={
                    "User-Agent": "RMI-TokenSupplyAnalyzer/1.0",
                    "Accept": "application/json",
                },
            ) as resp,
        ):
            if resp.status == 200:
                data = await resp.json()
                # Birdeye wraps data in data field
                if isinstance(data, dict) and "data" in data:
                    return cast("dict[str, Any]", data["data"])
                return cast("dict[str, Any]", data)
            elif resp.status == 429:
                logger.warning("Rate limited by %s, waiting 2s...", source)
                await asyncio.sleep(2)
                return {}
            else:
                logger.warning(
                    "%s returned status %d for %s",
                    source,
                    resp.status,
                    url[:60],
                )
                return {}
    except TimeoutError:
        logger.warning("Timeout fetching from %s: %s", source, url[:60])
        return {}
    except Exception as e:
        logger.warning("Error fetching from %s: %s", source, e)
        return {}


def _compute_pair_age_days(created_at_unix: int) -> float:
    """Compute pair age in days from Unix timestamp."""
    if not created_at_unix:
        return 0.0
    try:
        created = datetime.fromtimestamp(created_at_unix / 1000, tz=UTC)  # DexScreener uses ms
        now = datetime.now(tz=UTC)
        return max(0.0, (now - created).total_seconds() / 86400)
    except (ValueError, OSError):
        return 0.0


# ── Batch / streaming analysis ───────────────────────────────────


async def analyze_multiple_tokens(
    addresses: list[str], chain: str = ""
) -> list[TokenSupplyProfile]:
    """Analyze multiple tokens concurrently."""
    tasks = [analyze_token_supply(addr, chain) for addr in addresses]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    valid: list[TokenSupplyProfile] = []
    for r in results:
        if isinstance(r, TokenSupplyProfile):
            valid.append(r)
    return valid


# ── CLI entry point ──────────────────────────────────────────────


async def main() -> None:
    """Run supply analysis from command line."""
    import argparse

    parser = argparse.ArgumentParser(description="Token Supply & Liquidity Analyzer")
    parser.add_argument("address", help="Token contract address")
    parser.add_argument(
        "--chain",
        default="",
        help="Chain hint (ethereum, solana, bsc, polygon)",
    )
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    args = parser.parse_args()

    profile = await analyze_token_supply(args.address, args.chain)

    if args.json:
        import dataclasses

        def _serialize(obj: Any) -> str:
            if isinstance(obj, Enum):
                val = obj.value
                return str(val)
            if isinstance(obj, datetime):
                return obj.isoformat()
            return str(obj)

        print(
            json.dumps(
                dataclasses.asdict(profile),
                indent=2,
                default=_serialize,
            )
        )
    else:
        print(f"\n{'=' * 60}")
        print(f"TOKEN SUPPLY ANALYSIS: {profile.symbol} ({profile.name})")
        print(f"{'=' * 60}")
        print(f"Address: {profile.address}")
        print(f"Chain:   {profile.chain}")
        print("\n── Supply ──")
        print(f"  Total supply:     {profile.total_supply:,.2f}")
        print(f"  Circulating:      {profile.circulating_supply:,.2f}")
        print(f"  Max supply:       {profile.max_supply:,.2f}")
        print(f"  Burned:           {profile.burned_supply:,.2f}")
        print(
            f"  Has mint:         {'YES' if profile.has_mint_function else 'no'} ({profile.mint_function_type})"
        )
        print(
            f"  Has burn:         {'YES' if profile.has_burn_function else 'no'} ({profile.burn_function_type})"
        )
        print(f"  Deflationary:     {'YES' if profile.is_deflationary else 'no'}")
        print("\n── Concentration ──")
        print(f"  Holders:          {profile.holder_count:,}")
        print(f"  Top 10 hold:      {profile.top_10_holder_pct:.1f}%")
        print(f"  Top 50 hold:      {profile.top_50_holder_pct:.1f}%")
        print(f"  Deployer holds:   {profile.deployer_hold_pct:.1f}%")
        print("\n── Liquidity ──")
        print(f"  Lock status:      {profile.lock_status.value}")
        print(f"  Lock expiry:      {profile.liquidity_lock_expiry or 'N/A'}")
        print(f"  Liquidity (USD):  ${profile.pair_liquidity_usd:,.2f}")
        print(f"  Pair age (days):  {profile.pair_age_days:.1f}")
        print(f"  LP by deployer:   {profile.lp_token_pct_held_by_deployer:.1f}%")
        print("\n── Tax ──")
        print(f"  Buy tax:          {profile.buy_tax_pct:.1f}%")
        print(f"  Sell tax:         {profile.sell_tax_pct:.1f}%")
        print(f"  Dynamic tax:      {'YES' if profile.has_dynamic_tax else 'no'}")
        print(f"  Blacklist:        {'YES' if profile.has_blacklist else 'no'}")
        print("\n── Ownership ──")
        print(f"  Renounced:        {'YES' if profile.is_renounced else 'NO'}")
        print(f"  Proxy admin:      {'YES' if profile.has_proxy_admin else 'no'}")
        print(f"  Multi-sig:        {'YES' if profile.has_multisig else 'no'}")
        print("\n── Risk Scores ──")
        print(
            f"  Supply risk:      {profile.supply_risk.value} ({profile.supply_risk_score:.0f}/100)"
        )
        print(
            f"  Concentration:    {profile.concentration_risk.value} ({profile.concentration_score:.0f}/100)"
        )
        print(
            f"  Liquidity risk:   {profile.liquidity_risk.value} ({profile.liquidity_score:.0f}/100)"
        )
        print(
            f"  OVERALL RISK:     {profile.overall_risk.value} ({profile.overall_risk_score:.0f}/100)"
        )
        print("\n── Patterns Detected ──")
        for p in profile.patterns_detected:
            print(f"  • {p}")
        print("\n── Recommendations ──")
        for r in profile.recommendations:
            print(f"  {r}")
        print()


if __name__ == "__main__":
    asyncio.run(main())
