"""Market discovery for Polymarket and Kalshi.

Searches exchange APIs for markets matching filters and returns
Market objects ready for use with hz.run().
"""

from __future__ import annotations

import logging
from typing import Any

import requests

from horizon._horizon import Event, Market, Outcome

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

# API endpoints
_GAMMA_API_URL = "https://gamma-api.polymarket.com"
_KALSHI_API_URL = "https://trading-api.kalshi.com/trade-api/v2"


def discover_markets(
    exchange: str = "polymarket",
    query: str = "",
    active: bool = True,
    limit: int = 20,
    min_volume: float = 0.0,
    category: str = "",
    sort_by: str = "volume",
    gamma_url: str = _GAMMA_API_URL,
    kalshi_url: str = _KALSHI_API_URL,
) -> list[Market]:
    """Search for markets on the specified exchange.

    Args:
        exchange: "polymarket" or "kalshi".
        query: Search text (market title/slug).
        active: Only return active (open) markets.
        limit: Max number of results.
        min_volume: Minimum 24h volume filter.
        category: Category filter (exchange-specific).
        sort_by: Sort order - "volume" (default), "newest", "liquidity", or "".
        gamma_url: Override Polymarket Gamma API URL.
        kalshi_url: Override Kalshi API URL.

    Returns:
        List of Market objects populated with metadata.
    """
    if exchange == "polymarket":
        return _discover_polymarket(
            query=query, active=active, limit=limit,
            min_volume=min_volume, category=category,
            sort_by=sort_by, gamma_url=gamma_url,
        )
    elif exchange == "kalshi":
        return _discover_kalshi(
            query=query, active=active, limit=limit,
            category=category, sort_by=sort_by, kalshi_url=kalshi_url,
        )
    else:
        raise ValueError(f"Unknown exchange: {exchange}. Use 'polymarket' or 'kalshi'.")


def _discover_polymarket(
    query: str,
    active: bool,
    limit: int,
    min_volume: float,
    category: str,
    sort_by: str,
    gamma_url: str,
) -> list[Market]:
    """Search Polymarket markets via Gamma API."""
    # Fetch more than needed so volume filter doesn't cut below limit
    fetch_limit = max(limit * 3, 100) if min_volume > 0 else limit
    params: dict[str, Any] = {"limit": fetch_limit}
    if query:
        params["slug"] = query
    if active:
        params["active"] = "true"
    if category:
        params["tag"] = category
    # Gamma API sort: _sort field with - prefix for descending
    sort_map = {
        "volume": "-volume24hr",
        "newest": "-createdAt",
        "liquidity": "-liquidityNum",
    }
    if sort_by in sort_map:
        params["_sort"] = sort_map[sort_by]

    try:
        resp = requests.get(f"{gamma_url}/markets", params=params, timeout=15)
        resp.raise_for_status()
    except requests.RequestException as e:
        logger.error("Polymarket discovery failed: %s", e)
        return []

    try:
        data = resp.json()
    except ValueError as e:
        logger.error("Polymarket returned invalid JSON: %s", e)
        return []
    if not isinstance(data, list):
        data = [data]

    markets = []
    for item in data:
        # Volume filter
        vol = float(item.get("volume", 0) or 0)
        if vol < min_volume:
            continue

        slug = item.get("slug", item.get("question_id", ""))
        name = item.get("question", slug)

        # Extract token IDs
        yes_token = None
        no_token = None
        tokens = item.get("tokens", [])
        for token in tokens:
            outcome = token.get("outcome", "").lower()
            tid = token.get("token_id")
            if outcome == "yes" and tid:
                yes_token = tid
            elif outcome == "no" and tid:
                no_token = tid

        if not yes_token:
            yes_token = item.get("yes_token_id")
        if not no_token:
            no_token = item.get("no_token_id")

        condition_id = item.get("condition_id") or item.get("conditionId")
        neg_risk = bool(item.get("neg_risk") or item.get("negRisk", False))
        expiry = item.get("end_date_iso") or item.get("endDate")
        is_active = bool(item.get("active", True))

        m = Market(
            id=slug,
            name=name,
            slug=slug,
            exchange="polymarket",
            expiry=expiry,
            active=is_active,
            yes_token_id=yes_token,
            no_token_id=no_token,
            condition_id=condition_id,
            neg_risk=neg_risk,
        )
        markets.append(m)

    return markets[:limit]  # polymarket


def _discover_kalshi(
    query: str,
    active: bool,
    limit: int,
    category: str,
    sort_by: str,
    kalshi_url: str,
) -> list[Market]:
    """Search Kalshi markets via public API."""
    params: dict[str, Any] = {"limit": limit}
    if query:
        params["ticker"] = query.upper()
    if active:
        params["status"] = "open"
    if category:
        params["series_ticker"] = category

    try:
        resp = requests.get(f"{kalshi_url}/markets", params=params, timeout=15)
        resp.raise_for_status()
    except requests.RequestException as e:
        logger.error("Kalshi discovery failed: %s", e)
        return []

    try:
        data = resp.json()
    except ValueError as e:
        logger.error("Kalshi returned invalid JSON: %s", e)
        return []
    items = data.get("markets", data) if isinstance(data, dict) else data
    if not isinstance(items, list):
        items = [items]

    markets = []
    for item in items:
        ticker = item.get("ticker", "")
        title = item.get("title", ticker)
        expiry = item.get("expiration_time") or item.get("close_time")
        is_active = item.get("status", "open") == "open"

        m = Market(
            id=ticker.lower(),
            name=title,
            slug=ticker.lower(),
            exchange="kalshi",
            expiry=expiry,
            active=is_active,
            ticker=ticker,
        )
        markets.append(m)

    return markets[:limit]


# ---------------------------------------------------------------------------
# Multi-outcome event discovery
# ---------------------------------------------------------------------------


def _extract_outcome_name(question: str, event_title: str) -> str:
    """Derive a short outcome name from a market question and event title.

    Strips the event title prefix and common suffixes like "wins?" or "?"
    to produce a concise name like "Trump" from "Will Trump win the election?".
    """
    name = question
    if event_title and question.startswith(event_title):
        name = question[len(event_title):].strip(" -:")

    for suffix in ("?", " wins", " win", " to win"):
        if name.endswith(suffix):
            name = name[: -len(suffix)].strip()

    for prefix in ("Will ", "Does ", "Is ", "Can "):
        if name.startswith(prefix):
            name = name[len(prefix):]

    return name.strip() or question


def discover_events(
    exchange: str = "polymarket",
    query: str = "",
    active: bool = True,
    limit: int = 10,
    min_volume: float = 0.0,
    gamma_url: str = _GAMMA_API_URL,
) -> list[Event]:
    """Discover multi-outcome events from Polymarket.

    Hits the Gamma API /events endpoint and returns Event objects
    with all outcomes populated, including YES/NO token IDs and prices.

    Args:
        exchange: Currently only "polymarket" is supported.
        query: Search text for event title/slug.
        active: Only return active events.
        limit: Max number of events to return.
        min_volume: Minimum volume filter.
        gamma_url: Override Gamma API URL.

    Returns:
        List of Event objects with Outcome children.
    """
    if exchange != "polymarket":
        logger.warning("discover_events only supports polymarket, got %s", exchange)
        return []

    params: dict[str, Any] = {"limit": limit, "active": str(active).lower()}
    if query:
        params["slug"] = query

    try:
        resp = requests.get(f"{gamma_url}/events", params=params, timeout=15)
        resp.raise_for_status()
    except requests.RequestException as e:
        logger.error("Polymarket event discovery failed: %s", e)
        return []

    try:
        data = resp.json()
    except ValueError as e:
        logger.error("Polymarket returned invalid JSON: %s", e)
        return []

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

    events = []
    for item in data:
        event_id = str(item.get("slug", item.get("id", "")))
        event_title = str(item.get("title", event_id))
        condition_id = item.get("condition_id") or item.get("conditionId")
        neg_risk = bool(item.get("neg_risk") or item.get("negRisk", False))
        expiry = item.get("end_date_iso") or item.get("endDate")

        event_markets = item.get("markets", [])
        if not isinstance(event_markets, list) or len(event_markets) < 2:
            continue

        total_vol = sum(float(m.get("volume", 0) or 0) for m in event_markets)
        if total_vol < min_volume:
            continue

        outcomes = []
        for mkt in event_markets:
            mkt_slug = str(mkt.get("slug", mkt.get("question_id", "")))
            question = str(mkt.get("question", mkt_slug))
            outcome_name = _extract_outcome_name(question, event_title)

            yes_token = None
            no_token = None
            tokens = mkt.get("tokens", [])
            if isinstance(tokens, list):
                for token in tokens:
                    outcome_label = str(token.get("outcome", "")).lower()
                    tid = token.get("token_id")
                    if outcome_label == "yes" and tid:
                        yes_token = str(tid)
                    elif outcome_label == "no" and tid:
                        no_token = str(tid)

            if not yes_token:
                yes_token = mkt.get("yes_token_id")
            if not no_token:
                no_token = mkt.get("no_token_id")

            yes_price = float(mkt.get("outcomePrices", "0.0,0.0").split(",")[0] or 0)
            if yes_price == 0:
                yes_price = float(mkt.get("bestAsk", 0) or 0)

            outcomes.append(Outcome(
                name=outcome_name,
                market_id=mkt_slug,
                yes_token_id=yes_token,
                no_token_id=no_token,
                yes_price=yes_price,
            ))

        event = Event(
            id=event_id,
            name=event_title,
            outcomes=outcomes,
            neg_risk=neg_risk,
            exchange="polymarket",
            condition_id=str(condition_id) if condition_id else None,
            expiry=expiry,
        )
        events.append(event)

    return events[:limit]


# ---------------------------------------------------------------------------
# Convenience helpers
# ---------------------------------------------------------------------------


def top_markets(
    exchange: str = "polymarket",
    limit: int = 10,
    category: str = "",
    gamma_url: str = _GAMMA_API_URL,
    kalshi_url: str = _KALSHI_API_URL,
) -> list[Market]:
    """Get the top markets by volume.

    Convenience wrapper around discover_markets with sensible defaults.

    Args:
        exchange: "polymarket" or "kalshi".
        limit: Number of markets to return.
        category: Optional category filter.

    Returns:
        Top markets sorted by volume (descending).
    """
    return discover_markets(
        exchange=exchange,
        active=True,
        limit=limit,
        sort_by="volume",
        category=category,
        gamma_url=gamma_url,
        kalshi_url=kalshi_url,
    )
