"""Polymarket prediction market data provider.

Free public API for prediction market events, markets, and probability data.
No API key required for data access.

Docs: https://docs.polymarket.com/
"""

from __future__ import annotations

from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional

import requests

from ..models import Bar, Quote, TimeFrame

GAMMA_BASE = "https://gamma-api.polymarket.com"
CLOB_BASE = "https://clob.polymarket.com"


class PolymarketProvider:
    """Polymarket prediction market data provider.

    Free, no API key required. Provides event probabilities, market data,
    and price history for prediction markets.

    Usage:
        provider = PolymarketProvider()
        events = provider.get_events(active=True, limit=20)
        markets = provider.get_markets(tag="politics")
        bars = provider.get_bars("market-slug-or-condition-id")
    """

    name = "polymarket"
    supported_assets = ["prediction_markets"]

    def __init__(self, timeout: int = 15) -> None:
        self._timeout = timeout
        self._session = requests.Session()

    def _gamma(self, path: str, params: Optional[Dict[str, str]] = None) -> Any:
        url = f"{GAMMA_BASE}{path}"
        resp = self._session.get(url, params=params, timeout=self._timeout)
        resp.raise_for_status()
        return resp.json()

    def _clob(self, path: str, params: Optional[Dict[str, str]] = None) -> Any:
        url = f"{CLOB_BASE}{path}"
        resp = self._session.get(url, params=params, timeout=self._timeout)
        resp.raise_for_status()
        return resp.json()

    def get_events(
        self,
        active: bool = True,
        limit: int = 50,
        tag: Optional[str] = None,
        order: str = "volume",
    ) -> List[Dict[str, Any]]:
        """Get prediction market events.

        Args:
            active: Only active (tradeable) events
            limit: Maximum events to return
            tag: Filter by tag slug (e.g., "politics", "crypto", "sports")
            order: Sort by field (volume, liquidity, competitive)

        Returns:
            List of event dicts with title, markets, and metadata
        """
        params: Dict[str, str] = {
            "active": str(active).lower(),
            "closed": "false",
            "limit": str(limit),
            "order": order,
            "ascending": "false",
        }
        if tag:
            params["tag_slug"] = tag
        return self._gamma("/events", params)

    def get_markets(
        self,
        event_id: Optional[str] = None,
        condition_id: Optional[str] = None,
        slug: Optional[str] = None,
        tag: Optional[str] = None,
        active: Optional[bool] = True,
        limit: int = 50,
    ) -> List[Dict[str, Any]]:
        """Get prediction markets.

        Args:
            event_id: Filter by event ID
            condition_id: Get specific market by condition ID
            slug: Get specific market by slug
            tag: Filter by tag slug
            active: Only active markets. None = no filter (includes resolved).
            limit: Maximum markets to return

        Returns:
            List of market dicts with question, probabilities, volume
        """
        # Gamma API ignores condition_id as a query param, so use CLOB API
        # for direct lookups and verify client-side
        if condition_id:
            try:
                data = self._clob(f"/markets/{condition_id}")
                if isinstance(data, dict) and data.get("condition_id"):
                    return [data]
            except Exception:
                pass
            # Fallback: fetch from Gamma and filter client-side
            params: Dict[str, str] = {"limit": "100", "order": "volume", "ascending": "false"}
            try:
                markets = self._gamma("/markets", params)
                if isinstance(markets, list):
                    cid = condition_id.lower()
                    matched = [m for m in markets if (m.get("conditionId") or "").lower() == cid]
                    return matched
            except Exception:
                pass
            return []

        params = {
            "limit": str(limit),
            "order": "volume",
            "ascending": "false",
        }
        if active is not None:
            params["active"] = str(active).lower()
            if active:
                params["closed"] = "false"
        if event_id:
            params["event_id"] = event_id
        if slug:
            params["slug"] = slug
        if tag:
            params["tag_slug"] = tag
        return self._gamma("/markets", params)

    def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
        """Search for markets and events.

        Args:
            query: Search query string
            limit: Maximum results

        Returns:
            List of matching markets/events
        """
        result = self._gamma("/public-search", {"q": query})
        if isinstance(result, dict):
            return result.get("events", result.get("markets", []))
        return result if isinstance(result, list) else []

    def get_market_price(self, condition_id: str) -> Optional[Dict[str, float]]:
        """Get current YES/NO prices for a market.

        Args:
            condition_id: Market condition ID or slug

        Returns:
            Dict with 'yes' and 'no' probabilities, or None
        """
        import json as _json

        try:
            markets = self.get_markets(condition_id=condition_id, limit=1, active=None)
        except Exception:
            return None
        if not markets:
            return None
        m = markets[0] if isinstance(markets, list) else markets
        try:
            prices = m.get("outcomePrices")
            if isinstance(prices, str):
                prices = _json.loads(prices)
            if prices and len(prices) >= 2:
                return {"yes": float(prices[0]), "no": float(prices[1])}
            # CLOB API format: tokens list with price field
            tokens = m.get("tokens")
            if isinstance(tokens, list) and len(tokens) >= 2:
                return {"yes": float(tokens[0].get("price", 0)), "no": float(tokens[1].get("price", 0))}
        except (ValueError, IndexError, TypeError):
            pass
        return None

    def _resolve_token_id(self, symbol: str) -> Optional[str]:
        """Resolve a symbol (slug, condition_id, or query) to a CLOB token ID."""
        import json as _json

        market = None
        if symbol.startswith("0x") or len(symbol) == 66:
            try:
                markets = self.get_markets(condition_id=symbol, limit=1, active=None)
                if markets:
                    market = markets[0] if isinstance(markets, list) else markets
            except Exception:
                pass
        if not market:
            try:
                markets = self.get_markets(slug=symbol, limit=1, active=None)
                if markets:
                    market = markets[0] if isinstance(markets, list) else markets
            except Exception:
                pass
        if not market:
            return None
        try:
            # CLOB API returns tokens as list of dicts with token_id field
            tokens = market.get("tokens")
            if isinstance(tokens, list) and tokens:
                return str(tokens[0].get("token_id", "")) or None
            # Gamma API returns clobTokenIds as JSON string
            token_ids = market.get("clobTokenIds") or market.get("clob_token_ids")
            if isinstance(token_ids, str):
                token_ids = _json.loads(token_ids)
            return str(token_ids[0]) if token_ids else None
        except (IndexError, TypeError, ValueError):
            return None

    def get_bars(
        self,
        symbol: str,
        timeframe: TimeFrame | str = TimeFrame.DAY,
        start: Optional[datetime] = None,
        end: Optional[datetime] = None,
        limit: Optional[int] = None,
    ) -> List[Bar]:
        """Get probability history as OHLCV-style bars.

        Since prediction markets have probabilities (0-1) instead of prices,
        the open/high/low/close fields contain probability values.

        Args:
            symbol: Market slug, condition_id, or token_id
            timeframe: Bar timeframe (affects data resolution)
            start: Start datetime
            end: End datetime
            limit: Maximum bars to return

        Returns:
            List of Bar objects with probability as price
        """
        if isinstance(timeframe, str):
            timeframe = TimeFrame.from_string(timeframe)

        fidelity_map = {
            TimeFrame.MINUTE_1: "60",
            TimeFrame.MINUTE_5: "300",
            TimeFrame.MINUTE_15: "900",
            TimeFrame.MINUTE_30: "1800",
            TimeFrame.HOUR_1: "3600",
            TimeFrame.HOUR_4: "14400",
            TimeFrame.DAY: "86400",
            TimeFrame.WEEK: "604800",
            TimeFrame.MONTH: "2592000",
        }
        fidelity = fidelity_map.get(timeframe, "3600")

        token_id = self._resolve_token_id(symbol)
        if not token_id:
            return []

        try:
            data = self._clob(
                "/prices-history",
                {"market": token_id, "interval": "all", "fidelity": fidelity},
            )
        except Exception:
            return []

        history = data.get("history", data) if isinstance(data, dict) else data
        if not isinstance(history, list):
            return []

        bars: List[Bar] = []
        for p in history:
            ts = p.get("t", 0)
            price = float(p.get("p", 0))
            dt = datetime.utcfromtimestamp(ts) if isinstance(ts, (int, float)) else datetime.utcnow()

            if start and dt < start:
                continue
            if end and dt > end:
                continue

            bars.append(
                Bar(
                    symbol=symbol,
                    timestamp=dt,
                    open=price,
                    high=price,
                    low=price,
                    close=price,
                    volume=0,
                )
            )

        if limit and len(bars) > limit:
            bars = bars[-limit:]
        return bars

    def get_quote(self, symbol: str) -> Optional[Quote]:
        """Get current probability as a quote.

        Args:
            symbol: Market condition_id or slug

        Returns:
            Quote with bid/ask as YES/NO probabilities
        """
        prices = self.get_market_price(symbol)
        if not prices:
            return None
        return Quote(
            symbol=symbol,
            timestamp=datetime.utcnow(),
            bid=prices["yes"],
            ask=prices["yes"],
            bid_size=0,
            ask_size=0,
        )

    def get_quotes(self, symbols: List[str]) -> List[Quote]:
        """Get current probabilities for multiple markets."""
        quotes: List[Quote] = []
        for sym in symbols:
            q = self.get_quote(sym)
            if q:
                quotes.append(q)
        return quotes

    def list_markets(
        self,
        limit: int = 20,
        tag: Optional[str] = None,
        active: bool = True,
        all_markets: bool = False,
    ) -> List[Dict[str, Any]]:
        """Get prediction markets as clean, flat dicts (parsed prices/volume).

        Unlike get_events() which returns raw API data with nested markets and
        JSON-encoded prices, this returns one row per market with parsed fields,
        sorted by volume descending.

        Args:
            limit: Maximum rows to return
            tag: Filter by tag slug (e.g., "politics", "crypto", "sports")
            active: Only active (tradeable) events
            all_markets: If False (default), returns only the top market per
                event. If True, returns every sub-market.

        Returns:
            List of dicts with keys: question, yes, no, volume, condition_id,
            slug, end_date, event_title
        """
        import json as _json

        events = self.get_events(active=active, limit=limit, tag=tag)
        rows: List[Dict[str, Any]] = []
        for ev in events:
            event_title = ev.get("title", "")
            markets = ev.get("markets") or []
            if not all_markets and len(markets) > 1:
                # Pick the highest-volume market per event
                markets = sorted(
                    markets,
                    key=lambda m: float(m.get("volume") or m.get("volumeNum") or 0),
                    reverse=True,
                )[:1]
            for m in markets:
                op = m.get("outcomePrices")
                if isinstance(op, str):
                    try:
                        op = _json.loads(op)
                    except Exception:
                        op = None
                yes = float(op[0]) if op and len(op) >= 1 else 0.0
                no = float(op[1]) if op and len(op) >= 2 else 0.0
                rows.append({
                    "question": m.get("question") or event_title,
                    "yes": round(yes, 4),
                    "no": round(no, 4),
                    "volume": round(float(m.get("volume") or m.get("volumeNum") or 0), 2),
                    "condition_id": m.get("conditionId", ""),
                    "slug": m.get("slug", ""),
                    "end_date": m.get("endDate", ""),
                    "event_title": event_title,
                })
        rows.sort(key=lambda r: r["volume"], reverse=True)
        return rows[:limit]
