"""Tests for Kalshi support across the intelligence suite."""

from __future__ import annotations

import importlib
from unittest.mock import MagicMock, patch

import pytest

from horizon.oracle import (
    MarketForecast,
    OracleConfig,
    SignalReading,
    forecast_market,
    oracle,
    scan_edges,
)

_AUTH = "horizon.oracle.auth_require_ultra"


# ---------------------------------------------------------------------------
# Resolution analysis -- exchange param
# ---------------------------------------------------------------------------


def test_analyze_resolution_kalshi_exchange():
    """analyze_resolution with exchange='kalshi' calls _fetch_kalshi_description."""
    from horizon.resolution import analyze_resolution, ResolutionConfig

    kalshi_desc = {
        "title": "Will BTC hit 100k?",
        "description": "Will BTC hit 100k?\nResolves YES if BTC >= $100k.\nCrypto milestone",
        "resolution_source": "CoinGecko",
        "end_date": "2026-12-31T00:00:00Z",
    }
    llm_response = (
        '{"resolution_source": "CoinGecko", "conditions": [],'
        ' "ambiguity_score": 0.2, "edge_cases": [], "timing_risks": [],'
        ' "resolution_type": "threshold", "estimated_resolution_date": "2026-12-31",'
        ' "risk_level": "low", "reasoning": "Clear threshold"}'
    )

    with patch("horizon.resolution._fetch_kalshi_description", return_value=kalshi_desc) as mock_kalshi, \
         patch("horizon.resolution._call_resolution_llm", return_value=llm_response):
        analysis = analyze_resolution(
            "KXBTC100K",
            config=ResolutionConfig(cache_ttl=0),
            exchange="kalshi",
        )

    assert analysis.market_id == "KXBTC100K"
    assert analysis.risk_level == "low"
    assert analysis.ambiguity_score == 0.2
    mock_kalshi.assert_called_once_with("KXBTC100K")


def test_analyze_resolution_exchange_default():
    """Default exchange is polymarket (backward compat)."""
    from horizon.resolution import analyze_resolution, ResolutionConfig

    llm_response = (
        '{"resolution_source": "AP", "conditions": [],'
        ' "ambiguity_score": 0.3, "edge_cases": [], "timing_risks": [],'
        ' "resolution_type": "binary_event", "estimated_resolution_date": "unknown",'
        ' "risk_level": "low", "reasoning": "Clear"}'
    )

    with patch("horizon.resolution._fetch_market_description") as mock_fetch, \
         patch("horizon.resolution._call_resolution_llm", return_value=llm_response):
        mock_fetch.return_value = {
            "title": "Test", "description": "Desc",
            "resolution_source": "AP", "end_date": "",
        }
        analysis = analyze_resolution(
            "test-slug",
            config=ResolutionConfig(cache_ttl=0),
        )

    # Should call _fetch_market_description with exchange="polymarket"
    mock_fetch.assert_called_once_with("test-slug", exchange="polymarket")
    assert analysis.market_id == "test-slug"


def test_batch_analyze_kalshi():
    """batch_analyze passes exchange to analyze_resolution."""
    from horizon.resolution import batch_analyze, ResolutionConfig, ResolutionAnalysis
    import time

    mock_analysis = ResolutionAnalysis(
        market_id="KX1", market_title="Test", resolution_source="Kalshi",
        conditions=[], ambiguity_score=0.1, edge_cases=[], timing_risks=[],
        resolution_type="binary_event", estimated_resolution_date="unknown",
        risk_level="low", reasoning="Clear", timestamp=time.time(),
    )

    with patch("horizon.resolution.analyze_resolution", return_value=mock_analysis) as mock_ar:
        results = batch_analyze(
            ["KX1", "KX2"],
            config=ResolutionConfig(cache_ttl=0),
            exchange="kalshi",
        )

    assert len(results) == 2
    # Verify exchange was forwarded
    for call in mock_ar.call_args_list:
        assert call[1]["exchange"] == "kalshi"


# ---------------------------------------------------------------------------
# Kalshi description fetching
# ---------------------------------------------------------------------------


def test_fetch_kalshi_description_mocked():
    """_fetch_kalshi_description parses Kalshi API response."""
    from horizon.resolution import _fetch_kalshi_description

    kalshi_data = {
        "market": {
            "title": "Will it rain tomorrow?",
            "rules": "Resolves YES if rain measured > 0.1in by NWS.",
            "subtitle": "Weather market",
            "settlement_source": "NWS",
            "close_time": "2026-03-01T00:00:00Z",
        }
    }

    mock_resp = MagicMock()
    mock_resp.ok = True
    mock_resp.json.return_value = kalshi_data

    with patch("requests.get", return_value=mock_resp) as mock_get:
        result = _fetch_kalshi_description("KXRAIN")

    assert result["title"] == "Will it rain tomorrow?"
    assert "NWS" in result["description"]
    assert result["resolution_source"] == "NWS"
    assert result["end_date"] == "2026-03-01T00:00:00Z"


def test_fetch_kalshi_description_api_error():
    """_fetch_kalshi_description returns empty dict on API error."""
    from horizon.resolution import _fetch_kalshi_description

    mock_resp = MagicMock()
    mock_resp.ok = False
    mock_resp.status_code = 404

    with patch("requests.get", return_value=mock_resp):
        result = _fetch_kalshi_description("KXBADID")

    assert result == {}


# ---------------------------------------------------------------------------
# MCP tools -- exchange param
# ---------------------------------------------------------------------------


def test_llm_scan_markets_kalshi():
    """llm_scan_markets passes exchange to top_markets."""
    from horizon.tools import llm_scan_markets

    mock_market = MagicMock()
    mock_market.id = "KXTEST"
    mock_market.name = "Kalshi Test"

    with patch("horizon.discovery.top_markets", return_value=[mock_market]) as mock_top, \
         patch("horizon.llm.llm_scan", return_value=[]):
        llm_scan_markets(exchange="kalshi")

    mock_top.assert_called_once_with(limit=20, exchange="kalshi")


def test_sniper_scan_kalshi():
    """sniper_scan_tool passes exchange to top_markets."""
    from horizon.tools import sniper_scan_tool

    mock_market = MagicMock()
    mock_market.id = "KXTEST"
    mock_market.name = "Kalshi Test"

    with patch("horizon.discovery.top_markets", return_value=[mock_market]) as mock_top, \
         patch("horizon.sniper.scan_resolutions", return_value=[]):
        sniper_scan_tool(exchange="kalshi")

    mock_top.assert_called_once_with(limit=10, exchange="kalshi")


def test_analyze_resolution_tool_kalshi():
    """analyze_resolution_tool passes exchange param."""
    from horizon.tools import analyze_resolution_tool

    mock_analysis = MagicMock()
    mock_analysis.market_id = "KXTEST"
    mock_analysis.market_title = "Test"
    mock_analysis.resolution_source = "Kalshi"
    mock_analysis.conditions = []
    mock_analysis.ambiguity_score = 0.2
    mock_analysis.edge_cases = []
    mock_analysis.timing_risks = []
    mock_analysis.resolution_type = "binary_event"
    mock_analysis.estimated_resolution_date = "unknown"
    mock_analysis.risk_level = "low"
    mock_analysis.reasoning = "Clear"

    with patch("horizon.resolution.analyze_resolution", return_value=mock_analysis) as mock_ar:
        result = analyze_resolution_tool("KXTEST", exchange="kalshi")

    mock_ar.assert_called_once()
    assert mock_ar.call_args[1]["exchange"] == "kalshi"


def test_oracle_forecast_market_kalshi():
    """oracle_forecast_market passes exchange param."""
    from horizon.tools import oracle_forecast_market

    mock_fc = MagicMock()
    mock_fc.market_id = "KXTEST"
    mock_fc.ensemble_prob = 0.55
    mock_fc.confidence_low = 0.4
    mock_fc.confidence_high = 0.7
    mock_fc.edge_bps = 500.0
    mock_fc.market_price = 0.5
    mock_fc.signals = []
    mock_fc.timestamp = 1.0

    with patch("horizon.oracle.forecast_market", return_value=mock_fc) as mock_fm:
        oracle_forecast_market("KXTEST", exchange="kalshi")

    mock_fm.assert_called_once()
    assert mock_fm.call_args[1]["exchange"] == "kalshi"


def test_oracle_scan_edges_kalshi():
    """oracle_scan_edges passes exchange param."""
    from horizon.tools import oracle_scan_edges

    with patch("horizon.oracle.scan_edges", return_value=[]) as mock_se:
        oracle_scan_edges(exchange="kalshi")

    mock_se.assert_called_once()
    assert mock_se.call_args[1]["exchange"] == "kalshi"


# ---------------------------------------------------------------------------
# Oracle -- graceful degradation for Kalshi
# ---------------------------------------------------------------------------


class TestOracleKalshiGraceful:
    @patch(_AUTH)
    def test_forecast_market_kalshi_skips_flow(self, mock_auth):
        """forecast_market with exchange='kalshi' skips flow-dependent signals."""
        fc = forecast_market(
            "KXTEST",
            market_title="Kalshi Test",
            market_price=0.5,
            exchange="kalshi",
        )
        assert isinstance(fc, MarketForecast)
        # All signals should be neutral (0.5) for Kalshi
        for sig in fc.signals:
            assert sig.normalized_value == 0.5, (
                f"Signal {sig.name} should be 0.5 for Kalshi, got {sig.normalized_value}"
            )

    @patch(_AUTH)
    def test_forecast_market_kalshi_no_flow_calls(self, mock_auth):
        """forecast_market with exchange='kalshi' does not call flow.py."""
        with patch("horizon.oracle.get_market_trades") as mock_trades, \
             patch("horizon.oracle.get_top_holders") as mock_holders:
            fc = forecast_market(
                "KXTEST",
                market_title="Kalshi Test",
                market_price=0.5,
                exchange="kalshi",
            )
        # Should NOT call Polymarket-only APIs
        mock_trades.assert_not_called()
        mock_holders.assert_not_called()

    @patch(_AUTH)
    def test_forecast_market_kalshi_ensemble_neutral(self, mock_auth):
        """Kalshi forecast with all neutral signals produces ~0.5 ensemble."""
        fc = forecast_market(
            "KXTEST",
            market_title="Kalshi Test",
            market_price=0.5,
            exchange="kalshi",
        )
        # All neutral -> ensemble should be ~0.5
        assert 0.4 <= fc.ensemble_prob <= 0.6

    @patch(_AUTH)
    def test_scan_edges_kalshi_uses_kalshi_discovery(self, mock_auth):
        """scan_edges with exchange='kalshi' discovers Kalshi markets."""
        mock_market = MagicMock()
        mock_market.id = "KXTEST"
        mock_market.name = "Kalshi Test"

        with patch("horizon.discovery.top_markets", return_value=[mock_market]) as mock_top, \
             patch("horizon.oracle.forecast_market") as mock_fc:
            mock_fc.return_value = MarketForecast(
                market_id="KXTEST", market_title="T", ensemble_prob=0.5,
                confidence_low=0.4, confidence_high=0.6, edge_bps=0.0,
                signals=[], market_price=0.5, timestamp=1.0,
            )
            scan_edges(exchange="kalshi")

        mock_top.assert_called_once_with(limit=30, exchange="kalshi")

    @patch(_AUTH)
    def test_oracle_pipeline_kalshi(self, mock_auth):
        """oracle() pipeline with exchange='kalshi' passes exchange to forecast."""
        fn = oracle(forecast_interval_cycles=1, exchange="kalshi")

        ctx = MagicMock()
        ctx.market.id = "KXTEST"
        ctx.market.name = "Kalshi Test"
        ctx.feed.price = 0.5
        ctx.params = {}

        with patch("horizon.oracle.forecast_market") as mock_fc:
            sig = SignalReading(
                name="s", raw_value=0.5, normalized_value=0.5,
                weight=1.0, contribution=0.5,
            )
            mock_fc.return_value = MarketForecast(
                market_id="KXTEST", market_title="T", ensemble_prob=0.5,
                confidence_low=0.4, confidence_high=0.6, edge_bps=0.0,
                signals=[sig], market_price=0.5, timestamp=1.0,
            )
            fn(ctx)

        mock_fc.assert_called_once()
        assert mock_fc.call_args[1]["exchange"] == "kalshi"


# ---------------------------------------------------------------------------
# Polymarket-only module docstrings
# ---------------------------------------------------------------------------


def _get_module_doc(module_name: str) -> str:
    """Import a module by full path and return its __doc__."""
    mod = importlib.import_module(module_name)
    return mod.__doc__ or ""


def test_flow_module_docstring():
    """flow.py docstring mentions Polymarket-only."""
    assert "Polymarket-only" in _get_module_doc("horizon.flow")


def test_whale_galaxy_module_docstring():
    """whale_galaxy.py docstring mentions Polymarket-only."""
    assert "Polymarket-only" in _get_module_doc("horizon.whale_galaxy")


def test_copy_trader_module_docstring():
    """copy_trader.py docstring mentions Polymarket-only."""
    assert "Polymarket-only" in _get_module_doc("horizon.copy_trader")


def test_wallet_intel_module_docstring():
    """wallet_intel.py docstring mentions Polymarket-only."""
    assert "Polymarket-only" in _get_module_doc("horizon.wallet_intel")


def test_wallet_profiler_module_docstring():
    """wallet_profiler.py docstring mentions Polymarket-only."""
    assert "Polymarket-only" in _get_module_doc("horizon.wallet_profiler")
