"""Wallet and trade flow analytics for Polymarket.

Public Data API wrappers for fetching trades, positions, holders,
and wallet activity - no authentication required.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import Any

import requests

from horizon.constants import DATA_API_URL as _DATA_API_URL, GAMMA_API_URL as _GAMMA_API_URL

logger = logging.getLogger("horizon.flow")

_TIMEOUT = 15


# ---------------------------------------------------------------------------
# Data classes
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class Trade:
    """A single trade on Polymarket."""

    wallet: str
    side: str  # "BUY" or "SELL"
    outcome: str  # "Yes" or "No"
    size: float  # token amount
    price: float
    usdc_size: float  # size * price
    timestamp: int
    market_slug: str
    market_title: str
    condition_id: str
    token_id: str
    tx_hash: str | None = None
    pseudonym: str | None = None


@dataclass(frozen=True)
class WalletPosition:
    """An open position held by a wallet."""

    wallet: str
    market_slug: str
    market_title: str
    condition_id: str
    token_id: str
    outcome: str
    size: float
    avg_price: float
    current_price: float
    current_value: float
    pnl: float
    pnl_percent: float


@dataclass(frozen=True)
class Holder:
    """A top holder in a market."""

    wallet: str
    amount: float
    token_id: str
    outcome_index: int
    pseudonym: str | None = None
    name: str | None = None


@dataclass(frozen=True)
class WalletProfile:
    """Public profile for a Polymarket wallet."""

    wallet: str
    pseudonym: str | None = None
    name: str | None = None
    bio: str | None = None
    profile_image: str | None = None
    x_username: str | None = None
    created_at: str | None = None


@dataclass
class MarketFlow:
    """Aggregated flow analytics for a market."""

    condition_id: str
    total_trades: int
    buy_volume: float
    sell_volume: float
    net_flow: float  # buy - sell
    unique_wallets: int
    top_buyers: list[tuple[str, float]] = field(default_factory=list)
    top_sellers: list[tuple[str, float]] = field(default_factory=list)


# ---------------------------------------------------------------------------
# Trade fetching
# ---------------------------------------------------------------------------


def get_market_trades(
    condition_id: str,
    limit: int = 100,
    offset: int = 0,
    side: str | None = None,
    min_size: float = 0.0,
    data_api_url: str = _DATA_API_URL,
) -> list[Trade]:
    """Fetch recent trades for a market.

    Args:
        condition_id: Market condition ID (0x-prefixed hex).
        limit: Max trades to return (max 10000).
        offset: Pagination offset.
        side: Filter by "BUY" or "SELL" (None = both).
        min_size: Minimum trade size in USDC.
        data_api_url: Override Data API URL.

    Returns:
        List of Trade objects, newest first.
    """
    params: dict[str, Any] = {
        "market": condition_id,
        "limit": min(limit, 10000),
        "offset": offset,
        "takerOnly": "false",
    }
    if side:
        params["side"] = side.upper()
    if min_size > 0:
        params["filterType"] = "CASH"
        params["filterAmount"] = min_size

    try:
        resp = requests.get(f"{data_api_url}/trades", params=params, timeout=_TIMEOUT)
        resp.raise_for_status()
        data = resp.json()
    except (requests.RequestException, ValueError) as e:
        logger.error("Failed to fetch trades: %s", e)
        return []

    if not isinstance(data, list):
        return []

    trades = []
    for item in data:
        price = float(item.get("price", 0) or 0)
        size = float(item.get("size", 0) or 0)
        trades.append(Trade(
            wallet=str(item.get("proxyWallet", "")),
            side=str(item.get("side", "")),
            outcome=str(item.get("outcome", "")),
            size=size,
            price=price,
            usdc_size=size * price,
            timestamp=int(item.get("timestamp", 0) or 0),
            market_slug=str(item.get("slug", "")),
            market_title=str(item.get("title", "")),
            condition_id=str(item.get("conditionId", condition_id)),
            token_id=str(item.get("asset", "")),
            tx_hash=item.get("transactionHash"),
            pseudonym=item.get("pseudonym"),
        ))

    return trades


def get_wallet_trades(
    address: str,
    limit: int = 100,
    offset: int = 0,
    condition_id: str | None = None,
    data_api_url: str = _DATA_API_URL,
) -> list[Trade]:
    """Fetch trade history for a wallet.

    Args:
        address: Wallet address (0x...).
        limit: Max trades to return.
        offset: Pagination offset.
        condition_id: Optional market filter.
        data_api_url: Override Data API URL.

    Returns:
        List of Trade objects for this wallet.
    """
    params: dict[str, Any] = {
        "user": address,
        "limit": min(limit, 10000),
        "offset": offset,
    }
    if condition_id:
        params["market"] = condition_id

    try:
        resp = requests.get(f"{data_api_url}/trades", params=params, timeout=_TIMEOUT)
        resp.raise_for_status()
        data = resp.json()
    except (requests.RequestException, ValueError) as e:
        logger.error("Failed to fetch wallet trades: %s", e)
        return []

    if not isinstance(data, list):
        return []

    trades = []
    for item in data:
        price = float(item.get("price", 0) or 0)
        size = float(item.get("size", 0) or 0)
        trades.append(Trade(
            wallet=str(item.get("proxyWallet", address)),
            side=str(item.get("side", "")),
            outcome=str(item.get("outcome", "")),
            size=size,
            price=price,
            usdc_size=size * price,
            timestamp=int(item.get("timestamp", 0) or 0),
            market_slug=str(item.get("slug", "")),
            market_title=str(item.get("title", "")),
            condition_id=str(item.get("conditionId", "")),
            token_id=str(item.get("asset", "")),
            tx_hash=item.get("transactionHash"),
            pseudonym=item.get("pseudonym"),
        ))

    return trades


# ---------------------------------------------------------------------------
# Positions
# ---------------------------------------------------------------------------


def get_wallet_positions(
    address: str,
    limit: int = 100,
    offset: int = 0,
    condition_id: str | None = None,
    sort_by: str = "TOKENS",
    data_api_url: str = _DATA_API_URL,
) -> list[WalletPosition]:
    """Fetch open positions for a wallet.

    Args:
        address: Wallet address (0x...).
        limit: Max positions to return (max 500).
        offset: Pagination offset.
        condition_id: Optional market filter.
        sort_by: Sort field - "TOKENS", "CURRENT", "INITIAL", "CASHPNL",
                 "PERCENTPNL", "PRICE", "AVGPRICE".
        data_api_url: Override Data API URL.

    Returns:
        List of WalletPosition objects.
    """
    params: dict[str, Any] = {
        "user": address,
        "limit": min(limit, 500),
        "offset": offset,
        "sortBy": sort_by,
        "sortDirection": "DESC",
        "sizeThreshold": 0.01,
    }
    if condition_id:
        params["market"] = condition_id

    try:
        resp = requests.get(
            f"{data_api_url}/positions", params=params, timeout=_TIMEOUT,
        )
        resp.raise_for_status()
        data = resp.json()
    except (requests.RequestException, ValueError) as e:
        logger.error("Failed to fetch wallet positions: %s", e)
        return []

    if not isinstance(data, list):
        return []

    positions = []
    for item in data:
        positions.append(WalletPosition(
            wallet=str(item.get("proxyWallet", address)),
            market_slug=str(item.get("slug", "")),
            market_title=str(item.get("title", "")),
            condition_id=str(item.get("conditionId", "")),
            token_id=str(item.get("asset", "")),
            outcome=str(item.get("outcome", "")),
            size=float(item.get("size", 0) or 0),
            avg_price=float(item.get("avgPrice", 0) or 0),
            current_price=float(item.get("curPrice", 0) or 0),
            current_value=float(item.get("currentValue", 0) or 0),
            pnl=float(item.get("cashPnl", 0) or 0),
            pnl_percent=float(item.get("percentPnl", 0) or 0),
        ))

    return positions


def get_wallet_value(
    address: str,
    data_api_url: str = _DATA_API_URL,
) -> float:
    """Get total USD value of a wallet's open positions.

    Args:
        address: Wallet address (0x...).
        data_api_url: Override Data API URL.

    Returns:
        Total position value in USD. Returns 0.0 on error.
    """
    try:
        resp = requests.get(
            f"{data_api_url}/value",
            params={"user": address},
            timeout=_TIMEOUT,
        )
        resp.raise_for_status()
        data = resp.json()
    except (requests.RequestException, ValueError) as e:
        logger.error("Failed to fetch wallet value: %s", e)
        return 0.0

    if isinstance(data, list) and len(data) > 0:
        return float(data[0].get("value", 0) or 0)
    return 0.0


# ---------------------------------------------------------------------------
# Holders
# ---------------------------------------------------------------------------


def get_top_holders(
    condition_id: str,
    limit: int = 20,
    data_api_url: str = _DATA_API_URL,
) -> list[Holder]:
    """Get top holders for a market.

    Args:
        condition_id: Market condition ID.
        limit: Max holders (max 20).
        data_api_url: Override Data API URL.

    Returns:
        List of Holder objects sorted by position size.
    """
    try:
        resp = requests.get(
            f"{data_api_url}/holders",
            params={"market": condition_id, "limit": min(limit, 20)},
            timeout=_TIMEOUT,
        )
        resp.raise_for_status()
        data = resp.json()
    except (requests.RequestException, ValueError) as e:
        logger.error("Failed to fetch holders: %s", e)
        return []

    if not isinstance(data, list):
        return []

    holders = []
    for token_group in data:
        token_id = str(token_group.get("token", ""))
        for h in token_group.get("holders", []):
            holders.append(Holder(
                wallet=str(h.get("proxyWallet", "")),
                amount=float(h.get("amount", 0) or 0),
                token_id=token_id,
                outcome_index=int(h.get("outcomeIndex", 0) or 0),
                pseudonym=h.get("pseudonym"),
                name=h.get("name"),
            ))

    holders.sort(key=lambda x: x.amount, reverse=True)
    return holders[:limit]


# ---------------------------------------------------------------------------
# Profile
# ---------------------------------------------------------------------------


def get_wallet_profile(
    address: str,
    gamma_url: str = _GAMMA_API_URL,
) -> WalletProfile | None:
    """Fetch public profile for a wallet.

    Args:
        address: Wallet address (0x...).
        gamma_url: Override Gamma API URL.

    Returns:
        WalletProfile or None if not found.
    """
    try:
        resp = requests.get(
            f"{gamma_url}/public-profile",
            params={"address": address},
            timeout=_TIMEOUT,
        )
        resp.raise_for_status()
        data = resp.json()
    except (requests.RequestException, ValueError) as e:
        logger.error("Failed to fetch profile: %s", e)
        return None

    if not data or not isinstance(data, dict):
        return None

    return WalletProfile(
        wallet=str(data.get("proxyWallet", address)),
        pseudonym=data.get("pseudonym"),
        name=data.get("name"),
        bio=data.get("bio"),
        profile_image=data.get("profileImage"),
        x_username=data.get("xUsername"),
        created_at=data.get("createdAt"),
    )


# ---------------------------------------------------------------------------
# Aggregated flow analysis
# ---------------------------------------------------------------------------


def analyze_market_flow(
    condition_id: str,
    trade_limit: int = 500,
    top_n: int = 10,
    data_api_url: str = _DATA_API_URL,
) -> MarketFlow:
    """Analyze trade flow for a market.

    Fetches recent trades and computes buy/sell volume,
    net flow, unique wallets, and top buyers/sellers.

    Args:
        condition_id: Market condition ID.
        trade_limit: Number of recent trades to analyze (max 10000).
        top_n: Number of top buyers/sellers to return.
        data_api_url: Override Data API URL.

    Returns:
        MarketFlow with aggregated analytics.
    """
    trades = get_market_trades(
        condition_id, limit=trade_limit, data_api_url=data_api_url,
    )

    buy_volume = 0.0
    sell_volume = 0.0
    wallets: set[str] = set()
    buyer_volumes: dict[str, float] = {}
    seller_volumes: dict[str, float] = {}

    for t in trades:
        wallets.add(t.wallet)
        if t.side == "BUY":
            buy_volume += t.usdc_size
            buyer_volumes[t.wallet] = buyer_volumes.get(t.wallet, 0) + t.usdc_size
        elif t.side == "SELL":
            sell_volume += t.usdc_size
            seller_volumes[t.wallet] = seller_volumes.get(t.wallet, 0) + t.usdc_size

    top_buyers = sorted(buyer_volumes.items(), key=lambda x: x[1], reverse=True)[:top_n]
    top_sellers = sorted(seller_volumes.items(), key=lambda x: x[1], reverse=True)[:top_n]

    return MarketFlow(
        condition_id=condition_id,
        total_trades=len(trades),
        buy_volume=buy_volume,
        sell_volume=sell_volume,
        net_flow=buy_volume - sell_volume,
        unique_wallets=len(wallets),
        top_buyers=top_buyers,
        top_sellers=top_sellers,
    )
