"""Tests for logit-space pricing framework (Rust)."""

import math
import pytest
import horizon as hz


class TestLogitSigmoid:
    def test_inverse_property(self):
        for p in [0.1, 0.25, 0.5, 0.75, 0.9]:
            x = hz.logit(p)
            recovered = hz.sigmoid(x)
            assert abs(recovered - p) < 1e-10, f"Failed for p={p}"

    def test_known_values(self):
        assert abs(hz.logit(0.5)) < 1e-10
        assert abs(hz.sigmoid(0.0) - 0.5) < 1e-10

    def test_boundary_safety(self):
        # Should not crash near 0 or 1
        assert hz.logit(0.001) < 0
        assert hz.logit(0.999) > 0
        assert 0.0 < hz.sigmoid(-100) < 0.01
        assert 0.99 < hz.sigmoid(100) <= 1.0

    def test_sigmoid_prime(self):
        # S'(0) = 0.5 * 0.5 = 0.25
        assert abs(hz.sigmoid_prime(0.0) - 0.25) < 1e-10

    def test_sigmoid_double_prime(self):
        # S''(0) = 0.5 * 0.5 * (1 - 2*0.5) = 0
        assert abs(hz.sigmoid_double_prime(0.0)) < 1e-10


class TestLogitGreeks:
    def test_delta_sign_yes(self):
        g = hz.logit_greeks(0.5, 10.0, True)
        assert g.delta > 0.0

    def test_delta_sign_no(self):
        g = hz.logit_greeks(0.5, 10.0, False)
        assert g.delta < 0.0

    def test_gamma_maximal_at_half(self):
        # At p=0.5, gamma should be zero because (1-2p) = 0
        g = hz.logit_greeks(0.5, 10.0, True)
        assert abs(g.gamma) < 1e-10

    def test_gamma_nonzero_away_from_half(self):
        g = hz.logit_greeks(0.3, 10.0, True)
        assert g.gamma != 0.0

    def test_vega_positive(self):
        g = hz.logit_greeks(0.5, 10.0, True, belief_vol=0.2)
        assert g.belief_vega > 0.0

    def test_theta_negative_for_yes(self):
        g = hz.logit_greeks(0.5, 10.0, True, t_hours=24.0, belief_vol=0.2)
        assert g.theta < 0.0

    def test_repr(self):
        g = hz.logit_greeks(0.5, 10.0, True)
        assert "LogitGreeks" in repr(g)


class TestLogitReservation:
    def test_inventory_skew_direction(self):
        mid = 0.5
        long_res = hz.logit_reservation_price(mid, 10.0, 0.5, 0.2, 1.0)
        short_res = hz.logit_reservation_price(mid, -10.0, 0.5, 0.2, 1.0)
        # Long inventory should skew reservation price down
        assert long_res < mid
        # Short inventory should skew reservation price up
        assert short_res > mid

    def test_recovery_at_zero_inventory(self):
        mid = 0.5
        res = hz.logit_reservation_price(mid, 0.0, 0.5, 0.2, 1.0)
        assert abs(res - mid) < 0.01

    def test_clamped_to_valid_range(self):
        res = hz.logit_reservation_price(0.5, 1000.0, 5.0, 0.5, 1.0)
        assert 0.01 <= res <= 0.99


class TestLogitOptimalSpread:
    def test_positive_spread(self):
        s = hz.logit_optimal_spread(0.2, 0.0, 0.5, 1.5, 1.0)
        assert s > 0.0

    def test_spread_increases_with_vol(self):
        s_low = hz.logit_optimal_spread(0.1, 0.0, 0.5, 1.5, 1.0)
        s_high = hz.logit_optimal_spread(0.5, 0.0, 0.5, 1.5, 1.0)
        assert s_high > s_low


class TestLogitPnlDecompose:
    def test_components_sum_to_actual(self):
        attr = hz.logit_pnl_decompose(0.4, 0.6, 10.0, True, 0.2, 0.25)
        total = (attr.directional + attr.curvature + attr.belief_vega_pnl
                 + attr.correlation_vega_pnl + attr.residual)
        actual = 10.0 * (0.6 - 0.4)  # yes side
        assert abs(total - actual) < 1e-10

    def test_zero_move_zero_components(self):
        attr = hz.logit_pnl_decompose(0.5, 0.5, 10.0, True, 0.2, 0.2)
        assert abs(attr.directional) < 1e-10
        assert abs(attr.curvature) < 1e-10
        assert abs(attr.belief_vega_pnl) < 1e-10

    def test_repr(self):
        attr = hz.logit_pnl_decompose(0.4, 0.6, 10.0, True, 0.2, 0.2)
        assert "LogitPnlAttribution" in repr(attr)


class TestToxicityAdjustedSpread:
    def test_no_toxicity(self):
        s = hz.toxicity_adjusted_spread(0.04, 0.0)
        assert abs(s - 0.04) < 1e-10

    def test_full_toxicity(self):
        s = hz.toxicity_adjusted_spread(0.04, 1.0, sensitivity=1.0)
        assert abs(s - 0.08) < 1e-10

    def test_clamped(self):
        s = hz.toxicity_adjusted_spread(0.9, 1.0, sensitivity=10.0)
        assert s <= 1.0

    def test_increases_with_vpin(self):
        s_low = hz.toxicity_adjusted_spread(0.04, 0.2)
        s_high = hz.toxicity_adjusted_spread(0.04, 0.8)
        assert s_high > s_low
