"""SmaugLabs Python SDK — typed client for the historical Polymarket data API."""

from __future__ import annotations

import httpx
from typing import Any, Dict, List, Optional


class SmaugLabs:
    """Client for the SmaugLabs historical Polymarket data API.

    Usage::

        from smauglabs import SmaugLabs

        client = SmaugLabs(api_key="sl_live_...")
        events = client.list_events(limit=5)
        chart  = client.sovereign_chart("TOKEN_ID", granularity="5m")
        client.close()

    Or as a context manager::

        with SmaugLabs(api_key="sl_live_...") as client:
            events = client.list_events()
    """

    def __init__(
        self,
        api_key: str = "",
        base_url: str = "https://api.smauglabs.com",
        timeout: float = 30,
    ):
        self.base_url = base_url.rstrip("/")
        headers: Dict[str, str] = {}
        if api_key:
            headers["X-API-Key"] = api_key
        self._client = httpx.Client(
            base_url=self.base_url, headers=headers, timeout=timeout,
        )

    # ── transport helpers ──────────────────────────────────────

    def _get(self, path: str, params: Optional[dict] = None) -> Any:
        resp = self._client.get(path, params=_strip_none(params))
        resp.raise_for_status()
        return resp.json()

    def _post(self, path: str, params: Optional[dict] = None, json: Optional[dict] = None) -> Any:
        resp = self._client.post(path, params=_strip_none(params), json=json)
        resp.raise_for_status()
        return resp.json()

    def close(self) -> None:
        """Close the underlying HTTP connection pool."""
        self._client.close()

    def __enter__(self) -> "SmaugLabs":
        return self

    def __exit__(self, *exc: Any) -> None:
        self.close()

    # ── Market Discovery ───────────────────────────────────────

    def search_markets(self, query: str, limit: int = 10, active_only: bool = False) -> list:
        """Search Polymarket markets by keyword."""
        return self._get(
            "/v1/events/markets/search",
            {"q": query, "limit": limit, "active_only": active_only},
        )

    def list_events(self, limit: int = 20, offset: int = 0, active: Optional[bool] = None) -> list:
        """List Polymarket events with optional filters."""
        return self._get("/v1/events", {"limit": limit, "offset": offset, "active": active})

    def get_event(self, event_id: str) -> dict:
        """Get full event details including markets and token IDs."""
        return self._get(f"/v1/events/{event_id}")

    def trending_markets(self, limit: int = 10) -> list:
        """Top active markets ranked by volume."""
        return self._get("/v1/events/markets/trending", {"limit": limit})

    # ── Historical Orderbook ───────────────────────────────────

    def orderbook_snapshot(
        self,
        token_id: str,
        timestamp: Optional[str] = None,
        limit: int = 100,
    ) -> dict:
        """Orderbook snapshot at a point in time.

        ``timestamp`` should be an ISO-8601 UTC string, e.g. ``"2025-01-15T12:00:00"``.
        Omit for the latest available snapshot.
        """
        return self._get(
            f"/v1/orderbook/{token_id}/snapshot",
            {"timestamp": timestamp, "limit": limit},
        )

    def orderbook_history(
        self,
        token_id: str,
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        granularity: str = "1h",
        limit: int = 100,
    ) -> dict:
        """Aggregated orderbook history over time buckets.

        ``granularity`` accepts: ``50ms``, ``1s``, ``5s``, ``1m``, ``5m``, ``15m``, ``1h``, ``4h``, ``1d``.
        """
        return self._get(
            f"/v1/orderbook/{token_id}/history",
            {"start_time": start_time, "end_time": end_time, "granularity": granularity, "limit": limit},
        )

    def orderbook_depth(
        self,
        token_id: str,
        timestamp: Optional[str] = None,
        levels: int = 50,
    ) -> dict:
        """Depth chart data (cumulative bid/ask sizes) at a point in time."""
        return self._get(
            f"/v1/orderbook/{token_id}/depth",
            {"timestamp": timestamp, "levels": levels},
        )

    def spread_history(
        self,
        token_id: str,
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        granularity: str = "1h",
        limit: int = 100,
    ) -> dict:
        """Bid-ask spread history over time buckets."""
        return self._get(
            f"/v1/orderbook/{token_id}/spread",
            {"start_time": start_time, "end_time": end_time, "granularity": granularity, "limit": limit},
        )

    def sovereign_chart(
        self,
        token_id: str,
        granularity: str = "1m",
        limit: int = 200,
    ) -> dict:
        """OHLC price chart from sovereign recorder data (sub-100 ms).

        ``granularity`` accepts: ``1s``, ``5s``, ``1m``, ``5m``, ``1h``.
        """
        return self._get(
            f"/v1/orderbook/sovereign/chart/{token_id}",
            {"granularity": granularity, "limit": limit},
        )

    def list_tokens(self, limit: int = 500) -> dict:
        """List all available tokens/markets in the dataset."""
        return self._get("/v1/orderbook/tokens/list", {"limit": limit})

    def data_coverage(self) -> dict:
        """Data coverage info (row counts, time ranges, tokens)."""
        return self._get("/v1/orderbook/data/coverage")

    def data_schema(self) -> dict:
        """Parquet schema — column names and types."""
        return self._get("/v1/orderbook/data/schema")

    def cross_market_correlation(
        self,
        token_id: str,
        window_minutes: int = 60,
        limit: int = 20,
    ) -> dict:
        """Cross-market correlation: find markets whose prices moved in sync with a reference token.

        Uses Pearson correlation of 5-minute mid-price buckets.
        """
        return self._get(
            "/v1/orderbook/correlations",
            {"token_id": token_id, "window_minutes": window_minutes, "limit": limit},
        )

    def orderbook_correlation_matrix(
        self,
        token_ids: str,
        window: str = "24h",
        metric: str = "mid",
    ) -> dict:
        """Full NxN correlation matrix for portfolio analysis.

        ``token_ids``: comma-separated (max 20). ``window``: 1h, 4h, 12h, 24h, 7d.
        ``metric``: mid, spread, bid, ask.
        """
        return self._get(
            "/v1/orderbook/correlation-matrix",
            {"token_ids": token_ids, "window": window, "metric": metric},
        )

    def orderbook_top_movers(
        self,
        window: str = "1h",
        limit: int = 20,
        direction: str = "both",
    ) -> dict:
        """Markets with largest price movements in the time window.

        ``window``: 1h, 4h, 12h, 24h. ``direction``: up, down, both.
        """
        return self._get(
            "/v1/orderbook/top-movers",
            {"window": window, "limit": limit, "direction": direction},
        )

    def sentiment(self, token_id: str, *, window: str = "4h") -> dict:
        """Composite sentiment score for a Polymarket token."""
        return self._get(f"/v1/orderbook/{token_id}/sentiment", {"window": window})

    def sentiment_scan(
        self, *, window: str = "4h", limit: int = 20, min_activity: int = 10
    ) -> dict:
        """Scan all tokens for sentiment signals."""
        return self._get(
            "/v1/orderbook/sentiment/scan",
            {"window": window, "limit": limit, "min_activity": min_activity},
        )

    def monitoring_status(self) -> dict:
        """System health status (sovereign, binance, data, disk)."""
        return self._get("/v1/monitoring/status")

    def monitoring_history(self) -> dict:
        """Last 24h of health check results."""
        return self._get("/v1/monitoring/history")

    def orderbook_replay(
        self,
        token_id: str,
        start_time: str,
        end_time: Optional[str] = None,
        granularity: str = "1s",
        include_depth: bool = False,
    ) -> dict:
        """Replay historical orderbook ticks for backtesting. Single token, 24h max window.

        ``granularity``: 50ms, 1s, 5s, 1m. Returns data_coverage_pct.
        """
        return self._get(
            "/v1/orderbook/replay",
            {
                "token_id": token_id,
                "start_time": start_time,
                "end_time": end_time,
                "granularity": granularity,
                "include_depth": include_depth,
            },
        )

    def orderbook_replay_multi(
        self,
        token_ids: str,
        start_time: str,
        end_time: Optional[str] = None,
        granularity: str = "1m",
    ) -> dict:
        """Replay historical orderbook ticks for multiple tokens (up to 10). For backtesting.

        ``token_ids``: comma-separated token IDs. ``granularity``: 50ms, 1s, 5s, 1m.
        """
        return self._get(
            "/v1/orderbook/replay/multi",
            {
                "token_ids": token_ids,
                "start_time": start_time,
                "end_time": end_time,
                "granularity": granularity,
            },
        )

    def orderbook_simulate(self, trades: List[Dict[str, Any]]) -> dict:
        """Simulate P&L for hypothetical trades against historical orderbook data.

        Each trade: ``token_id``, ``side`` (buy/sell), ``entry_time``, ``exit_time`` (ISO 8601),
        ``size`` (default 100). Returns simulation summary (total_pnl, win_rate, sharpe_ratio,
        max_drawdown_pct) and per-trade results with entry/exit prices and slippage estimate.
        Max 50 trades per request. Requires sovereign orderbook data.
        """
        return self._post("/v1/orderbook/simulate", json={"trades": trades})

    def query(self, sql: str, limit: int = 1000) -> dict:
        """Execute a read-only SQL query against the orderbook data.

        Available views: ``orderbook_raw``, ``price_changes``, ``book_snapshots``.
        """
        return self._post("/v1/orderbook/query", params={"sql": sql, "limit": limit})

    # ── Crypto Reference ───────────────────────────────────────

    def crypto_history(self, symbol: str, interval: int = 60, limit: int = 200) -> list:
        """Time-bucketed price history for a crypto symbol.

        ``interval`` is the bucket size in seconds.
        """
        return self._get(f"/v1/crypto/history/{symbol}", {"interval": interval, "limit": limit})

    def crypto_chart(self, symbol: str, interval: int = 30, limit: int = 300) -> dict:
        """Chart-optimised price data (auto-selects best source: klines > ticks > CoinGecko)."""
        return self._get(f"/v1/crypto/chart/{symbol}", {"interval": interval, "limit": limit})

    def crypto_klines(self, symbol: str, interval: str = "1h", limit: int = 500) -> dict:
        """Historical OHLCV candles from Binance (1m to 1d resolution)."""
        return self._get(f"/v1/crypto/klines/{symbol}", {"interval": interval, "limit": limit})

    def crypto_ticks(self, symbol: str, limit: int = 500, event_type: Optional[str] = None) -> dict:
        """Recent high-frequency trade ticks from Binance WebSocket."""
        return self._get(f"/v1/crypto/ticks/{symbol}", {"limit": limit, "event_type": event_type})

    def crypto_tick_ohlcv(self, symbol: str, interval: int = 1, limit: int = 500) -> dict:
        """OHLCV candles built from high-frequency tick data.

        ``interval`` is candle size in seconds (1 = 1 s, 60 = 1 m).
        """
        return self._get(f"/v1/crypto/ticks/{symbol}/ohlcv", {"interval": interval, "limit": limit})

    def crypto_depth(self, symbol: str) -> dict:
        """Latest full order book snapshot (top 20 levels)."""
        return self._get(f"/v1/crypto/depth/{symbol}")

    def crypto_coverage(self) -> dict:
        """Data coverage info for all crypto recordings."""
        return self._get("/v1/crypto/coverage")

    def crypto_klines_coverage(self) -> dict:
        """Coverage info for historical klines data."""
        return self._get("/v1/crypto/klines/data/coverage")

    def crypto_ticks_coverage(self) -> dict:
        """Coverage info for high-frequency tick data."""
        return self._get("/v1/crypto/ticks/data/coverage")

    def crypto_symbols(self) -> dict:
        """Available symbols with CoinGecko IDs."""
        return self._get("/v1/crypto/symbols")

    def btc_price(self, timestamp: Optional[str] = None) -> dict:
        """BTC price — current or historical.

        Pass an ISO-8601 ``timestamp`` for a historical lookup.
        """
        return self._get("/v1/metadata/btc/price", {"timestamp": timestamp})

    # ── Event Correlation ──────────────────────────────────────

    def event_timeline(
        self,
        event_id: str,
        interval_minutes: int = 5,
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
    ) -> dict:
        """Chronological timeline merging price movements, news, and metadata."""
        return self._get(
            f"/v1/metadata/{event_id}/timeline",
            {"interval_minutes": interval_minutes, "start_time": start_time, "end_time": end_time},
        )

    def event_news(self, event_id: str, fresh: bool = False) -> dict:
        """News articles related to a Polymarket event."""
        return self._get(f"/v1/metadata/news/{event_id}", {"fresh": fresh})

    def event_metadata(self, event_id: str, metadata_type: Optional[str] = None) -> dict:
        """Stored metadata for an event."""
        return self._get(f"/v1/metadata/{event_id}", {"metadata_type": metadata_type})

    # ── Trader Activity ────────────────────────────────────────

    def leaderboard(self, limit: int = 20) -> dict:
        """Top tracked Polymarket traders ranked by PnL."""
        return self._get("/v1/signals/leaderboard", {"limit": limit})

    def latest_signals(self, limit: int = 50, signal_type: Optional[str] = None) -> dict:
        """Recent copy-trading signals from top traders."""
        return self._get("/v1/signals/latest", {"limit": limit, "signal_type": signal_type})

    def signal_status(self) -> dict:
        """Copy-trading signal engine status and stats."""
        return self._get("/v1/signals/status")

    def list_traders(
        self,
        min_pnl: Optional[float] = None,
        min_score: Optional[float] = None,
        limit: int = 50,
    ) -> dict:
        """List all tracked traders with summary stats."""
        return self._get(
            "/v1/signals/traders",
            {"min_pnl": min_pnl, "min_score": min_score, "limit": limit},
        )

    def trader_profile(self, address: str) -> dict:
        """Full profile for a single tracked trader."""
        return self._get(f"/v1/signals/traders/{address}/profile")

    def trader_signals(self, address: str, limit: int = 50, signal_type: Optional[str] = None) -> dict:
        """All signals generated for a specific trader."""
        return self._get(
            f"/v1/signals/traders/{address}/signals",
            {"limit": limit, "signal_type": signal_type},
        )

    def trader_trades(
        self,
        address: str,
        market_id: Optional[str] = None,
        limit: int = 50,
    ) -> dict:
        """Trade history for a tracked trader. Optionally filter by market_id."""
        params: Dict[str, Any] = {"limit": limit}
        if market_id:
            params["market_id"] = market_id
        return self._get(f"/v1/signals/traders/{address}/trades", params)

    def trader_pnl(self, address: str, limit: int = 50) -> dict:
        """Per-market realized PnL computed from a trader's trade history."""
        return self._get(f"/v1/signals/traders/{address}/pnl", {"limit": limit})

    # ── Data Export ────────────────────────────────────────────

    def export_orderbook(
        self,
        token_id: Optional[str] = None,
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        limit: int = 10000,
        format: str = "json",
    ) -> dict:
        """Export orderbook snapshot data for offline backtesting."""
        return self._get(
            "/v1/export/orderbook",
            {"token_id": token_id, "start_time": start_time, "end_time": end_time, "limit": limit, "format": format},
        )

    def export_klines(
        self,
        symbol: str,
        interval: str = "1h",
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        limit: int = 10000,
        format: str = "json",
    ) -> dict:
        """Export crypto OHLCV kline data for offline backtesting."""
        return self._get(
            "/v1/export/crypto/klines",
            {"symbol": symbol, "interval": interval, "start_time": start_time, "end_time": end_time, "limit": limit, "format": format},
        )

    def export_catalog(self) -> dict:
        """List available datasets and export options."""
        return self._get("/v1/export/catalog")

    def list_parquet_files(self, dataset: str = "orderbook") -> dict:
        """List available parquet files for raw download.

        ``dataset``: orderbook, crypto_klines, crypto_ticks, crypto_depth.
        """
        return self._get("/v1/export/files", {"dataset": dataset})

    # ── Market Analytics ─────────────────────────────────────────

    def volatility(self, token_id: str, *, window: str = "1h",
                   start_time: Optional[str] = None, end_time: Optional[str] = None) -> dict:
        """Realized volatility for a Polymarket token."""
        return self._get(f"/v1/orderbook/{token_id}/volatility",
                         _strip_none({"window": window, "start_time": start_time, "end_time": end_time}))

    def vwap(self, token_id: str, *, interval: str = "1h", limit: int = 100,
             start_time: Optional[str] = None, end_time: Optional[str] = None) -> dict:
        """VWAP (spread-inverse weighted) for a Polymarket token."""
        return self._get(f"/v1/orderbook/{token_id}/vwap",
                         _strip_none({"interval": interval, "limit": limit,
                                      "start_time": start_time, "end_time": end_time}))

    def microstructure(self, token_id: str, *, window: str = "1h",
                       start_time: Optional[str] = None, end_time: Optional[str] = None) -> dict:
        """Order flow microstructure metrics for a Polymarket token."""
        return self._get(f"/v1/orderbook/{token_id}/microstructure",
                         _strip_none({"window": window, "start_time": start_time, "end_time": end_time}))

    # ── Data Quality ────────────────────────────────────────────

    def orderbook_quality(self) -> dict:
        """Sovereign orderbook data quality report."""
        return self._get("/v1/orderbook/data/quality")

    def crypto_quality(self) -> dict:
        """Crypto data quality report."""
        return self._get("/v1/crypto/data/quality")

    # ── Market Categories ──────────────────────────────────────

    def market_categories(self, limit: int = 100) -> dict:
        """Markets grouped by auto-detected category (crypto, politics, sports, etc.)."""
        return self._get("/v1/events/categories", {"limit": limit})

    def classify_markets(self, *, limit: int = 50, category: Optional[str] = None) -> dict:
        """Auto-classify markets by category. Returns categorized events with tags and confidence."""
        return self._get("/v1/events/classify", _strip_none({"limit": limit, "category": category}))

    def event_tags(self, event_id: str) -> dict:
        """Classification tags for a specific event (category, tags, confidence, related_categories)."""
        return self._get(f"/v1/events/{event_id}/tags")

    # ── Health ─────────────────────────────────────────────────

    def health(self) -> dict:
        """Basic health check."""
        return self._get("/health")

    def status(self) -> dict:
        """Detailed system status."""
        return self._get("/status")


# ── helpers ────────────────────────────────────────────────────

def _strip_none(params: Optional[dict]) -> Optional[dict]:
    """Remove keys whose value is None so they aren't sent as query params."""
    if params is None:
        return None
    return {k: v for k, v in params.items() if v is not None}
