"""Test all Python code snippets from docs/quant.mdx.

Each snippet is wrapped in try/except and prints PASS/FAIL.
Snippets requiring network access, real exchanges, or hz.run() are skipped.
Synthetic data is created where needed.
"""

import random
import math

import horizon as hz

passed = 0
failed = 0
skipped = 0
results = []


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


# ============================================================================
# Generate synthetic data used across multiple snippets
# ============================================================================
random.seed(42)
# Price series: random walk starting at 0.50
price_series = [0.50]
for _ in range(199):
    price_series.append(max(0.01, min(0.99, price_series[-1] + random.gauss(0, 0.01))))

# Returns from price series
returns = [(price_series[i] - price_series[i - 1]) / price_series[i - 1]
           for i in range(1, len(price_series))]

# Predictions and outcomes (correlated with noise)
predictions = [0.5 + random.gauss(0, 0.1) for _ in range(100)]
outcomes = [p + random.gauss(0, 0.05) for p in predictions]

# Volumes
volumes = [random.uniform(100, 10000) for _ in range(len(returns))]

# Signed volumes (positive for positive returns, negative for negative)
signed_volumes = [
    (1.0 if r >= 0 else -1.0) * abs(v)
    for r, v in zip(returns, volumes)
]

# Source and target series for transfer entropy
source_series = [random.gauss(0, 1) for _ in range(100)]
target_series = [0.5 * source_series[i] + random.gauss(0, 0.5) for i in range(100)]

# Signal values for half-life
signal_values = [random.gauss(0, 1) for _ in range(100)]

# Returns series for CPCV
returns_series = [random.gauss(0.001, 0.02) for _ in range(200)]

print("=" * 70)
print("docs/quant.mdx -- Code Snippet Tests")
print("=" * 70)
print()

# ============================================================================
# Snippet 1: Information Theory
# ============================================================================
print("--- Information Theory ---")

try:
    h = hz.shannon_entropy(0.5)  # 1.0 bits
    assert isinstance(h, float), f"Expected float, got {type(h)}"
    assert abs(h - 1.0) < 0.01, f"Expected ~1.0, got {h}"
    record("shannon_entropy(0.5)", "PASS")
except Exception as e:
    record("shannon_entropy(0.5)", "FAIL", str(e))

try:
    h = hz.joint_entropy([0.25, 0.25, 0.25, 0.25])  # 2.0 bits
    assert isinstance(h, float), f"Expected float, got {type(h)}"
    assert abs(h - 2.0) < 0.01, f"Expected ~2.0, got {h}"
    record("joint_entropy([0.25]*4)", "PASS")
except Exception as e:
    record("joint_entropy([0.25]*4)", "FAIL", str(e))

try:
    kl = hz.kl_divergence([0.9, 0.1], [0.5, 0.5])
    assert isinstance(kl, float), f"Expected float, got {type(kl)}"
    assert kl > 0, f"KL divergence should be positive, got {kl}"
    record("kl_divergence", "PASS")
except Exception as e:
    record("kl_divergence", "FAIL", str(e))

try:
    mi = hz.mutual_information(predictions, outcomes, n_bins=10)
    assert isinstance(mi, float), f"Expected float, got {type(mi)}"
    assert mi >= 0, f"MI should be non-negative, got {mi}"
    record("mutual_information", "PASS")
except Exception as e:
    record("mutual_information", "FAIL", str(e))

try:
    te = hz.transfer_entropy(source_series, target_series, lag=1, n_bins=10)
    assert isinstance(te, float), f"Expected float, got {type(te)}"
    record("transfer_entropy", "PASS")
except Exception as e:
    record("transfer_entropy", "FAIL", str(e))

# ============================================================================
# Snippet 2: Microstructure
# ============================================================================
print()
print("--- Microstructure ---")

# price_changes for Kyle's lambda: use returns (which are price changes / price)
price_changes = returns[:50]
signed_vols_subset = signed_volumes[:50]

try:
    lam = hz.kyles_lambda(price_changes, signed_vols_subset)
    assert isinstance(lam, float), f"Expected float, got {type(lam)}"
    record("kyles_lambda", "PASS")
except Exception as e:
    record("kyles_lambda", "FAIL", str(e))

try:
    illiq = hz.amihud_ratio(returns[:50], volumes[:50])
    assert isinstance(illiq, float), f"Expected float, got {type(illiq)}"
    record("amihud_ratio", "PASS")
except Exception as e:
    record("amihud_ratio", "FAIL", str(e))

try:
    spread = hz.roll_spread(returns[:50])
    assert isinstance(spread, float), f"Expected float, got {type(spread)}"
    record("roll_spread", "PASS")
except Exception as e:
    record("roll_spread", "FAIL", str(e))

try:
    eff = hz.effective_spread(fill_price=0.52, mid=0.50, is_buy=True)
    assert isinstance(eff, float), f"Expected float, got {type(eff)}"
    assert eff > 0, f"Effective spread should be positive for this case, got {eff}"
    record("effective_spread", "PASS")
except Exception as e:
    record("effective_spread", "FAIL", str(e))

try:
    real = hz.realized_spread(fill_price=0.52, mid_after=0.51, is_buy=True)
    assert isinstance(real, float), f"Expected float, got {type(real)}"
    record("realized_spread", "PASS")
except Exception as e:
    record("realized_spread", "FAIL", str(e))

try:
    imb = hz.lob_imbalance(bids=[(0.50, 100)], asks=[(0.51, 80)], levels=5)
    assert isinstance(imb, float), f"Expected float, got {type(imb)}"
    record("lob_imbalance", "PASS")
except Exception as e:
    record("lob_imbalance", "FAIL", str(e))

try:
    mid = hz.weighted_mid(bids=[(0.50, 200)], asks=[(0.52, 100)])
    assert isinstance(mid, float), f"Expected float, got {type(mid)}"
    # Weighted mid should be between 0.50 and 0.52
    assert 0.50 <= mid <= 0.52, f"Weighted mid out of range: {mid}"
    record("weighted_mid", "PASS")
except Exception as e:
    record("weighted_mid", "FAIL", str(e))

# ============================================================================
# Snippet 3: Risk Analytics -- Cornish-Fisher VaR/CVaR
# ============================================================================
print()
print("--- Risk Analytics: Cornish-Fisher VaR/CVaR ---")

try:
    var = hz.cornish_fisher_var(returns, confidence=0.95)
    assert isinstance(var, float), f"Expected float, got {type(var)}"
    record("cornish_fisher_var", "PASS")
except Exception as e:
    record("cornish_fisher_var", "FAIL", str(e))

try:
    cvar = hz.cornish_fisher_cvar(returns, confidence=0.95)
    assert isinstance(cvar, float), f"Expected float, got {type(cvar)}"
    record("cornish_fisher_cvar", "PASS")
except Exception as e:
    record("cornish_fisher_cvar", "FAIL", str(e))

# ============================================================================
# Snippet 4: Prediction Greeks
# ============================================================================
print()
print("--- Prediction Greeks ---")

try:
    greeks = hz.prediction_greeks(
        price=0.65,
        size=100.0,
        is_yes=True,
        t_hours=24.0,
        vol=0.2,
    )
    assert hasattr(greeks, "delta"), "PredictionGreeks missing 'delta'"
    assert hasattr(greeks, "gamma"), "PredictionGreeks missing 'gamma'"
    assert hasattr(greeks, "theta"), "PredictionGreeks missing 'theta'"
    assert hasattr(greeks, "vega"), "PredictionGreeks missing 'vega'"
    # Access all four as shown in the docs
    _ = greeks.delta, greeks.gamma, greeks.theta, greeks.vega
    record("prediction_greeks", "PASS")
except Exception as e:
    record("prediction_greeks", "FAIL", str(e))

# ============================================================================
# Snippet 5: Signal Analysis
# ============================================================================
print()
print("--- Signal Analysis ---")

try:
    ic = hz.information_coefficient(predictions, outcomes)
    assert isinstance(ic, float), f"Expected float, got {type(ic)}"
    record("information_coefficient", "PASS")
except Exception as e:
    record("information_coefficient", "FAIL", str(e))

try:
    hl = hz.signal_half_life(signal_values)
    assert isinstance(hl, float), f"Expected float, got {type(hl)}"
    record("signal_half_life", "PASS")
except Exception as e:
    record("signal_half_life", "FAIL", str(e))

try:
    h = hz.hurst_exponent(price_series)
    assert isinstance(h, float), f"Expected float, got {type(h)}"
    record("hurst_exponent", "PASS")
except Exception as e:
    record("hurst_exponent", "FAIL", str(e))

try:
    vr = hz.variance_ratio(returns, period=2)
    assert isinstance(vr, float), f"Expected float, got {type(vr)}"
    record("variance_ratio", "PASS")
except Exception as e:
    record("variance_ratio", "FAIL", str(e))

# ============================================================================
# Snippet 6: Statistical Testing -- Deflated Sharpe
# ============================================================================
print()
print("--- Statistical Testing ---")

try:
    p_value = hz.deflated_sharpe(
        sharpe=2.1,
        n_obs=500,
        n_trials=50,
        skew=0.1,
        kurt=3.5,
    )
    assert isinstance(p_value, float), f"Expected float, got {type(p_value)}"
    assert 0 <= p_value <= 1, f"p_value should be in [0,1], got {p_value}"
    record("deflated_sharpe", "PASS")
except Exception as e:
    record("deflated_sharpe", "FAIL", str(e))

try:
    threshold = hz.bonferroni_threshold(alpha=0.05, n_trials=50)
    assert isinstance(threshold, float), f"Expected float, got {type(threshold)}"
    assert abs(threshold - 0.001) < 0.0001, f"Expected 0.001, got {threshold}"
    record("bonferroni_threshold", "PASS")
except Exception as e:
    record("bonferroni_threshold", "FAIL", str(e))

try:
    rejected = hz.benjamini_hochberg([0.01, 0.03, 0.5], alpha=0.05)
    assert isinstance(rejected, list), f"Expected list, got {type(rejected)}"
    assert rejected == [True, True, False], f"Expected [True, True, False], got {rejected}"
    record("benjamini_hochberg", "PASS")
except Exception as e:
    record("benjamini_hochberg", "FAIL", str(e))

# ============================================================================
# Snippet 7: Streaming Detectors -- VPIN
# ============================================================================
print()
print("--- Streaming Detectors ---")

try:
    vpin = hz.VpinDetector(bucket_volume=1000.0, n_buckets=50)
    toxicity = vpin.update(0.65, 50.0, True)
    cur = vpin.current_vpin()
    assert isinstance(toxicity, float), f"Expected float, got {type(toxicity)}"
    assert isinstance(cur, float), f"Expected float, got {type(cur)}"
    record("VpinDetector", "PASS")
except Exception as e:
    record("VpinDetector", "FAIL", str(e))

# ============================================================================
# Snippet 8: CUSUM Change-Point Detector
# ============================================================================

try:
    cusum = hz.CusumDetector(threshold=5.0, drift=0.0)
    is_break = cusum.update(value=2.5)
    assert isinstance(is_break, bool), f"Expected bool, got {type(is_break)}"
    u = cusum.upper()
    lo = cusum.lower()
    assert isinstance(u, float), f"Expected float for upper, got {type(u)}"
    assert isinstance(lo, float), f"Expected float for lower, got {type(lo)}"
    record("CusumDetector", "PASS")
except Exception as e:
    record("CusumDetector", "FAIL", str(e))

# ============================================================================
# Snippet 9: OFI Tracker
# ============================================================================

try:
    ofi = hz.OfiTracker()
    delta = ofi.update(best_bid_qty=150.0, best_ask_qty=100.0)
    assert isinstance(delta, float), f"Expected float, got {type(delta)}"
    cum = ofi.cumulative()
    assert isinstance(cum, float), f"Expected float, got {type(cum)}"
    record("OfiTracker", "PASS")
except Exception as e:
    record("OfiTracker", "FAIL", str(e))

# ============================================================================
# Snippet 10: Pipeline -- toxic_flow (SKIP: requires hz.run())
# ============================================================================
print()
print("--- Pipeline Functions (hz.run() based -- SKIPPED) ---")
record("toxic_flow pipeline (hz.run)", "SKIP", "requires hz.run()")
record("microstructure pipeline (hz.run)", "SKIP", "requires hz.run()")
record("change_detector pipeline (hz.run)", "SKIP", "requires hz.run()")

# ============================================================================
# Snippet 11: Offline -- strategy_significance
# ============================================================================
print()
print("--- Offline Analysis ---")

try:
    equity_values = [1000.0, 1005.0, 1002.0, 1010.0, 1008.0, 1015.0]
    result = hz.strategy_significance(
        result={"equity_curve": equity_values},
        n_trials=50,
        alpha=0.05,
    )
    assert isinstance(result, dict), f"Expected dict, got {type(result)}"
    assert "deflated_sharpe_pvalue" in result, "Missing 'deflated_sharpe_pvalue'"
    assert "is_significant" in result, "Missing 'is_significant'"
    # The docs use a 6-element equity curve (5 returns), which is <10
    # so the function returns defaults. Check it doesn't crash.
    record("strategy_significance (short equity)", "PASS")
except Exception as e:
    record("strategy_significance (short equity)", "FAIL", str(e))

# Also test with sufficient data
try:
    long_equity = [1000.0]
    for i in range(100):
        long_equity.append(long_equity[-1] * (1 + random.gauss(0.002, 0.01)))
    result = hz.strategy_significance(
        result={"equity_curve": long_equity},
        n_trials=50,
        alpha=0.05,
    )
    assert isinstance(result["deflated_sharpe_pvalue"], float)
    assert isinstance(result["is_significant"], bool)
    record("strategy_significance (long equity)", "PASS")
except Exception as e:
    record("strategy_significance (long equity)", "FAIL", str(e))

# ============================================================================
# Snippet 12: signal_diagnostics
# ============================================================================

try:
    diag = hz.signal_diagnostics(predictions, outcomes)
    assert isinstance(diag, dict), f"Expected dict, got {type(diag)}"
    assert "ic" in diag, "Missing 'ic'"
    assert "half_life" in diag, "Missing 'half_life'"
    assert "hurst" in diag, "Missing 'hurst'"
    record("signal_diagnostics", "PASS")
except Exception as e:
    record("signal_diagnostics", "FAIL", str(e))

# ============================================================================
# Snippet 13: market_efficiency
# ============================================================================

try:
    eff = hz.market_efficiency(price_series)
    assert isinstance(eff, dict), f"Expected dict, got {type(eff)}"
    assert "variance_ratio" in eff, "Missing 'variance_ratio'"
    assert "hurst" in eff, "Missing 'hurst'"
    assert "is_efficient" in eff, "Missing 'is_efficient'"
    record("market_efficiency", "PASS")
except Exception as e:
    record("market_efficiency", "FAIL", str(e))

# ============================================================================
# Snippet 14: Stress Testing
# ============================================================================
print()
print("--- Stress Testing ---")

try:
    from horizon import SimPosition, stress_test, CORRELATION_SPIKE, ALL_RESOLVE_NO

    positions = [
        SimPosition("mkt1", "yes", 100.0, 0.50, 0.60),
        SimPosition("mkt2", "yes", 50.0, 0.40, 0.55),
    ]

    result = hz.stress_test(positions, n_simulations=10000, seed=42)
    assert hasattr(result, "worst_scenario"), "Missing 'worst_scenario'"
    assert hasattr(result, "worst_pnl"), "Missing 'worst_pnl'"
    assert isinstance(result.worst_scenario, str)
    assert isinstance(result.worst_pnl, float)
    summary = result.summary()
    assert isinstance(summary, dict)
    record("stress_test (default scenarios)", "PASS")
except Exception as e:
    record("stress_test (default scenarios)", "FAIL", str(e))

# ============================================================================
# Snippet 15: Custom Stress Scenarios
# ============================================================================

try:
    from horizon import StressScenario

    crash = StressScenario(
        name="election_shock",
        correlation_override=0.99,
        price_shocks={"mkt1": 0.05, "mkt2": 0.10},
    )
    result = hz.stress_test(positions, scenarios=[crash])
    assert result.worst_scenario == "election_shock"
    record("stress_test (custom scenario)", "PASS")
except Exception as e:
    record("stress_test (custom scenario)", "FAIL", str(e))

# ============================================================================
# Snippet 16: CPCV
# ============================================================================
print()
print("--- CPCV ---")

try:
    from horizon import cpcv, probability_of_overfitting

    def pipeline_factory(params):
        multiplier = params["multiplier"]
        def strategy(returns):
            adjusted = [r * multiplier for r in returns]
            mean = sum(adjusted) / len(adjusted)
            std = (sum((r - mean) ** 2 for r in adjusted) / len(adjusted)) ** 0.5
            return mean / std if std > 0 else 0.0
        return strategy

    result = cpcv(
        data=returns_series,
        pipeline_factory=pipeline_factory,
        param_grid=[{"multiplier": m} for m in [0.5, 1.0, 1.5, 2.0]],
        n_groups=8,
        purge_gap=0.02,
    )

    assert hasattr(result, "pbo"), "Missing 'pbo'"
    assert hasattr(result, "is_overfit"), "Missing 'is_overfit'"
    assert hasattr(result, "oos_sharpes"), "Missing 'oos_sharpes'"
    assert hasattr(result, "n_combinations"), "Missing 'n_combinations'"
    # Verify these print without error (as shown in the docs)
    _ = f"PBO: {result.pbo:.2%}"
    _ = f"Overfit: {result.is_overfit}"
    _ = f"OOS Sharpes: {result.oos_sharpes}"
    _ = f"Combinations tested: {result.n_combinations}"
    record("cpcv", "PASS")
except Exception as e:
    record("cpcv", "FAIL", str(e))

try:
    pbo = probability_of_overfitting(result)
    assert isinstance(pbo, float)
    record("probability_of_overfitting", "PASS")
except Exception as e:
    record("probability_of_overfitting", "FAIL", str(e))

# ============================================================================
# Snippet 17: Hawkes Process -- Rust API
# ============================================================================
print()
print("--- Hawkes Process ---")

try:
    from horizon import HawkesProcess

    hp = HawkesProcess(mu=0.1, alpha=0.5, beta=1.0)
    hp.add_event(1000.0)
    hp.add_event(1000.5)

    intensity = hp.intensity(1001.0)
    assert isinstance(intensity, float), f"Expected float, got {type(intensity)}"
    assert intensity > 0, f"Intensity should be > 0 (baseline mu=0.1), got {intensity}"

    branching = hp.branching_ratio()
    assert isinstance(branching, float), f"Expected float, got {type(branching)}"
    assert abs(branching - 0.5) < 0.01, f"Expected branching ratio ~0.5, got {branching}"

    expected = hp.expected_events(1001.0, 60.0)
    assert isinstance(expected, float), f"Expected float, got {type(expected)}"
    assert expected > 0, f"Expected events should be > 0, got {expected}"

    hp.reset()
    record("HawkesProcess", "PASS")
except Exception as e:
    record("HawkesProcess", "FAIL", str(e))

# ============================================================================
# Snippet 18: Hawkes Pipeline (SKIP: requires hz.run())
# ============================================================================
record("hawkes_intensity pipeline (hz.run)", "SKIP", "requires hz.run()")

# ============================================================================
# Snippet 19: Ledoit-Wolf Shrinkage -- Rust API
# ============================================================================
print()
print("--- Ledoit-Wolf Shrinkage ---")

try:
    from horizon import ledoit_wolf_shrinkage

    returns_lw = [[0.01, 0.02], [-0.01, 0.03], [0.02, -0.01]]
    matrix, shrinkage = ledoit_wolf_shrinkage(returns_lw)
    assert isinstance(matrix, list), f"Expected list, got {type(matrix)}"
    assert isinstance(shrinkage, float), f"Expected float, got {type(shrinkage)}"
    assert len(matrix) == 2, f"Expected 2x2 matrix, got {len(matrix)} rows"
    assert len(matrix[0]) == 2, f"Expected 2x2 matrix, got {len(matrix[0])} cols"
    assert 0 <= shrinkage <= 1, f"Shrinkage should be in [0,1], got {shrinkage}"
    record("ledoit_wolf_shrinkage", "PASS")
except Exception as e:
    record("ledoit_wolf_shrinkage", "FAIL", str(e))

# ============================================================================
# Snippet 20: Correlation Estimator Pipeline (SKIP: requires hz.run())
# ============================================================================
record("correlation_estimator pipeline (hz.run)", "SKIP", "requires hz.run()")

# ============================================================================
# Verify pipeline function constructors work (even though we skip hz.run())
# ============================================================================
print()
print("--- Pipeline Function Constructors (no hz.run, just instantiation) ---")

try:
    fn = hz.toxic_flow("book", bucket_volume=500, threshold=0.7)
    assert callable(fn), "toxic_flow should return a callable"
    record("toxic_flow() constructor", "PASS")
except Exception as e:
    record("toxic_flow() constructor", "FAIL", str(e))

try:
    fn = hz.microstructure("book", lookback=100)
    assert callable(fn), "microstructure should return a callable"
    record("microstructure() constructor", "PASS")
except Exception as e:
    record("microstructure() constructor", "FAIL", str(e))

try:
    fn = hz.change_detector(threshold=5.0)
    assert callable(fn), "change_detector should return a callable"
    record("change_detector() constructor", "PASS")
except Exception as e:
    record("change_detector() constructor", "FAIL", str(e))

try:
    fn = hz.hawkes_intensity(feed_name="book", mu=0.1, alpha=0.5, beta=1.0)
    assert callable(fn), "hawkes_intensity should return a callable"
    record("hawkes_intensity() constructor", "PASS")
except Exception as e:
    record("hawkes_intensity() constructor", "FAIL", str(e))

try:
    fn = hz.correlation_estimator(feed_names=["btc", "eth"], window=100)
    assert callable(fn), "correlation_estimator should return a callable"
    record("correlation_estimator() constructor", "PASS")
except Exception as e:
    record("correlation_estimator() constructor", "FAIL", str(e))

# ============================================================================
# Summary
# ============================================================================
print()
print("=" * 70)
print(f"SUMMARY: {passed} passed, {failed} failed, {skipped} skipped "
      f"(total: {passed + failed + skipped})")
print("=" * 70)

if failed > 0:
    print()
    print("FAILED tests:")
    for name, status, detail in results:
        if status == "FAIL":
            print(f"  - {name}: {detail}")
