"""
RMI x402 Analytics Dashboard Endpoint
=======================================
GET /api/v1/x402/dashboard - Public analytics endpoint returning:
  - Total earnings, earnings by chain, earnings by tool, earnings by day
  - Trial usage counts (today, all-time, unique fingerprints, unique wallets)
  - Top tools by revenue, top tools by usage
  - Paid from Redis x402:spent_tx:*, x402:trial:*, x402:global:trials_today, etc.
  - Persisted via Supabase x402_payments table

Author: RMI Development
Date: 2026-05-20
"""

import asyncio
import contextlib
import json
import logging
import os
import time
from collections import defaultdict
from datetime import UTC, datetime
from typing import Any

from fastapi import APIRouter, HTTPException

logger = logging.getLogger("x402_dashboard")

router = APIRouter(prefix="/api/v1/x402", tags=["x402 Analytics Dashboard"])

# ── Redis helper (reuse existing pattern from enforcement) ─────────────


def _get_redis():
    """Synchronous Redis client for dashboard queries."""
    try:
        import redis

        r = redis.Redis(
            host=os.getenv("REDIS_HOST", "localhost"),
            port=int(os.getenv("REDIS_PORT", "6379")),
            password=os.getenv("REDIS_PASSWORD", ""),
            db=0,
            decode_responses=True,
        )
        r.ping()
        return r
    except Exception as e:
        logger.warning(f"Dashboard: Redis unavailable: {e}")
        return None


async def _get_redis_async():
    """Async Redis client for dashboard queries."""
    try:
        from app.auth import get_redis

        r = await get_redis()
        return r
    except Exception:
        return _get_redis()


# ── Supabase payment persistence ────────────────────────────────────


async def _persist_payment_to_supabase(
    tool: str, amount_atoms: str, chain: str, payer: str, tx_hash: str, status: str = "fulfilled"
):
    """Insert a payment record into Supabase x402_payments table."""
    try:
        from app.services.supabase_service import _post

        await _post(
            "x402_payments",
            {
                "tool": tool,
                "amount_atoms": str(amount_atoms),
                "chain": chain,
                "payer": payer or "unknown",
                "tx_hash": tx_hash or "",
                "status": status,
                "created_at": datetime.now(UTC).isoformat(),
            },
        )
        logger.info(
            f"Persisted x402 payment to Supabase: tool={tool} chain={chain} tx={tx_hash[:16] if tx_hash else 'N/A'}"
        )
    except Exception as e:
        logger.warning(f"Supabase payment persist failed: {e}")


async def _sync_spent_tx_to_supabase():
    """Sync Redis x402:spent_tx:* entries to Supabase (run periodically).

    Reads all x402:spent_tx:* keys from Redis and upserts them into x402_payments.
    Uses tx_hash as dedup key to avoid duplicates.
    """
    r = _get_redis()
    if not r:
        return 0

    synced = 0
    try:
        from app.services.supabase_service import _get, _post

        # Get existing tx_hashes from Supabase to avoid duplicates
        existing = await _get("x402_payments", select="tx_hash", limit=10000)
        existing_txs = {row["tx_hash"] for row in existing if row.get("tx_hash")}

        # Scan Redis for spent_tx entries
        cursor = 0
        while True:
            cursor, keys = r.scan(cursor, match="x402:spent_tx:*", count=200)
            for key in keys:
                tx_hash = key.replace("x402:spent_tx:", "")
                if tx_hash in existing_txs:
                    continue
                try:
                    data = json.loads(r.get(key))
                    tool = data.get("tool", "unknown")
                    amount_atoms = data.get("amount", "0")
                    chain = data.get("chain", "unknown")
                    payer = data.get("payer", "unknown")
                    await _post(
                        "x402_payments",
                        {
                            "tool": tool,
                            "amount_atoms": str(amount_atoms),
                            "chain": chain,
                            "payer": payer,
                            "tx_hash": tx_hash,
                            "status": "fulfilled",
                            "created_at": datetime.fromtimestamp(data.get("timestamp", time.time()), tz=UTC).isoformat()
                            if data.get("timestamp")
                            else datetime.now(UTC).isoformat(),
                        },
                    )
                    existing_txs.add(tx_hash)
                    synced += 1
                except Exception as e:
                    logger.debug(f"Sync failed for {key}: {e}")
            if cursor == 0:
                break
        if synced:
            logger.info(f"Synced {synced} x402 payments from Redis to Supabase")
    except Exception as e:
        logger.warning(f"Redis->Supabase sync error: {e}")
    return synced


# ── Dashboard data aggregation from Redis ────────────────────────────


def _aggregate_payment_stats(r) -> dict[str, Any]:
    """Aggregate payment/earnings data from Redis x402:spent_tx:* keys."""
    total_earnings_atoms = 0
    earnings_by_chain: dict[str, int] = defaultdict(int)
    defaultdict(int)
    tool_usage: dict[str, int] = defaultdict(int)
    tool_revenue: dict[str, int] = defaultdict(int)
    unique_payers = set()
    all_payments = []  # For daily breakdown

    cursor = 0
    while True:
        cursor, keys = r.scan(cursor, match="x402:spent_tx:*", count=500)
        for key in keys:
            try:
                data = json.loads(r.get(key))
                amount = int(data.get("amount", "0") or "0")
                chain = data.get("chain", "unknown")
                payer = data.get("payer", "unknown")
                tool = data.get("tool", "unknown")
                timestamp = data.get("timestamp", 0)

                total_earnings_atoms += amount
                earnings_by_chain[chain] += amount
                if tool and tool != "unknown":
                    tool_usage[tool] += 1
                    tool_revenue[tool] += amount
                if payer and payer != "unknown":
                    unique_payers.add(payer)

                all_payments.append(
                    {
                        "amount": amount,
                        "chain": chain,
                        "tool": tool,
                        "payer": payer,
                        "timestamp": timestamp,
                    }
                )
            except Exception:
                continue
        if cursor == 0:
            break

    # Also check x402-tool:* keys (from old record_x402_payment) - tool stats only, no double-count
    cursor = 0
    while True:
        cursor, keys = r.scan(cursor, match="x402-tool:*", count=500)
        for key in keys:
            try:
                data = json.loads(r.get(key))
                tool = data.get("tool", "unknown")
                amount_str = data.get("amount", "0")
                # Amount might be in USD format (e.g., "0.05") or atoms
                try:
                    amt = float(amount_str)
                    # Convert USD to atoms if < 1 (heuristic: USD prices are < 1)
                    amt_atoms = int(amt * 1000000.0) if amt < 1 else int(amt)
                except (ValueError, TypeError):
                    amt_atoms = 0

                tool_usage[tool] += 1
                tool_revenue[tool] += amt_atoms
                # Don't add to total_earnings_atoms - already counted from spent_tx

                customer = data.get("customer", "")
                if customer:
                    unique_payers.add(customer)
                chain = data.get("chain", "unknown")
                # x402-tool entries may not have chain info
                if chain != "unknown":
                    earnings_by_chain[chain] += amt_atoms
            except Exception:
                continue
        if cursor == 0:
            break

    # Also check x402:receipt:* keys (from x402_middleware.py)
    cursor = 0
    while True:
        cursor, keys = r.scan(cursor, match="x402:receipt:*", count=500)
        for key in keys:
            try:
                data = json.loads(r.get(key))
                tool = data.get("tool", "unknown")
                amount_str = str(data.get("amount", "0"))
                chain = data.get("network", data.get("chain", "unknown"))
                customer = data.get("customer_address", data.get("customer", ""))

                try:
                    amt = float(amount_str)
                    amt_atoms = int(amt * 1000000.0) if amt < 1 else int(amt)
                except (ValueError, TypeError):
                    amt_atoms = 0

                tool_usage[tool] += 1
                # Don't double-count totals from receipt keys - spent_tx is canonical
                tool_revenue[tool] += amt_atoms
                if chain:
                    pass  # Don't double-count chain earnings
                if customer:
                    unique_payers.add(customer)
            except Exception:
                continue
        if cursor == 0:
            break

    # Earnings by day (from all_payments timestamps)
    time.time()
    earnings_by_day: dict[str, int] = defaultdict(int)
    for p in all_payments:
        if p.get("timestamp"):
            try:
                day = datetime.fromtimestamp(p["timestamp"], tz=UTC).strftime("%Y-%m-%d")
                earnings_by_day[day] += p.get("amount", 0)
            except Exception:
                pass

    # Today's earnings
    today_str = datetime.now(UTC).strftime("%Y-%m-%d")
    today_earnings = earnings_by_day.get(today_str, 0)

    # Convert atoms to USDC (6 decimals)
    total_earnings_usdc = total_earnings_atoms / 1e6
    today_earnings_usdc = today_earnings / 1e6

    earnings_by_chain_usdc = {k: v / 1e6 for k, v in earnings_by_chain.items()}
    earnings_by_day_usdc = {k: v / 1e6 for k, v in sorted(earnings_by_day.items())[-30:]}  # Last 30 days
    tool_revenue_usdc = {k: v / 1e6 for k, v in tool_revenue.items()}

    # Top tools by revenue
    top_tools_revenue = sorted(tool_revenue_usdc.items(), key=lambda x: -x[1])[:10]
    # Top tools by usage count
    top_tools_usage = sorted(tool_usage.items(), key=lambda x: -x[1])[:10]

    return {
        "total_earnings_usdc": round(total_earnings_usdc, 6),
        "today_earnings_usdc": round(today_earnings_usdc, 6),
        "earnings_by_chain": earnings_by_chain_usdc,
        "earnings_by_day": earnings_by_day_usdc,
        "unique_payers": len(unique_payers),
        "top_tools_by_revenue": [{"tool": t, "revenue_usdc": round(r, 6)} for t, r in top_tools_revenue],
        "top_tools_by_usage": [{"tool": t, "calls": c} for t, c in top_tools_usage],
    }


def _aggregate_trial_stats(r) -> dict[str, Any]:
    """Aggregate trial usage stats from Redis x402:trial:* keys."""
    total_trials_all_time = 0
    unique_fingerprints = set()
    unique_wallets = set()
    trials_by_tool: dict[str, int] = defaultdict(int)

    cursor = 0
    while True:
        cursor, keys = r.scan(cursor, match="x402:trial:*", count=500)
        for key in keys:
            # Key format: x402:trial:{client_id}:{tool_id}
            try:
                parts = key.replace("x402:trial:", "").split(":", 1)
                client_id = parts[0] if parts else "unknown"
                tool_id = parts[1] if len(parts) > 1 else "unknown"

                val = int(r.get(key) or 0)
                total_trials_all_time += val
                trials_by_tool[tool_id] += val

                # Categorize identity
                if client_id.startswith("w:"):
                    unique_wallets.add(client_id)
                elif client_id.startswith("dev:") or client_id.startswith("ts:"):
                    unique_fingerprints.add(client_id)
                else:
                    unique_fingerprints.add(client_id)
            except Exception:
                continue
        if cursor == 0:
            break

    # Today's trials
    trials_today = 0
    with contextlib.suppress(Exception):
        trials_today = int(r.get("x402:global:trials_today") or 0)

    # IP daily stats
    cursor = 0
    ip_daily_total = 0
    while True:
        cursor, keys = r.scan(cursor, match="x402:ip_daily:*", count=500)
        for key in keys:
            with contextlib.suppress(Exception):
                ip_daily_total += int(r.get(key) or 0)
        if cursor == 0:
            break

    return {
        "trials_today": trials_today,
        "trials_all_time": total_trials_all_time,
        "unique_fingerprint_ids": len(unique_fingerprints),
        "unique_wallet_ids": len(unique_wallets),
        "trials_by_tool": dict(sorted(trials_by_tool.items(), key=lambda x: -x[1])[:20]),
        "ip_daily_free_calls": ip_daily_total,
    }


# ── Dashboard endpoint ──────────────────────────────────────────────


@router.get("/dashboard")
async def x402_dashboard():
    """
    Public analytics dashboard for x402 payment and trial stats.

    Returns:
    - Total earnings (USDC), today's earnings
    - Earnings breakdown by chain, tool, day
    - Trial stats: today, all-time, unique fingerprints/wallets
    - Top tools by revenue and usage count
    - System health metadata
    """
    try:
        r = _get_redis()

        if r:
            payment_stats = _aggregate_payment_stats(r)
            trial_stats = _aggregate_trial_stats(r)
        else:
            payment_stats = {
                "total_earnings_usdc": 0,
                "today_earnings_usdc": 0,
                "earnings_by_chain": {},
                "earnings_by_day": {},
                "unique_payers": 0,
                "top_tools_by_revenue": [],
                "top_tools_by_usage": [],
            }
            trial_stats = {
                "trials_today": 0,
                "trials_all_time": 0,
                "unique_fingerprint_ids": 0,
                "unique_wallet_ids": 0,
                "trials_by_tool": {},
                "ip_daily_free_calls": 0,
            }

        # Try to get Supabase data for enrichment
        supabase_count = 0
        supabase_total = 0.0
        supabase_summary = {}
        try:
            from app.services.supabase_service import _get, get_x402_earnings_summary

            rows = await _get("x402_payments", select="id,amount_atoms,chain,tool,status", limit=5000)
            supabase_count = len(rows)
            for row in rows:
                with contextlib.suppress(ValueError, TypeError):
                    supabase_total += int(row.get("amount_atoms", "0") or "0") / 1e6
            # Get full Supabase earnings summary
            supabase_summary = await get_x402_earnings_summary()
        except Exception as e:
            logger.debug(f"Supabase dashboard enrichment failed: {e}")

        # Trigger background sync (non-blocking)
        with contextlib.suppress(Exception):
            asyncio.create_task(_sync_spent_tx_to_supabase())

        return {
            "earnings": payment_stats,
            "trials": trial_stats,
            "supabase": {
                "persisted_count": supabase_count,
                "total_usdc": round(supabase_total, 6),
                "earnings_summary": supabase_summary,
            },
            "meta": {
                "timestamp": datetime.now(UTC).isoformat(),
                "redis_available": r is not None,
                "chains_supported": [
                    "base",
                    "solana",
                    "ethereum",
                    "bsc",
                    "arbitrum",
                    "optimism",
                    "polygon",
                ],
            },
        }

    except Exception as e:
        logger.error(f"Dashboard error: {e}")
        raise HTTPException(status_code=500, detail=f"Dashboard error: {str(e)[:200]}") from e


# ── Supabase table creation helper ──────────────────────────────────


async def ensure_x402_payments_table():
    """Ensure x402_payments table exists in Supabase.

    Delegates to supabase_service.ensure_x402_payments_table()
    which uses the working exec_sql RPC function.
    """
    try:
        from app.services.supabase_service import ensure_x402_payments_table as _ensure

        result = await _ensure()
        if result:
            logger.info("x402_payments table ensured in Supabase")
        else:
            logger.warning("x402_payments table creation may have failed")
        return result
    except ImportError:
        # Fallback: direct call via REST
        try:
            from app.services.supabase_service import _get_key, _get_url

            SK = _get_key()
            SU = _get_url()
            sql = """
            DO $$ BEGIN
                CREATE TABLE IF NOT EXISTS x402_payments (
                    id BIGSERIAL PRIMARY KEY,
                    tool TEXT NOT NULL DEFAULT 'unknown',
                    amount_atoms TEXT NOT NULL DEFAULT '0',
                    chain TEXT NOT NULL DEFAULT 'unknown',
                    payer TEXT NOT NULL DEFAULT 'unknown',
                    tx_hash TEXT NOT NULL DEFAULT '',
                    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
                    status TEXT NOT NULL DEFAULT 'fulfilled'
                );
            END $$;
            CREATE INDEX IF NOT EXISTS idx_x402_payments_tool ON x402_payments(tool);
            CREATE INDEX IF NOT EXISTS idx_x402_payments_chain ON x402_payments(chain);
            CREATE INDEX IF NOT EXISTS idx_x402_payments_tx_hash ON x402_payments(tx_hash);
            CREATE INDEX IF NOT EXISTS idx_x402_payments_created_at ON x402_payments(created_at);
            CREATE INDEX IF NOT EXISTS idx_x402_payments_payer ON x402_payments(payer);
            -- RLS: service_role only
            ALTER TABLE x402_payments ENABLE ROW LEVEL SECURITY;
            DO $$ BEGIN
                DROP POLICY IF EXISTS "Service role full access" ON x402_payments;
            EXCEPTION WHEN OTHERS THEN NULL; END $$;
            CREATE POLICY "Service role full access" ON x402_payments FOR ALL TO service_role USING (true) WITH CHECK (true);
            """
            import httpx

            headers = {
                "apikey": SK,
                "Authorization": f"Bearer {SK}",
                "Content-Type": "application/json",
            }
            async with httpx.AsyncClient(timeout=15) as c:
                r = await c.post(
                    f"{SU}/rest/v1/rpc/exec_sql",
                    json={"sql": sql},
                    headers=headers,
                )
                logger.info(f"x402_payments table creation via exec_sql: {r.status_code}")
                return r.status_code in (200, 204)
        except Exception as e:
            logger.warning(f"Could not ensure x402_payments table (fallback): {e}")
            return False
    except Exception as e:
        logger.warning(f"Could not ensure x402_payments table: {e}")
        return False


# ── Register on app startup ─────────────────────────────────────────


async def on_startup():
    """Call this on app startup to ensure the table exists."""
    await ensure_x402_payments_table()
