"""
RMI DataBus Fallback Middleware - Universal Single-Source Pipeline
====================================================================
Every x402 tool gets DataBus as its data backbone. If the primary endpoint
returns empty data, an error, or times out, DataBus.fetch() catches it with
its 38-chain fallback system and caching layer.

Architecture:
  Request → Primary endpoint → success with real data? → Response
                                  ↓ empty/error/timeout
           → DataBus.fetch(data_type, ...) → success? → Response + metadata
                                               ↓ fail
           → Original error response (DataBus exhausted all sources)

Zero internal source names leak to clients. Descriptions are bot-friendly.
The `_fallback` and `_databus_chain` fields tell bots this data is verified
through multi-source consensus, not a single point of failure.
"""

import logging
from typing import Any

from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse

logger = logging.getLogger("x402_databus_fallback")

# ── Complete Tool ID → DataBus data_type mapping ──────────────────
# Every tool maps to at least one DataBus chain. Where a tool's primary
# function doesn't match a chain exactly, we map to the closest chain
# that provides relevant data. Composite tools map to their most
# important underlying data need.

TOOL_TO_DATABUS = {
    # ─── Price & Token Intelligence ───
    "token_price": "token_price",
    "token_detail": "token_detail",
    "token_deep_dive": "token_detail",
    "token_comparison": "token_price",
    "token_forensics": "token_detail",
    "token_pulse": "token_price",
    "token_velocity": "token_detail",
    "token_distribution_health": "token_detail",
    "token_age": "token_detail",
    "pulse": "token_price",
    "fresh_pair": "dex_data",
    "dex_volume_rank": "dex_data",
    # ─── Market Data ───
    "market_overview": "market_overview",
    "market_movers": "market_movers",
    "trending": "trending",
    "tvl": "tvl",
    "defi_protocols": "defi_protocols",
    "chain_health": "tvl",
    "funding_rate": "market_overview",
    "liquidation_heatmap": "market_movers",
    "volatility_surface": "market_movers",
    "volume_profile": "market_movers",
    "sector_rotation": "market_overview",
    "options_flow": "market_movers",
    "orderbook_imbalance": "dex_data",
    # ─── DEX & DeFi ───
    "dex_data": "dex_data",
    "defi_yield_scanner": "tvl",
    "defi_position": "defi_protocols",
    "yield_aggregator": "tvl",
    "impermanent_loss": "defi_protocols",
    "liquidity_depth": "dex_data",
    "liquidity_flow": "dex_data",
    "liquidity_migration": "dex_data",
    "liquidity_verify": "dex_data",
    "bridge_security": "tvl",
    # ─── Wallet Intelligence ───
    "wallet_balance": "wallet_balance",
    "wallet_profile": "wallet_profile",
    "wallet_labels": "wallet_labels",
    "wallet_tokens": "wallet_tokens",
    "wallet_pnl": "wallet_pnl",
    "wallet_cluster": "wallet_cluster",
    "wallet_graph": "wallet_cluster",
    "wallet": "wallet_profile",
    "whale": "wallet_profile",
    "whale_profile": "wallet_profile",
    "whale_scan": "wallet_profile",
    "whale_accumulation": "smart_money",
    "whale_network_map": "wallet_cluster",
    "full_wallet_dossier": "wallet_profile",
    "wallet_label_registry": "wallet_labels",
    "wallet_drain_scanner": "risk_scan",
    "wallet_cluster_score": "wallet_cluster",
    "smart_contract_interactions": "wallet_profile",
    "portfolio_tracker": "portfolio",
    "portfolio_aggregate": "portfolio",
    "portfolio_risk": "portfolio",
    # ─── Smart Money & Tracking ───
    "smart_money": "smart_money",
    "smart_money_alpha": "smart_money",
    "smartmoney": "smart_money",
    "gmgn_smart_money": "gmgn_smart_money",
    "copy_trade_finder": "gmgn_smart_money",
    "insider": "smart_money",
    "insider_network": "entity_intel",
    "dormant_whale_alert": "smart_money",
    # ─── Cross-Chain & Forensics ───
    "cross_chain": "cross_chain",
    "cross_chain_trace": "cross_chain",
    "cross_chain_whale": "cross_chain",
    "funding_source": "funding_source",
    "fund_flow": "funding_source",
    # ─── Risk & Security ───
    "risk_scan": "risk_scan",
    "rug_shield": "risk_scan",
    "rugshield": "risk_scan",
    "rug_pull_predictor": "risk_scan",
    "rug_probability": "risk_scan",
    "honeypot_check": "risk_scan",
    "threat_check": "threat_check",
    "sentinel_scan": "sentinel_deep",
    "sentinel_deep": "sentinel_deep",
    "audit": "contract_scan",
    "deep_contract_audit": "contract_scan",
    "contract_scan": "contract_scan",
    "static_analysis": "contract_scan",
    "decompiler_analysis": "contract_scan",
    "contract_diff": "contract_scan",
    "contract_upgrade_monitor": "contract_scan",
    "reentrancy_scanner": "contract_scan",
    "proxy_detect": "contract_scan",
    "scam_database": "threat_check",
    "urlcheck": "threat_check",
    "url_scam_detector": "threat_check",
    "dust_attack_detect": "threat_check",
    "phantom_mint_detect": "threat_check",
    "privilege_escalation": "contract_scan",
    # ─── Bundle & MEV Detection ───
    "bundle_detect": "bundle_detect",
    "bundler_detect": "bundle_detect",
    "mev_alert": "bundle_detect",
    "mev_detect": "bundle_detect",
    "mev_protection": "bundle_detect",
    "flash_loan_detect": "bundle_detect",
    "pump_dump_detect": "bundle_detect",
    "pumpfun_analysis": "risk_scan",
    # ─── Entity & Labels ───
    "entity_intel": "entity_intel",
    "arkham_labels": "arkham_labels",
    "arkham_entity": "arkham_entity",
    "arkham_portfolio": "arkham_portfolio",
    "arkham_transfers": "arkham_transfers",
    "arkham_counterparties": "arkham_counterparties",
    "nansen_labels": "nansen_labels",
    "nansen_smart_money": "nansen_smart_money",
    "address_labels": "wallet_labels",
    "deployer_history": "wallet_labels",
    "dev_reputation": "wallet_labels",
    "reputation_score": "wallet_labels",
    "kol_performance": "social_feed",
    # ─── Holder & Distribution ───
    "bubble_map": "bubble_map",
    "rugmaps_analysis": "rugmaps_analysis",
    "holder_analysis": "rugmaps_analysis",
    # ─── Social & Sentiment ───
    "social_feed": "social_feed",
    "social_sentiment": "social_feed",
    "social_signal": "social_feed",
    "sentiment": "social_feed",
    "sentiment_spike": "social_feed",
    "sentiment_check": "social_feed",
    "narrative": "social_feed",
    "reddit_sentiment": "social_feed",
    "discord_alpha": "social_feed",
    "influencer_impact_score": "social_feed",
    "twitter_profile": "social_feed",
    "twitter_timeline": "social_feed",
    "twitter_search": "social_feed",
    "tw_profile": "social_feed",
    "tw_timeline": "social_feed",
    "tw_search": "social_feed",
    "telegram_pump_detect": "social_feed",
    "meme_vibe_score": "social_feed",
    # ─── News & Information ───
    "news": "news",
    "alpha_digest": "news",
    "github_developer_activity": "news",
    # ─── Prediction Markets ───
    "prediction_markets": "prediction_markets",
    "prediction_signals": "prediction_signals",
    "listing_predictor": "prediction_markets",
    # ─── Social Identity ───
    "socialfi_resolve": "socialfi_resolve",
    # ─── Launch & New Tokens ───
    "sniper_alert": "trending",
    "launch_radar": "trending",
    "launch_intel": "trending",
    "launch": "trending",
    "fair_launch_detect": "trending",
    "presale_scanner": "trending",
    "ido_tracker": "defi_protocols",
    # ─── RAG ───
    "rag_search": "rag_search",
    "osint_identity_hunt": "rag_search",
    "investigation_report": "rag_search",
    "forensic_valuation": "rag_search",
    # ─── Gas ───
    "gas_forecast": "market_overview",
    # ─── Syndicate & Clustering ───
    "syndicate_scan": "wallet_cluster",
    "syndicate_track": "wallet_cluster",
    "cluster": "wallet_cluster",
    "cluster_detection": "wallet_cluster",
    # ─── NFT ───
    "nft_wash_detector": "threat_check",
    "nft_floor_analytics": "dex_data",
    # ─── Anomaly & Analysis ───
    "anomaly": "market_movers",
    "anomaly_detector": "market_movers",
    "correlation_matrix": "market_overview",
    "drawdown_analyzer": "market_movers",
    "sharpe_ratio_calc": "market_overview",
    "tax_lot_optimizer": "portfolio",
    "forensics": "token_detail",
    "composite_score": "risk_scan",
    # ─── Token Watch (static/config endpoints need no fallback) ───
    # These return config/status, not data
    # "token_watch_create", "token_watch_list", etc. → no fallback needed
    # ─── Webhooks (config, not data) ───
    # "webhook_register", "webhook_list" → no fallback needed
    # ─── Airdrop ───
    "airdrop_check": "defi_protocols",
    "airdrop_finder": "defi_protocols",
    "vesting_schedule_analyzer": "defi_protocols",
    "unlock_calendar": "defi_protocols",
    # ─── Analysis ───
    "history": "token_price",
    "degen_score": "risk_scan",
    "profile_flip": "social_feed",
}

# Data types that accept 'address' parameter
ADDRESS_TYPES = {
    "risk_scan",
    "contract_scan",
    "wallet_profile",
    "wallet_pnl",
    "wallet_labels",
    "smart_money",
    "gmgn_smart_money",
    "funding_source",
    "cross_chain",
    "wallet_cluster",
    "bundle_detect",
    "sentinel_deep",
    "entity_intel",
    "arkham_labels",
    "arkham_entity",
    "arkham_portfolio",
    "arkham_transfers",
    "arkham_counterparties",
    "nansen_labels",
    "nansen_smart_money",
    "portfolio",
    "wallet_balance",
    "wallet_tokens",
    "threat_check",
    "socialfi_resolve",
    "bubble_map",
    "rugmaps_analysis",
    "token_detail",
    "dex_data",
}

# Data types that accept 'mint' parameter
MINT_TYPES = {"token_price", "token_detail", "dex_data", "risk_scan"}

# Data types that accept 'query' parameter
QUERY_TYPES = {
    "trending",
    "news",
    "social_feed",
    "market_movers",
    "prediction_markets",
    "prediction_signals",
    "rag_search",
    "smart_money",
    "gmgn_smart_money",
}


class DataBusFallbackMiddleware(BaseHTTPMiddleware):
    """
    Universal fallback middleware: if any x402-tools endpoint returns
    empty data, errors, or times out, transparently falls back to
    DataBus.fetch() which has 38 chains with automatic source failover
    and multi-layer caching.
    """

    async def dispatch(self, request: Request, call_next):
        response = await call_next(request)

        # Only intercept x402-tools paths (not x402-databus which already uses DataBus)
        path = request.url.path
        if not path.startswith("/api/v1/x402-tools/"):
            return response

        # Only intercept failed or empty responses
        if response.status_code >= 500:
            return await self._try_databus_fallback(request, response, "server_error")

        if response.status_code == 200:
            # Skip trial responses - DataBus was already used for execution
            if response.headers.get("X-RMI-Trial") == "true":
                return response

            try:
                body = b""
                async for chunk in response.body_iterator:
                    body += chunk
                import json

                data = json.loads(body)

                if self._is_empty_response(data):
                    return await self._try_databus_fallback(request, response, "empty_data", original_data=data)

                # Success - return as-is with body we already read
                return JSONResponse(content=data, status_code=200, headers=dict(response.headers))
            except Exception:
                # Can't parse body - return original response
                return response

        # 4xx errors (402 payment required, 400 bad request) pass through
        return response

    def _is_empty_response(self, data: Any) -> bool:
        """Check if response has meaningful data."""
        if data is None:
            return True
        if isinstance(data, dict):
            if data.get("error") and not data.get("data"):
                return True
            if data.get("status") in ("error", "failed", "no_data", "unavailable"):
                return True
            result = data.get("result") or data.get("data") or data.get("analysis") or data.get("report")
            if result is None and len(data) <= 3:
                return True
        return False

    async def _try_databus_fallback(
        self,
        request: Request,
        original_response: Response,
        reason: str,
        original_data: dict | None = None,
    ) -> Response:
        """Attempt DataBus fallback for the tool."""
        # Extract tool_id from path
        tool_id = request.url.path.replace("/api/v1/x402-tools/", "").strip("/")

        # Strip trailing path segments (e.g., /api/v1/x402-tools/rug_shield/solana)
        tool_id = tool_id.split("/")[0] if "/" in tool_id else tool_id

        data_type = TOOL_TO_DATABUS.get(tool_id)
        if not data_type:
            # No DataBus mapping - return original response
            return original_response

        try:
            from app.databus import databus

            kwargs = {"consumer_type": "x402_paid", "x402_tier": "basic"}

            # Extract parameters from request body
            try:
                body_bytes = await request.body()
                if body_bytes:
                    import json

                    body_data = json.loads(body_bytes)
                else:
                    body_data = {}
            except Exception:
                body_data = {}

            # Map request params to DataBus kwargs
            address = body_data.get("address") or body_data.get("wallet")
            if address and data_type in ADDRESS_TYPES:
                kwargs["address"] = address

            mint = body_data.get("mint") or body_data.get("token") or (address if data_type in MINT_TYPES else None)
            if mint and data_type in MINT_TYPES:
                kwargs["mint"] = mint

            chain = body_data.get("chain", "solana")
            kwargs["chain"] = chain

            query = body_data.get("query") or body_data.get("q")
            if query and data_type in QUERY_TYPES:
                kwargs["query"] = query

            # Also check query params
            qp = dict(request.query_params)
            if "address" in qp and data_type in ADDRESS_TYPES:
                kwargs["address"] = qp["address"]
            if "mint" in qp and data_type in MINT_TYPES:
                kwargs["mint"] = qp["mint"]
            if "chain" in qp:
                kwargs["chain"] = qp["chain"]
            if "query" in qp and data_type in QUERY_TYPES:
                kwargs["query"] = qp["query"]

            result = await databus.fetch(data_type, **kwargs)

            if result and not (isinstance(result, dict) and result.get("error") and not result.get("data")):
                logger.info(f"DataBus fallback: {tool_id} → {data_type} (reason: {reason})")
                if isinstance(result, dict):
                    result["_fallback"] = True
                    result["_databus_chain"] = data_type
                    result["_original_error"] = reason
                return JSONResponse(content=result, status_code=200)

        except Exception as e:
            logger.warning(f"DataBus fallback failed for {tool_id} → {data_type}: {e}")

        # DataBus also failed - return original error
        return original_response
