"""Resolution analysis for prediction markets.

Analyzes resolution conditions, identifies ambiguity risks, edge cases, and
timing risks using LLM-powered reasoning. Provides a pipeline guard that
blocks quoting on markets with high ambiguity scores.

Typical usage::

    # Standalone analysis
    analysis = hz.analyze_resolution("market-123")

    # Pipeline guard
    hz.run(pipeline=[hz.resolution_guard(block_above_ambiguity=0.6), my_quoter])
"""

from __future__ import annotations

import json
import logging
import os
import re
import time
from dataclasses import dataclass, field
from typing import Any, Callable

from horizon.context import Context

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


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


@dataclass(frozen=True)
class ResolutionCondition:
    """A single resolution condition for a prediction market.

    Attributes:
        description: What needs to happen for resolution.
        source: Authority that determines resolution (e.g., "AP", "official").
        verifiable: Whether the condition is objectively verifiable.
        ambiguity_notes: Notes on potential ambiguity.
    """

    description: str
    source: str
    verifiable: bool
    ambiguity_notes: str


@dataclass(frozen=True)
class ResolutionAnalysis:
    """Complete resolution analysis for a prediction market.

    Attributes:
        market_id: Market identifier.
        market_title: Human-readable market question.
        resolution_source: Primary resolution authority.
        conditions: List of resolution conditions.
        ambiguity_score: 0.0 (clear) to 1.0 (highly ambiguous).
        edge_cases: Identified edge cases that could complicate resolution.
        timing_risks: Timing-related resolution risks.
        resolution_type: Type of resolution mechanism.
        estimated_resolution_date: Expected resolution date or "unknown".
        risk_level: Overall risk level ("low", "medium", "high").
        reasoning: LLM reasoning for the analysis.
        timestamp: Unix timestamp of analysis generation.
    """

    market_id: str
    market_title: str
    resolution_source: str
    conditions: list[ResolutionCondition]
    ambiguity_score: float
    edge_cases: list[str]
    timing_risks: list[str]
    resolution_type: str
    estimated_resolution_date: str
    risk_level: str
    reasoning: str
    timestamp: float


@dataclass
class ResolutionConfig:
    """Configuration for resolution analysis.

    Attributes:
        provider: LLM provider (``"anthropic"``, ``"openai"``, or any
            litellm-supported provider).
        model: Model name. Empty string uses provider default. Accepts
            litellm-qualified strings like ``"openrouter/meta-llama/..."``.
        ambiguity_threshold: Score above which a market is flagged.
        cache_ttl: Cache time-to-live in seconds (rules rarely change).
    """

    provider: str = "anthropic"
    model: str = ""
    ambiguity_threshold: float = 0.6
    cache_ttl: float = 3600.0


# ---------------------------------------------------------------------------
# Module-level cache
# ---------------------------------------------------------------------------

_analysis_cache: dict[str, tuple[float, ResolutionAnalysis]] = {}


# ---------------------------------------------------------------------------
# Market description fetching
# ---------------------------------------------------------------------------


def _fetch_kalshi_description(market_id: str) -> dict[str, Any]:
    """Fetch market details from the Kalshi public API.

    Returns a dict with keys: title, description, resolution_source, end_date.
    """
    try:
        import requests
    except ImportError:
        logger.warning("requests not installed")
        return {}

    try:
        resp = requests.get(
            f"https://api.elections.kalshi.com/trade-api/v2/markets/{market_id}",
            timeout=15,
        )
        if not resp.ok:
            logger.debug("Kalshi API returned %d for %s", resp.status_code, market_id)
            return {}

        data = resp.json().get("market", {})
        title = data.get("title", "")
        rules = data.get("rules", "")
        subtitle = data.get("subtitle", "")
        description = "\n".join(p for p in [title, rules, subtitle] if p)

        return {
            "title": title,
            "description": description,
            "resolution_source": data.get("settlement_source", ""),
            "end_date": data.get("close_time", ""),
        }
    except Exception as exc:
        logger.debug("Kalshi description fetch failed for %s: %s", market_id, exc)
        return {}


def _fetch_market_description(
    market_id: str, exchange: str = "polymarket",
) -> dict[str, Any]:
    """Fetch market description from the exchange API.

    Returns a dict with keys: title, description, resolution_source, end_date.
    """
    if exchange == "kalshi":
        return _fetch_kalshi_description(market_id)

    try:
        import requests
    except ImportError:
        logger.warning("requests not installed")
        return {}

    gamma_url = os.environ.get(
        "GAMMA_API_URL", "https://gamma-api.polymarket.com"
    )

    try:
        resp = requests.get(
            f"{gamma_url}/markets",
            params={"slug": market_id, "limit": 1},
            timeout=15,
        )
        if not resp.ok:
            logger.debug("Gamma API returned %d for %s", resp.status_code, market_id)
            return {}

        data = resp.json()
        if isinstance(data, list) and data:
            data = data[0]
        elif not isinstance(data, dict):
            return {}

        return {
            "title": data.get("question") or data.get("title", ""),
            "description": data.get("description", ""),
            "resolution_source": data.get("resolutionSource", ""),
            "end_date": data.get("endDate") or data.get("end_date_iso", ""),
        }
    except Exception as exc:
        logger.debug("Market description fetch failed for %s: %s", market_id, exc)
        return {}


# ---------------------------------------------------------------------------
# LLM prompt + parsing
# ---------------------------------------------------------------------------


def _build_resolution_prompt(market_data: dict[str, Any]) -> str:
    """Build a prompt for resolution analysis."""
    title = market_data.get("title", "Unknown market")
    description = market_data.get("description", "No description available")
    resolution_source = market_data.get("resolution_source", "Unknown")
    end_date = market_data.get("end_date", "Unknown")

    return (
        "Analyze the resolution criteria for this prediction market.\n"
        "\n"
        f'Market: "{title}"\n'
        f'Description: "{description[:2000]}"\n'
        f'Resolution source: "{resolution_source}"\n'
        f'End date: "{end_date}"\n'
        "\n"
        "Return ONLY valid JSON:\n"
        "{\n"
        '  "resolution_source": "primary authority that determines outcome",\n'
        '  "conditions": [{"description": "...", "source": "...", "verifiable": true, "ambiguity_notes": "..."}],\n'
        '  "ambiguity_score": 0.XX,\n'
        '  "edge_cases": ["potential edge case 1", "..."],\n'
        '  "timing_risks": ["timing risk 1", "..."],\n'
        '  "resolution_type": "binary_event|threshold|committee|oracle",\n'
        '  "estimated_resolution_date": "YYYY-MM-DD or unknown",\n'
        '  "risk_level": "low|medium|high",\n'
        '  "reasoning": "1-3 sentence explanation of overall resolution risk"\n'
        "}\n"
    )


def _parse_resolution_response(
    text: str, market_id: str, market_title: str,
) -> ResolutionAnalysis:
    """Parse LLM response into ResolutionAnalysis."""
    now = time.time()

    # Defaults
    defaults = ResolutionAnalysis(
        market_id=market_id,
        market_title=market_title,
        resolution_source="unknown",
        conditions=[],
        ambiguity_score=0.5,
        edge_cases=[],
        timing_risks=[],
        resolution_type="binary_event",
        estimated_resolution_date="unknown",
        risk_level="medium",
        reasoning="Unable to parse LLM response",
        timestamp=now,
    )

    try:
        json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', text, re.DOTALL)
        if not json_match:
            return defaults

        data = json.loads(json_match.group())

        conditions = []
        for c in data.get("conditions", []):
            if isinstance(c, dict):
                conditions.append(ResolutionCondition(
                    description=str(c.get("description", "")),
                    source=str(c.get("source", "")),
                    verifiable=bool(c.get("verifiable", True)),
                    ambiguity_notes=str(c.get("ambiguity_notes", "")),
                ))

        ambiguity = float(data.get("ambiguity_score", 0.5))
        ambiguity = max(0.0, min(1.0, ambiguity))

        risk_level = str(data.get("risk_level", "medium"))
        if risk_level not in ("low", "medium", "high"):
            risk_level = "medium"

        resolution_type = str(data.get("resolution_type", "binary_event"))
        valid_types = ("binary_event", "threshold", "committee", "oracle")
        if resolution_type not in valid_types:
            resolution_type = "binary_event"

        return ResolutionAnalysis(
            market_id=market_id,
            market_title=market_title,
            resolution_source=str(data.get("resolution_source", "unknown")),
            conditions=conditions,
            ambiguity_score=round(ambiguity, 4),
            edge_cases=[str(e) for e in data.get("edge_cases", [])],
            timing_risks=[str(t) for t in data.get("timing_risks", [])],
            resolution_type=resolution_type,
            estimated_resolution_date=str(data.get("estimated_resolution_date", "unknown")),
            risk_level=risk_level,
            reasoning=str(data.get("reasoning", "")),
            timestamp=now,
        )
    except (json.JSONDecodeError, ValueError, TypeError) as exc:
        logger.debug("Resolution response parse failed: %s", exc)
        return defaults


def _call_resolution_llm(
    prompt: str, config: ResolutionConfig,
) -> str:
    """Call LLM for resolution analysis. Returns raw text response."""
    from horizon.llm import _call_llm_raw

    return _call_llm_raw(prompt, config.provider, config.model)


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


def analyze_resolution(
    market_id: str,
    market_title: str = "",
    market_description: str = "",
    config: ResolutionConfig | None = None,
    exchange: str = "polymarket",
) -> ResolutionAnalysis:
    """Analyze resolution conditions and risks for a prediction market.

    Fetches market description (if not provided), calls the configured LLM
    to parse resolution criteria, and returns a :class:`ResolutionAnalysis`.

    Args:
        market_id: Market identifier or slug.
        market_title: Optional market title. Fetched if empty.
        market_description: Optional description. Fetched if empty.
        config: Resolution configuration.
        exchange: Exchange to fetch market details from
            (``"polymarket"`` or ``"kalshi"``).

    Returns:
        :class:`ResolutionAnalysis` with conditions, ambiguity score, and risks.
    """
    config = config or ResolutionConfig()

    # Check cache
    now = time.time()
    if market_id in _analysis_cache:
        ts, cached = _analysis_cache[market_id]
        if now - ts < config.cache_ttl:
            return cached

    # Fetch market data if needed
    market_data: dict[str, Any] = {}
    if not market_title or not market_description:
        market_data = _fetch_market_description(market_id, exchange=exchange)

    if not market_title:
        market_title = market_data.get("title", market_id)
    if not market_description:
        market_data["description"] = market_data.get("description", "")
    else:
        market_data["description"] = market_description
    market_data["title"] = market_title
    market_data.setdefault("resolution_source", "")
    market_data.setdefault("end_date", "")

    # Build prompt and call LLM
    prompt = _build_resolution_prompt(market_data)
    response_text = _call_resolution_llm(prompt, config)

    if not response_text:
        # Return defaults without LLM
        analysis = ResolutionAnalysis(
            market_id=market_id,
            market_title=market_title,
            resolution_source="unknown",
            conditions=[],
            ambiguity_score=0.5,
            edge_cases=[],
            timing_risks=[],
            resolution_type="binary_event",
            estimated_resolution_date="unknown",
            risk_level="medium",
            reasoning="LLM unavailable",
            timestamp=now,
        )
    else:
        analysis = _parse_resolution_response(response_text, market_id, market_title)

    # Cache
    _analysis_cache[market_id] = (now, analysis)

    logger.debug(
        "analyze_resolution(%s): ambiguity=%.2f risk=%s type=%s",
        market_id[:12], analysis.ambiguity_score, analysis.risk_level,
        analysis.resolution_type,
    )

    return analysis


def batch_analyze(
    market_ids: list[str],
    config: ResolutionConfig | None = None,
    exchange: str = "polymarket",
) -> list[ResolutionAnalysis]:
    """Analyze resolution conditions for multiple markets.

    Args:
        market_ids: List of market identifiers.
        config: Resolution configuration.
        exchange: Exchange to fetch market details from
            (``"polymarket"`` or ``"kalshi"``).

    Returns:
        List of :class:`ResolutionAnalysis` results.
    """
    config = config or ResolutionConfig()
    results: list[ResolutionAnalysis] = []

    for market_id in market_ids:
        try:
            analysis = analyze_resolution(
                market_id, config=config, exchange=exchange,
            )
            results.append(analysis)
        except Exception as exc:
            logger.warning("batch_analyze: failed for %s: %s", market_id[:12], exc)

    return results


# ---------------------------------------------------------------------------
# Pipeline factory
# ---------------------------------------------------------------------------


def resolution_guard(
    config: ResolutionConfig | None = None,
    block_above_ambiguity: float = 0.6,
    analysis_interval_cycles: int = 100,
) -> Callable[[Context], None]:
    """Create a pipeline function that blocks quoting on ambiguous markets.

    On first encounter (and every *analysis_interval_cycles*), analyzes the
    market's resolution conditions. If ``ambiguity_score > block_above_ambiguity``,
    injects ``ctx.params["resolution_blocked"] = True`` so downstream functions
    can skip quoting.

    Injected context parameters:
        ``resolution_analysis`` -- the :class:`ResolutionAnalysis` object.
        ``resolution_risk`` -- ``str``, risk level ("low", "medium", "high").
        ``resolution_blocked`` -- ``bool``, True if ambiguity exceeds threshold.

    Args:
        config: Resolution configuration.
        block_above_ambiguity: Ambiguity threshold for blocking.
        analysis_interval_cycles: Cycles between analysis refreshes.

    Returns:
        Pipeline function with signature ``(Context) -> None``.
    """
    config = config or ResolutionConfig()

    analyses: dict[str, ResolutionAnalysis] = {}
    cycle_count = 0

    def _inner(ctx: Context) -> None:
        nonlocal analyses, cycle_count
        cycle_count += 1

        market = ctx.market
        if market is None:
            return
        market_id = market.id
        if not market_id:
            return

        # Analyze on first cycle or at interval
        needs_refresh = (
            market_id not in analyses
            or cycle_count % analysis_interval_cycles == 0
        )

        if needs_refresh:
            try:
                analysis = analyze_resolution(
                    market_id,
                    market_title=market.name,
                    config=config,
                )
                analyses[market_id] = analysis
                logger.info(
                    "Resolution analysis for %s: ambiguity=%.2f risk=%s",
                    market_id[:12], analysis.ambiguity_score, analysis.risk_level,
                )
            except Exception as exc:
                logger.warning(
                    "Resolution analysis failed for %s: %s", market_id[:12], exc,
                )

        if market_id in analyses:
            analysis = analyses[market_id]
            ctx.params["resolution_analysis"] = analysis
            ctx.params["resolution_risk"] = analysis.risk_level
            ctx.params["resolution_blocked"] = (
                analysis.ambiguity_score > block_above_ambiguity
            )

    _inner.__name__ = "resolution_guard"
    return _inner
