"""
Test every Python code snippet from docs/feeds.mdx.

For snippets requiring live connections or API keys,
we only verify imports and object construction work.
"""

import traceback
import json

results = []


def run_test(name, fn):
    """Run a test function and record PASS/FAIL."""
    try:
        fn()
        results.append((name, "PASS", ""))
    except Exception as e:
        tb = traceback.format_exc()
        results.append((name, "FAIL", tb))


# =============================================================================
# Snippet 1: BinanceWS feed construction + fair_value function
# docs/feeds.mdx lines 48-56
# =============================================================================
def test_snippet_01_binance_ws():
    import horizon as hz

    # Verify BinanceWS object construction
    feed = hz.BinanceWS("btcusdt")
    assert feed.symbol == "btcusdt"
    assert feed._type == "binance_ws"

    # Verify the fair_value function pattern works with FeedData
    from horizon.context import FeedData
    fd = FeedData(price=100250.0, bid=100248.0, ask=100252.0)

    ctx = hz.Context(feeds={"btc": fd})

    def fair_value(ctx: hz.Context) -> float:
        return ctx.feeds["btc"].price

    result = fair_value(ctx)
    assert result == 100250.0


run_test("Snippet 01: BinanceWS feed + fair_value", test_snippet_01_binance_ws)


# =============================================================================
# Snippet 2: PolymarketBook feed construction + midpoint fair_value
# docs/feeds.mdx lines 68-77
# =============================================================================
def test_snippet_02_polymarket_book():
    import horizon as hz
    from horizon.context import FeedData

    # Verify PolymarketBook object construction
    feed = hz.PolymarketBook("will-btc-hit-100k")
    assert feed.market_slug == "will-btc-hit-100k"
    assert feed._type == "polymarket_book"

    # Verify the midpoint fair_value function
    fd = FeedData(bid=0.55, ask=0.60)
    ctx = hz.Context(feeds={"poly": fd})

    def fair_value(ctx: hz.Context) -> float:
        poly = ctx.feeds["poly"]
        return (poly.bid + poly.ask) / 2

    result = fair_value(ctx)
    assert abs(result - 0.575) < 1e-9


run_test("Snippet 02: PolymarketBook feed + midpoint", test_snippet_02_polymarket_book)


# =============================================================================
# Snippet 3: KalshiBook feed construction
# docs/feeds.mdx lines 87-92
# =============================================================================
def test_snippet_03_kalshi_book():
    import horizon as hz

    feed = hz.KalshiBook("KXBTC-25FEB16")
    assert feed.ticker == "KXBTC-25FEB16"
    assert feed._type == "kalshi_book"


run_test("Snippet 03: KalshiBook feed", test_snippet_03_kalshi_book)


# =============================================================================
# Snippet 4: RESTFeed construction
# docs/feeds.mdx lines 102-107
# =============================================================================
def test_snippet_04_rest_feed():
    import horizon as hz

    feed = hz.RESTFeed("https://api.example.com/price", interval=5.0)
    assert feed.url == "https://api.example.com/price"
    assert feed.interval == 5.0
    assert feed._type == "rest"


run_test("Snippet 04: RESTFeed", test_snippet_04_rest_feed)


# =============================================================================
# Snippet 5: PredictItFeed construction + fair_value
# docs/feeds.mdx lines 118-127
# =============================================================================
def test_snippet_05_predictit_feed():
    import horizon as hz
    from horizon.context import FeedData

    feed = hz.PredictItFeed(market_id=7456, contract_id=28562)
    assert feed.market_id == 7456
    assert feed.contract_id == 28562
    assert feed._type == "predictit"

    # Verify fair_value pattern
    fd = FeedData(price=0.65)
    ctx = hz.Context(feeds={"election": fd})

    def fair_value(ctx: hz.Context) -> float:
        pi = ctx.feeds["election"]
        return pi.price  # lastTradePrice

    assert fair_value(ctx) == 0.65


run_test("Snippet 05: PredictItFeed + fair_value", test_snippet_05_predictit_feed)


# =============================================================================
# Snippet 6: ManifoldFeed construction + fair_value
# docs/feeds.mdx lines 143-151
# =============================================================================
def test_snippet_06_manifold_feed():
    import horizon as hz
    from horizon.context import FeedData

    feed = hz.ManifoldFeed("will-btc-hit-100k-by-2026")
    assert feed.slug == "will-btc-hit-100k-by-2026"
    assert feed._type == "manifold"

    # Verify fair_value pattern
    fd = FeedData(price=0.72)
    ctx = hz.Context(feeds={"manifold": fd})

    def fair_value(ctx: hz.Context) -> float:
        return ctx.feeds["manifold"].price  # probability (0-1)

    assert fair_value(ctx) == 0.72


run_test("Snippet 06: ManifoldFeed + fair_value", test_snippet_06_manifold_feed)


# =============================================================================
# Snippet 7: ESPNFeed construction + score_signal
# docs/feeds.mdx lines 164-177
# =============================================================================
def test_snippet_07_espn_feed():
    import horizon as hz
    from horizon.context import FeedData

    feed = hz.ESPNFeed("basketball", "nba")
    assert feed.sport == "basketball"
    assert feed.league == "nba"
    assert feed._type == "espn"

    # Verify score_signal pattern
    fd = FeedData(price=105.0, bid=98.0, volume_24h=3.0,
                  source="espn:basketball:nba:12345:in_progress")
    ctx = hz.Context(feeds={"nba": fd})

    def score_signal(ctx: hz.Context) -> float:
        game = ctx.feeds["nba"]
        home_score = game.price
        away_score = game.bid
        period = game.volume_24h
        # game.source contains "espn:basketball:nba:{event_id}:{status}"
        return home_score - away_score

    result = score_signal(ctx)
    assert result == 7.0


run_test("Snippet 07: ESPNFeed + score_signal", test_snippet_07_espn_feed)


# =============================================================================
# Snippet 8: NWSFeed forecast mode + weather_signal
# docs/feeds.mdx lines 194-206
# =============================================================================
def test_snippet_08_nws_forecast():
    import horizon as hz
    from horizon.context import FeedData

    feed = hz.NWSFeed(office="TOP", grid_x=31, grid_y=80)
    assert feed.office == "TOP"
    assert feed.grid_x == 31
    assert feed.grid_y == 80
    assert feed.mode == "forecast"
    assert feed._type == "nws"

    # Verify weather_signal pattern
    fd = FeedData(price=75.0, bid=12.0, ask=30.0)  # temp, wind, precip
    ctx = hz.Context(feeds={"weather": fd})

    def weather_signal(ctx: hz.Context) -> float:
        wx = ctx.feeds["weather"]
        temperature = wx.price
        wind_speed = wx.bid
        precip_chance = wx.ask
        return precip_chance / 100.0

    result = weather_signal(ctx)
    assert abs(result - 0.30) < 1e-9


run_test("Snippet 08: NWSFeed forecast + weather_signal", test_snippet_08_nws_forecast)


# =============================================================================
# Snippet 9: NWSFeed alerts mode + alert_signal
# docs/feeds.mdx lines 210-222
# =============================================================================
def test_snippet_09_nws_alerts():
    import horizon as hz
    from horizon.context import FeedData

    feed = hz.NWSFeed(state="FL", mode="alerts")
    assert feed.state == "FL"
    assert feed.mode == "alerts"
    assert feed._type == "nws"

    # Verify alert_signal pattern
    fd = FeedData(price=1.0, bid=3.0, source="nws:alerts:FL:Severe")
    ctx = hz.Context(feeds={"alerts": fd})

    def alert_signal(ctx: hz.Context) -> float:
        al = ctx.feeds["alerts"]
        has_alerts = al.price  # 1.0 if active alerts, 0.0 otherwise
        alert_count = al.bid
        # al.source contains severity: "nws:alerts:FL:Severe"
        return has_alerts

    result = alert_signal(ctx)
    assert result == 1.0


run_test("Snippet 09: NWSFeed alerts + alert_signal", test_snippet_09_nws_alerts)


# =============================================================================
# Snippet 10: RESTJsonPathFeed construction
# docs/feeds.mdx lines 243-252
# =============================================================================
def test_snippet_10_rest_json_path():
    import horizon as hz

    feed = hz.RESTJsonPathFeed(
        url="https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
        price_path="bitcoin.usd",
    )
    assert feed.url == "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd"
    assert feed.price_path == "bitcoin.usd"
    assert feed._type == "rest_json_path"


run_test("Snippet 10: RESTJsonPathFeed", test_snippet_10_rest_json_path)


# =============================================================================
# Snippet 11: ChainlinkFeed construction + fair_value
# docs/feeds.mdx lines 278-291
# =============================================================================
def test_snippet_11_chainlink_feed():
    import horizon as hz
    from horizon.context import FeedData

    feed = hz.ChainlinkFeed(
        contract_address="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
        rpc_url="https://eth.llamarpc.com",
    )
    assert feed.contract_address == "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419"
    assert feed.rpc_url == "https://eth.llamarpc.com"
    assert feed.decimals == 8
    assert feed.interval == 10.0
    assert feed._type == "chainlink"

    # Verify fair_value pattern
    fd = FeedData(price=3245.12)
    ctx = hz.Context(feeds={"eth_usd": fd})

    def fair_value(ctx: hz.Context) -> float:
        return ctx.feeds["eth_usd"].price  # e.g., 3245.12

    assert fair_value(ctx) == 3245.12


run_test("Snippet 11: ChainlinkFeed + fair_value", test_snippet_11_chainlink_feed)


# =============================================================================
# Snippet 12: Feed Data in Context (ctx.feeds access pattern)
# docs/feeds.mdx lines 319-329
# =============================================================================
def test_snippet_12_feed_data_in_context():
    import horizon as hz
    from horizon.context import FeedData

    fd = FeedData(price=100250.0, bid=100248.0, ask=100252.0, timestamp=1700000000.0)
    ctx = hz.Context(feeds={"btc": fd})

    def fair_value(ctx: hz.Context) -> float:
        btc = ctx.feeds.get("btc", hz.context.FeedData())

        btc.price       # Last trade price
        btc.bid         # Best bid
        btc.ask         # Best ask
        btc.timestamp   # Unix timestamp of last update

        return btc.price

    result = fair_value(ctx)
    assert result == 100250.0

    # Also verify the default FeedData() fallback
    empty = hz.context.FeedData()
    assert empty.price == 0.0
    assert empty.bid == 0.0
    assert empty.ask == 0.0
    assert empty.timestamp == 0.0


run_test("Snippet 12: Feed Data in Context", test_snippet_12_feed_data_in_context)


# =============================================================================
# Snippet 13: FeedSnapshot at Engine level
# docs/feeds.mdx lines 335-344
# =============================================================================
def test_snippet_13_feed_snapshot_engine():
    import horizon as hz

    # Create an engine and verify FeedSnapshot API exists
    engine = hz.Engine(exchange="paper")

    # feed_snapshot should return None if feed doesn't exist,
    # or a FeedSnapshot. We just verify the method exists.
    snap = engine.feed_snapshot("btc")
    # snap may be None since no feed is running

    # Verify all_feed_snapshots exists
    all_snaps = engine.all_feed_snapshots()
    assert isinstance(all_snaps, dict)

    # Verify feed_age exists
    age = engine.feed_age("btc")
    # age is None if no feed data

    # Verify stop_feeds exists
    engine.stop_feeds()


run_test("Snippet 13: FeedSnapshot Engine level", test_snippet_13_feed_snapshot_engine)


# =============================================================================
# Snippet 14: Feed Staleness - hz.run params
# docs/feeds.mdx lines 359-364
# =============================================================================
def test_snippet_14_feed_staleness_params():
    """Verify the feed_stale_threshold param pattern is valid."""
    # We can't call hz.run() without a full strategy, but we can verify
    # that the params dict with feed_stale_threshold can be constructed.
    params = {"feed_stale_threshold": 60.0}
    assert params["feed_stale_threshold"] == 60.0


run_test("Snippet 14: Feed staleness params", test_snippet_14_feed_staleness_params)


# =============================================================================
# Snippet 15: FeedData.is_stale() method
# docs/feeds.mdx lines 370-374
# =============================================================================
def test_snippet_15_feed_is_stale():
    import horizon as hz
    from horizon.context import FeedData

    feed = FeedData()
    # Default timestamp=0.0 should be stale
    assert feed.is_stale(max_age_secs=30.0) is True

    # A feed with recent timestamp should not be stale
    import time
    feed_recent = FeedData(timestamp=time.time())
    assert feed_recent.is_stale(max_age_secs=30.0) is False

    # A feed with old timestamp should be stale
    feed_old = FeedData(timestamp=time.time() - 60.0)
    assert feed_old.is_stale(max_age_secs=30.0) is True

    # Verify the doc pattern works
    ctx = hz.Context(feeds={"btc": feed_recent})
    feed_check = ctx.feeds.get("btc", hz.context.FeedData())
    if feed_check.is_stale(max_age_secs=30.0):
        result = []  # skip quoting
    else:
        result = ["quote"]  # would return quotes
    assert result == ["quote"]


run_test("Snippet 15: FeedData.is_stale()", test_snippet_15_feed_is_stale)


# =============================================================================
# Snippet 16: Engine-level feed management (start_feed calls)
# docs/feeds.mdx lines 381-409
# =============================================================================
def test_snippet_16_engine_start_feed():
    import horizon as hz
    import json

    engine = hz.Engine(exchange="paper")

    # Original feeds - verify method signature works
    # These will start feeds that connect to live endpoints,
    # but we just verify the calls don't crash immediately.
    # We call start_feed and then immediately stop to avoid lingering connections.

    # Test: "binance_ws" feed type with symbol kwarg
    engine.start_feed("btc", "binance_ws", symbol="btcusdt")

    # Test: "polymarket_book" feed type with symbol kwarg
    engine.start_feed("poly", "polymarket_book", symbol="will-btc-hit-100k")

    # Test: "kalshi_book" feed type with symbol kwarg
    engine.start_feed("kalshi", "kalshi_book", symbol="KXBTC-25FEB16")

    # Test: "rest" feed type with url and interval kwargs
    engine.start_feed("custom", "rest", url="https://api.example.com/price", interval=5.0)

    # New feeds (v0.4.5) - use config_json
    engine.start_feed("pi", "predictit", config_json=json.dumps({
        "market_id": 7456, "contract_id": 28562
    }))
    engine.start_feed("mf", "manifold", config_json=json.dumps({
        "slug": "will-btc-hit-100k-by-2026"
    }))
    engine.start_feed("nba", "espn", config_json=json.dumps({
        "sport": "basketball", "league": "nba"
    }))
    engine.start_feed("wx", "nws", config_json=json.dumps({
        "mode": "alerts", "state": "FL"
    }))
    engine.start_feed("btc2", "rest_json_path", config_json=json.dumps({
        "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
        "price_path": "bitcoin.usd"
    }))
    engine.start_feed("eth_usd", "chainlink", config_json=json.dumps({
        "contract_address": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
        "rpc_url": "https://eth.llamarpc.com"
    }))

    # Clean up
    engine.stop_feeds()


run_test("Snippet 16: Engine start_feed calls", test_snippet_16_engine_start_feed)


# =============================================================================
# Snippet 17: Multiple feeds combined + fair_value
# docs/feeds.mdx lines 416-453
# =============================================================================
def test_snippet_17_multiple_feeds():
    import horizon as hz
    from horizon.context import FeedData

    # Verify all feed objects can be constructed in a dict
    feeds_config = {
        "btc": hz.BinanceWS("btcusdt"),
        "poly": hz.PolymarketBook("will-btc-hit-100k"),
        "kalshi": hz.KalshiBook("KXBTC-25FEB16"),
        "pi_election": hz.PredictItFeed(market_id=7456, contract_id=28562),
        "manifold": hz.ManifoldFeed("will-btc-hit-100k-by-2026"),
        "nba": hz.ESPNFeed("basketball", "nba"),
        "weather_fl": hz.NWSFeed(state="FL", mode="alerts"),
        "custom": hz.RESTJsonPathFeed(
            url="https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
            price_path="bitcoin.usd",
        ),
        "eth_usd": hz.ChainlinkFeed(
            contract_address="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419",
            rpc_url="https://eth.llamarpc.com",
        ),
    }
    assert len(feeds_config) == 9

    # Verify the multi-feed fair_value function
    btc_data = FeedData(price=100250.0)
    poly_data = FeedData(bid=0.55, ask=0.60)
    ctx = hz.Context(feeds={"btc": btc_data, "poly": poly_data})

    def fair_value(ctx: hz.Context) -> float:
        btc = ctx.feeds.get("btc", hz.context.FeedData())
        poly = ctx.feeds.get("poly", hz.context.FeedData())

        # Combine BTC spot price with Polymarket book
        if poly.bid > 0 and poly.ask > 0:
            return (poly.bid + poly.ask) / 2
        return btc.price * 0.01  # Fallback

    result = fair_value(ctx)
    assert abs(result - 0.575) < 1e-9

    # Test the fallback path (no poly data)
    ctx_no_poly = hz.Context(feeds={"btc": btc_data})

    def fair_value_fallback(ctx: hz.Context) -> float:
        btc = ctx.feeds.get("btc", hz.context.FeedData())
        poly = ctx.feeds.get("poly", hz.context.FeedData())
        if poly.bid > 0 and poly.ask > 0:
            return (poly.bid + poly.ask) / 2
        return btc.price * 0.01

    result_fallback = fair_value_fallback(ctx_no_poly)
    assert abs(result_fallback - 1002.50) < 1e-9


run_test("Snippet 17: Multiple feeds + fair_value", test_snippet_17_multiple_feeds)


# =============================================================================
# Print results
# =============================================================================
print("\n" + "=" * 72)
print("FEEDS DOCS SNIPPET TEST RESULTS")
print("=" * 72)

pass_count = 0
fail_count = 0

for name, status, tb in results:
    icon = "PASS" if status == "PASS" else "FAIL"
    print(f"\n[{icon}] {name}")
    if status == "FAIL":
        fail_count += 1
        # Print traceback indented
        for line in tb.strip().split("\n"):
            print(f"       {line}")
    else:
        pass_count += 1

print("\n" + "-" * 72)
print(f"Total: {len(results)} | Passed: {pass_count} | Failed: {fail_count}")
print("-" * 72)

if fail_count > 0:
    print("\nSome snippets FAILED. See details above.")
else:
    print("\nAll snippets PASSED.")
