"""Test all Python code snippets from docs/examples batch 1.

Tests snippets from:
  1. simple-mm.mdx  (3 snippets)
  2. glft-mm.mdx    (1 snippet)
  3. kalshi-mm.mdx   (1 snippet)
  4. backtesting.mdx  (7 snippets)

For hz.run()-based strategies: verify imports, function defs, Market/Quote construction.
Do NOT actually call hz.run() (it blocks).
For hz.backtest()-based strategies: only verify structure, do NOT call hz.backtest().
"""

import sys
import traceback

results = []


def record(name, passed, detail=""):
    status = "PASS" if passed else "FAIL"
    results.append((name, status, detail))
    tag = f"[{status}]"
    msg = f"{tag} {name}"
    if detail:
        msg += f" -- {detail}"
    print(msg)


# ============================================================
# simple-mm.mdx  Snippet 1: Full Code (the 20-line strategy)
# ============================================================
def test_simple_mm_full():
    name = "simple-mm / Full Code"
    try:
        import horizon as hz

        # Define the pipeline functions exactly as in the doc
        def fair_value(ctx: hz.Context) -> float:
            """Simple fair value: just return 0.5 (coin flip)."""
            return 0.50

        def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
            """Quote around the fair value with a fixed spread."""
            return hz.quotes(fair, spread=0.06, size=5)

        # Verify hz.Risk works
        risk = hz.Risk(max_position=100, max_drawdown_pct=5)
        assert risk.max_position == 100
        assert risk.max_drawdown_pct == 5

        # Verify pipeline functions work
        ctx = hz.Context()
        fv = fair_value(ctx)
        assert fv == 0.50, f"Expected 0.50, got {fv}"
        quotes = quoter(ctx, fv)
        assert len(quotes) == 1
        q = quotes[0]
        assert abs(q.bid - 0.47) < 1e-9, f"Bid={q.bid}"
        assert abs(q.ask - 0.53) < 1e-9, f"Ask={q.ask}"

        # Verify hz.run is callable (don't invoke)
        assert callable(hz.run)

        record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# simple-mm.mdx  Snippet 2: Extending - inventory skew quoter
# ============================================================
def test_simple_mm_skew():
    name = "simple-mm / Extending (inventory skew)"
    try:
        import horizon as hz

        def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
            skew = ctx.inventory.net * 0.001
            return hz.quotes(fair - skew, spread=0.06, size=5)

        ctx = hz.Context()
        quotes = quoter(ctx, 0.50)
        assert len(quotes) == 1
        # inventory.net is 0 for default context, so skew = 0
        assert abs(quotes[0].bid - 0.47) < 1e-9
        assert abs(quotes[0].ask - 0.53) < 1e-9

        record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# simple-mm.mdx  Snippet 3: Feed-based fair value
# ============================================================
def test_simple_mm_feed():
    name = "simple-mm / Extending (feed-based fair value)"
    try:
        import horizon as hz

        def fair_value(ctx: hz.Context) -> float:
            btc = ctx.feeds.get("btc", hz.context.FeedData())
            return btc.price * 0.01 if btc.price > 0 else 0.50

        # Default FeedData has price=0, so should return 0.50
        ctx = hz.Context()
        fv = fair_value(ctx)
        assert fv == 0.50, f"Expected 0.50, got {fv}"

        # Verify BinanceWS feed config can be constructed
        feed = hz.BinanceWS("btcusdt")
        assert feed is not None

        record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# glft-mm.mdx  Snippet 1: Full Code (GLFT pipeline)
# ============================================================
def test_glft_mm_full():
    name = "glft-mm / Full Code"
    try:
        import math
        import horizon as hz

        def black_scholes_binary(price: float, strike: float, vol: float, tte: float) -> float:
            """Binary option fair value: Phi(d2)."""
            if tte <= 0:
                return 1.0 if price >= strike else 0.0
            d2 = (math.log(price / strike) - 0.5 * vol**2 * tte) / (vol * math.sqrt(tte))
            return 0.5 * (1 + math.erf(d2 / math.sqrt(2)))

        def spread_toxicity(bid: float, ask: float) -> float:
            """Spread-based toxicity proxy: wider spread = more toxic flow."""
            if bid <= 0 or ask <= 0:
                return 0.5
            mid = (bid + ask) / 2.0
            if mid <= 0:
                return 0.5
            relative_spread = (ask - bid) / mid
            return min(1.0, max(0.0, relative_spread * 100.0))

        def glft_spread(fair: float, tox: float, inventory: float, gamma: float = 0.1) -> float:
            """GLFT adverse selection spread with inventory penalty."""
            base = 0.02
            inv_penalty = gamma * abs(inventory) * 0.001
            tox_adj = tox * 0.04
            return base + inv_penalty + tox_adj

        # --- Pipeline functions ---
        def fair_value(ctx: hz.Context) -> float:
            btc_price = ctx.feeds.get("btc", hz.context.FeedData()).price or 100_000
            strike = 100_000
            vol = 0.6
            tte = 30 / 365
            return black_scholes_binary(btc_price, strike, vol, tte)

        def toxicity(ctx: hz.Context) -> float:
            feed = ctx.feeds.get("btc", hz.context.FeedData())
            return spread_toxicity(feed.bid, feed.ask)

        def quoter(ctx: hz.Context, fair: float, tox: float) -> list[hz.Quote]:
            spread = glft_spread(fair, tox, ctx.inventory.net, gamma=0.1)
            return hz.quotes(fair, spread, size=5)

        # Test the pipeline
        ctx = hz.Context()
        fv = fair_value(ctx)
        assert 0.0 <= fv <= 1.0, f"Fair value out of range: {fv}"
        tox = toxicity(ctx)
        assert 0.0 <= tox <= 1.0, f"Toxicity out of range: {tox}"
        quotes = quoter(ctx, fv, tox)
        assert len(quotes) == 1
        assert quotes[0].bid < quotes[0].ask

        # Verify Polymarket exchange constructor exists
        assert hasattr(hz, 'Polymarket')
        # Verify BinanceWS feed config
        feed = hz.BinanceWS("btcusdt")
        assert feed is not None

        record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# kalshi-mm.mdx  Snippet 1: Full Code
# ============================================================
def test_kalshi_mm_full():
    name = "kalshi-mm / Full Code"
    try:
        import math
        import horizon as hz

        def fair_value(ctx: hz.Context) -> float:
            btc_price = ctx.feeds.get("btc", hz.context.FeedData()).price or 100_000
            strike = 100_000
            vol = 0.6
            tte = 1 / 365
            if tte <= 0:
                return 1.0 if btc_price >= strike else 0.0
            d2 = (math.log(btc_price / strike) - 0.5 * vol**2 * tte) / (vol * math.sqrt(tte))
            return 0.5 * (1 + math.erf(d2 / math.sqrt(2)))

        def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
            spread = 0.04
            return hz.quotes(fair, spread, size=10)

        # Test the pipeline
        ctx = hz.Context()
        fv = fair_value(ctx)
        assert 0.0 <= fv <= 1.0, f"Fair value out of range: {fv}"
        quotes = quoter(ctx, fv)
        assert len(quotes) == 1
        assert quotes[0].bid < quotes[0].ask

        # Verify Kalshi exchange constructor exists
        assert hasattr(hz, 'Kalshi')
        # Verify hz.Risk with single kwarg
        risk = hz.Risk(max_position=50)
        assert risk.max_position == 50

        record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# backtesting.mdx  Snippet 1: Full Code (main backtest example)
# ============================================================
def test_backtest_full():
    name = "backtesting / Full Code"
    try:
        import horizon as hz

        def fair_value(ctx: hz.Context) -> float:
            feed = ctx.feeds.get("default", hz.Context.FeedData())
            return feed.price if feed.price > 0 else 0.50

        def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
            skew = ctx.inventory.net * 0.002
            return hz.quotes(fair - skew, spread=0.06, size=5)

        # Test: hz.Context.FeedData() -- is this valid?
        # The docs use hz.Context.FeedData() but FeedData is in hz.context
        try:
            fd = hz.Context.FeedData()
            feeddata_on_context = True
        except AttributeError:
            feeddata_on_context = False

        # Test pipeline
        ctx = hz.Context()
        # Use the correct FeedData path for testing the function logic
        if feeddata_on_context:
            fv = fair_value(ctx)
        else:
            # The doc code uses hz.Context.FeedData() which may not exist
            # Record this as a finding
            pass

        # Generate sample data as in the doc
        data = [
            {"timestamp": float(i), "price": 0.50 + 0.05 * ((-1) ** i) * (i % 10) / 10}
            for i in range(500)
        ]
        assert len(data) == 500
        assert "timestamp" in data[0]
        assert "price" in data[0]

        # Verify backtest function exists and is callable
        assert callable(hz.backtest)

        # Verify Risk
        risk = hz.Risk(max_position=50, max_drawdown_pct=10)
        assert risk.max_position == 50

        if not feeddata_on_context:
            record(name, False, "hz.Context.FeedData() does not exist; docs use hz.Context.FeedData() but should be hz.context.FeedData()")
        else:
            record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# backtesting.mdx  Snippet 2: Data Formats - list[dict]
# ============================================================
def test_backtest_data_list_dict():
    name = "backtesting / Data Formats (list[dict])"
    try:
        data = [
            {"timestamp": 0.0, "price": 0.50},
            {"timestamp": 1.0, "price": 0.52},
            {"timestamp": 2.0, "price": 0.48, "bid": 0.47, "ask": 0.49},
        ]
        assert len(data) == 3
        for d in data:
            assert "timestamp" in d
            assert "price" in d or "bid" in d
        record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# backtesting.mdx  Snippet 3: CSV file path
# ============================================================
def test_backtest_csv_path():
    name = "backtesting / CSV file path"
    try:
        import horizon as hz
        # Just verify the call signature is valid (don't actually run)
        # The doc shows: hz.backtest(data="data/btc_market.csv", pipeline=[...], risk=...)
        assert callable(hz.backtest)
        risk = hz.Risk(max_position=50)
        assert risk is not None
        record(name, True, "Snippet is just a call signature demo; verified callable + Risk")
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# backtesting.mdx  Snippet 4: pandas DataFrame
# ============================================================
def test_backtest_pandas():
    name = "backtesting / pandas DataFrame"
    try:
        import horizon as hz
        # Just verify the call signature is valid
        assert callable(hz.backtest)
        risk = hz.Risk(max_position=50)
        assert risk is not None
        record(name, True, "Snippet is just a call signature demo; verified callable + Risk")
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# backtesting.mdx  Snippet 5: Multi-feed backtest
# ============================================================
def test_backtest_multi_feed():
    name = "backtesting / Multi-Feed Backtesting"
    try:
        import horizon as hz

        def fair_value(ctx: hz.Context) -> float:
            btc = ctx.feeds.get("btc", hz.Context.FeedData())
            if btc.price > 100_000:
                return 0.70
            elif btc.price > 95_000:
                return 0.50
            else:
                return 0.30

        def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
            book = ctx.feeds.get("book", hz.Context.FeedData())
            spread_est = (book.ask - book.bid) if book.bid > 0 else 0.06
            spread = max(0.04, spread_est * 1.2)
            skew = ctx.inventory.net * 0.001
            return hz.quotes(fair - skew, spread=spread, size=5)

        # Test: hz.Context.FeedData()
        try:
            fd = hz.Context.FeedData()
            feeddata_on_context = True
        except AttributeError:
            feeddata_on_context = False

        if not feeddata_on_context:
            record(name, False, "hz.Context.FeedData() does not exist; docs use hz.Context.FeedData() but should be hz.context.FeedData()")
        else:
            ctx = hz.Context()
            fv = fair_value(ctx)
            assert fv == 0.30  # default price=0 < 95000
            quotes = quoter(ctx, fv)
            assert len(quotes) == 1
            record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# backtesting.mdx  Snippet 6: CSV Input Example
# ============================================================
def test_backtest_csv_input():
    name = "backtesting / CSV Input Example"
    try:
        import horizon as hz

        def fair_value(ctx: hz.Context) -> float:
            feed = ctx.feeds.get("default", hz.Context.FeedData())
            if feed.bid > 0 and feed.ask > 0:
                return (feed.bid + feed.ask) / 2.0
            return feed.price if feed.price > 0 else 0.50

        def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
            return hz.quotes(fair, spread=0.04, size=10)

        try:
            fd = hz.Context.FeedData()
            feeddata_on_context = True
        except AttributeError:
            feeddata_on_context = False

        if not feeddata_on_context:
            record(name, False, "hz.Context.FeedData() does not exist; docs use hz.Context.FeedData() but should be hz.context.FeedData()")
        else:
            ctx = hz.Context()
            fv = fair_value(ctx)
            assert fv == 0.50  # defaults
            quotes = quoter(ctx, fv)
            assert len(quotes) == 1
            record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# backtesting.mdx  Snippet 7: With Outcomes for Brier Score
# ============================================================
def test_backtest_outcomes():
    name = "backtesting / With Outcomes (Brier Score)"
    try:
        import horizon as hz

        def fair_value(ctx: hz.Context) -> float:
            feed = ctx.feeds.get("default", hz.Context.FeedData())
            return feed.price if feed.price > 0 else 0.50

        def quoter(ctx: hz.Context, fair: float) -> list[hz.Quote]:
            skew = ctx.inventory.net * 0.002
            return hz.quotes(fair - skew, spread=0.06, size=5)

        try:
            fd = hz.Context.FeedData()
            feeddata_on_context = True
        except AttributeError:
            feeddata_on_context = False

        if not feeddata_on_context:
            record(name, False, "hz.Context.FeedData() does not exist; docs use hz.Context.FeedData() but should be hz.context.FeedData()")
        else:
            ctx = hz.Context()
            fv = fair_value(ctx)
            assert fv == 0.50
            quotes = quoter(ctx, fv)
            assert len(quotes) == 1

            # Verify backtest signature accepts 'outcomes'
            import inspect
            sig = inspect.signature(hz.backtest)
            assert 'outcomes' in sig.parameters, "hz.backtest() missing 'outcomes' parameter"
            record(name, True)
    except Exception as e:
        record(name, False, f"{e}\n{traceback.format_exc()}")


# ============================================================
# Run all tests
# ============================================================
if __name__ == "__main__":
    print("=" * 70)
    print("Testing doc example snippets (batch 1)")
    print("=" * 70)
    print()

    test_simple_mm_full()
    test_simple_mm_skew()
    test_simple_mm_feed()
    test_glft_mm_full()
    test_kalshi_mm_full()
    test_backtest_full()
    test_backtest_data_list_dict()
    test_backtest_csv_path()
    test_backtest_pandas()
    test_backtest_multi_feed()
    test_backtest_csv_input()
    test_backtest_outcomes()

    print()
    print("=" * 70)
    print("SUMMARY")
    print("=" * 70)
    passed = sum(1 for _, s, _ in results if s == "PASS")
    failed = sum(1 for _, s, _ in results if s == "FAIL")
    total = len(results)
    for name, status, detail in results:
        line = f"  {status}  {name}"
        if detail and status == "FAIL":
            line += f"  -->  {detail.splitlines()[0]}"
        print(line)
    print()
    print(f"Total: {total}  |  Passed: {passed}  |  Failed: {failed}")
    if failed > 0:
        sys.exit(1)
