"""Tests for Kelly criterion functions (Rust core)."""

import pytest
from horizon import (
    kelly,
    kelly_no,
    fractional_kelly,
    kelly_size,
    multi_kelly,
    liquidity_adjusted_kelly,
    edge,
)


class TestKelly:
    def test_kelly_with_edge(self):
        """f* = (0.60 - 0.50) / (1 - 0.50) = 0.20"""
        assert abs(kelly(0.60, 0.50) - 0.20) < 1e-10

    def test_kelly_high_edge(self):
        """f* = (0.90 - 0.50) / (1 - 0.50) = 0.80"""
        assert abs(kelly(0.90, 0.50) - 0.80) < 1e-10

    def test_kelly_no_edge(self):
        assert kelly(0.50, 0.50) == 0.0

    def test_kelly_negative_edge(self):
        assert kelly(0.40, 0.50) == 0.0

    def test_kelly_invalid_price_zero(self):
        assert kelly(0.60, 0.0) == 0.0

    def test_kelly_invalid_price_one(self):
        assert kelly(0.60, 1.0) == 0.0

    def test_kelly_invalid_price_negative(self):
        assert kelly(0.60, -0.1) == 0.0


class TestKellyNo:
    def test_kelly_no_with_edge(self):
        """f* = (0.50 - 0.40) / 0.50 = 0.20"""
        assert abs(kelly_no(0.40, 0.50) - 0.20) < 1e-10

    def test_kelly_no_no_edge(self):
        assert kelly_no(0.50, 0.50) == 0.0

    def test_kelly_no_wrong_direction(self):
        assert kelly_no(0.60, 0.50) == 0.0

    def test_kelly_no_extreme(self):
        """f* = (0.80 - 0.10) / 0.80 = 0.875"""
        assert abs(kelly_no(0.10, 0.80) - 0.875) < 1e-10


class TestFractionalKelly:
    def test_half_kelly(self):
        full = kelly(0.70, 0.50)
        half = fractional_kelly(0.70, 0.50, 0.5)
        assert abs(half - full * 0.5) < 1e-10

    def test_quarter_kelly(self):
        full = kelly(0.70, 0.50)
        quarter = fractional_kelly(0.70, 0.50, 0.25)
        assert abs(quarter - full * 0.25) < 1e-10

    def test_fraction_clamped_to_one(self):
        """Fraction > 1.0 is clamped to 1.0."""
        assert abs(fractional_kelly(0.70, 0.50, 1.5) - kelly(0.70, 0.50)) < 1e-10

    def test_fraction_clamped_to_zero(self):
        assert fractional_kelly(0.70, 0.50, -0.5) == 0.0


class TestKellySize:
    def test_basic_sizing(self):
        """prob=0.60, price=0.50, bankroll=1000, fraction=1.0, max=100.
        kelly=0.20, risk=200, contracts=200/0.50=400, capped at 100."""
        assert abs(kelly_size(0.60, 0.50, 1000.0, 1.0, 100.0) - 100.0) < 1e-10

    def test_uncapped(self):
        """prob=0.55, price=0.50, bankroll=100, fraction=0.5, max=1000.
        kelly=0.10, frac=0.05, risk=5, contracts=5/0.50=10."""
        assert abs(kelly_size(0.55, 0.50, 100.0, 0.5, 1000.0) - 10.0) < 1e-10

    def test_zero_bankroll(self):
        assert kelly_size(0.60, 0.50, 0.0, 1.0, 100.0) == 0.0

    def test_zero_price(self):
        assert kelly_size(0.60, 0.0, 1000.0, 1.0, 100.0) == 0.0

    def test_no_edge(self):
        assert kelly_size(0.50, 0.50, 1000.0, 1.0, 100.0) == 0.0


class TestMultiKelly:
    def test_independent_sizing(self):
        fracs = multi_kelly([0.55, 0.55], [0.50, 0.50], 1.0)
        assert len(fracs) == 2
        assert all(f > 0 for f in fracs)

    def test_scales_down_when_over_allocated(self):
        fracs = multi_kelly([0.90, 0.90], [0.50, 0.50], 1.0)
        assert len(fracs) == 2
        total = sum(fracs)
        assert total <= 1.0 + 1e-10

    def test_no_scaling_needed(self):
        fracs = multi_kelly([0.55, 0.55], [0.50, 0.50], 1.0)
        total = sum(fracs)
        # Each kelly = 0.10, total = 0.20 < 1.0
        assert total <= 1.0

    def test_empty_inputs(self):
        fracs = multi_kelly([], [], 1.0)
        assert fracs == []

    def test_mismatched_lengths(self):
        """Uses min of both lengths."""
        fracs = multi_kelly([0.60, 0.70, 0.80], [0.50, 0.50], 1.0)
        assert len(fracs) == 2


class TestLiquidityAdjustedKelly:
    def test_reduces_size(self):
        full = kelly_size(0.70, 0.50, 1000.0, 1.0, 1000.0)
        adjusted = liquidity_adjusted_kelly(0.70, 0.50, 1000.0, 1.0, 50.0, 1000.0)
        assert adjusted < full
        assert adjusted <= 50.0

    def test_no_reduction_with_abundant_liquidity(self):
        """With enough liquidity, size approaches raw Kelly size."""
        raw = kelly_size(0.55, 0.50, 100.0, 0.5, 1000.0)
        adjusted = liquidity_adjusted_kelly(0.55, 0.50, 100.0, 0.5, 10000.0, 1000.0)
        assert abs(adjusted - raw) < 1e-6

    def test_zero_liquidity(self):
        assert liquidity_adjusted_kelly(0.70, 0.50, 1000.0, 1.0, 0.0, 100.0) == 0.0


class TestEdge:
    def test_positive_edge(self):
        assert abs(edge(0.60, 0.50) - 0.10) < 1e-10

    def test_negative_edge(self):
        assert abs(edge(0.40, 0.50) - (-0.10)) < 1e-10

    def test_zero_edge(self):
        assert abs(edge(0.50, 0.50)) < 1e-10
