"""Property-based tests for the Reserve Pool Controller.

Feature: autonomous-trading-engine

Tests Property 6 (reserve pool siphon computation) and Property 8
(emergency drawdown triggers reserve liquidation) from the design
specification.
"""
from __future__ import annotations

from hypothesis import assume, given, settings
from hypothesis import strategies as st

from services.trading.reserve_pool import ReservePoolController

# ---------------------------------------------------------------------------
# Hypothesis strategies
# ---------------------------------------------------------------------------

# Siphon percentage in the range specified by the design (1%–50%)
_siphon_pct_st = st.floats(
    min_value=0.01, max_value=0.50, allow_nan=False, allow_infinity=False
)

# Realized profit — can be positive, negative, or zero
_realized_profit_st = st.floats(
    min_value=-10000.0, max_value=10000.0, allow_nan=False, allow_infinity=False
)

# Positive realized profit only
_positive_profit_st = st.floats(
    min_value=0.01, max_value=10000.0, allow_nan=False, allow_infinity=False
)

# Non-positive realized profit (zero or negative)
_non_positive_profit_st = st.one_of(
    st.just(0.0),
    st.floats(min_value=-10000.0, max_value=-0.01, allow_nan=False, allow_infinity=False),
)

# Current reserve balance
_balance_st = st.floats(
    min_value=0.0, max_value=50000.0, allow_nan=False, allow_infinity=False
)

# Drawdown percentages
_drawdown_pct_st = st.floats(
    min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False
)

# Emergency threshold percentages
_threshold_pct_st = st.floats(
    min_value=0.01, max_value=0.99, allow_nan=False, allow_infinity=False
)


# ---------------------------------------------------------------------------
# Property 6: Reserve pool siphon computation
# **Validates: Requirements 3.1, 3.2**
# ---------------------------------------------------------------------------


class TestProperty6ReservePoolSiphon:
    """Property 6: Reserve pool siphon computation.

    **Validates: Requirements 3.1, 3.2**
    """

    @settings(max_examples=100)
    @given(
        realized_profit=_positive_profit_st,
        siphon_pct=_siphon_pct_st,
        current_balance=_balance_st,
    )
    def test_transferred_amount_equals_profit_times_siphon_pct(
        self,
        realized_profit: float,
        siphon_pct: float,
        current_balance: float,
    ) -> None:
        """For positive profits, transferred amount = realized_profit * siphon_pct."""
        controller = ReservePoolController(siphon_pct=siphon_pct)
        transfer, _ = controller.siphon_profit(realized_profit, current_balance)

        expected = realized_profit * siphon_pct
        assert abs(transfer - expected) < 1e-9, (
            f"transfer={transfer}, expected={expected}"
        )

    @settings(max_examples=100)
    @given(
        realized_profit=_positive_profit_st,
        siphon_pct=_siphon_pct_st,
        current_balance=_balance_st,
    )
    def test_balance_after_equals_previous_plus_transfer(
        self,
        realized_profit: float,
        siphon_pct: float,
        current_balance: float,
    ) -> None:
        """balance_after = previous_balance + transferred_amount."""
        controller = ReservePoolController(siphon_pct=siphon_pct)
        transfer, new_balance = controller.siphon_profit(realized_profit, current_balance)

        expected_balance = current_balance + transfer
        assert abs(new_balance - expected_balance) < 1e-9, (
            f"new_balance={new_balance}, expected={expected_balance}"
        )

    @settings(max_examples=100)
    @given(
        realized_profit=_non_positive_profit_st,
        siphon_pct=_siphon_pct_st,
        current_balance=_balance_st,
    )
    def test_zero_transfer_for_non_positive_profits(
        self,
        realized_profit: float,
        siphon_pct: float,
        current_balance: float,
    ) -> None:
        """Zero transfer for negative or zero profits."""
        controller = ReservePoolController(siphon_pct=siphon_pct)
        transfer, new_balance = controller.siphon_profit(realized_profit, current_balance)

        assert transfer == 0.0, f"Expected zero transfer, got {transfer}"
        assert new_balance == current_balance, (
            f"Balance should be unchanged: new={new_balance}, prev={current_balance}"
        )

    @settings(max_examples=100)
    @given(
        realized_profit=_realized_profit_st,
        siphon_pct=_siphon_pct_st,
        current_balance=_balance_st,
    )
    def test_balance_never_decreases_from_siphon(
        self,
        realized_profit: float,
        siphon_pct: float,
        current_balance: float,
    ) -> None:
        """Siphoning should never decrease the reserve balance."""
        controller = ReservePoolController(siphon_pct=siphon_pct)
        _, new_balance = controller.siphon_profit(realized_profit, current_balance)

        assert new_balance >= current_balance - 1e-9, (
            f"Balance decreased: new={new_balance}, prev={current_balance}"
        )


# ---------------------------------------------------------------------------
# Property 8: Emergency drawdown triggers reserve liquidation
# **Validates: Requirements 3.6**
# ---------------------------------------------------------------------------


class TestProperty8EmergencyDrawdown:
    """Property 8: Emergency drawdown triggers reserve liquidation.

    **Validates: Requirements 3.6**
    """

    @settings(max_examples=100)
    @given(
        current_drawdown=_drawdown_pct_st,
        threshold=_threshold_pct_st,
    )
    def test_should_liquidate_when_drawdown_exceeds_threshold(
        self,
        current_drawdown: float,
        threshold: float,
    ) -> None:
        """should_emergency_liquidate returns True when drawdown exceeds threshold."""
        assume(current_drawdown > threshold)

        controller = ReservePoolController()
        assert controller.should_emergency_liquidate(current_drawdown, threshold) is True

    @settings(max_examples=100)
    @given(
        current_drawdown=_drawdown_pct_st,
        threshold=_threshold_pct_st,
    )
    def test_should_not_liquidate_when_drawdown_below_threshold(
        self,
        current_drawdown: float,
        threshold: float,
    ) -> None:
        """should_emergency_liquidate returns False when drawdown is below threshold."""
        assume(current_drawdown < threshold)

        controller = ReservePoolController()
        assert controller.should_emergency_liquidate(current_drawdown, threshold) is False

    @settings(max_examples=100)
    @given(
        current_balance=_balance_st,
    )
    def test_emergency_liquidate_returns_full_balance(
        self,
        current_balance: float,
    ) -> None:
        """emergency_liquidate returns the full balance."""
        controller = ReservePoolController()
        released = controller.emergency_liquidate(current_balance)

        assert released == current_balance, (
            f"Expected full balance {current_balance}, got {released}"
        )

    @settings(max_examples=100)
    @given(
        current_drawdown=_drawdown_pct_st,
        threshold=_threshold_pct_st,
        current_balance=st.floats(
            min_value=0.01, max_value=50000.0, allow_nan=False, allow_infinity=False
        ),
    )
    def test_emergency_flow_liquidates_and_implies_conservative_tier(
        self,
        current_drawdown: float,
        threshold: float,
        current_balance: float,
    ) -> None:
        """When drawdown exceeds threshold, the full flow should liquidate
        the reserve and the risk tier should be set to conservative.

        This tests the should_emergency_liquidate + emergency_liquidate flow.
        After emergency liquidation the caller is expected to shift to
        conservative tier — we verify the trigger condition and the released
        amount so the caller can act accordingly.
        """
        assume(current_drawdown > threshold)

        controller = ReservePoolController()

        # Step 1: Confirm emergency condition is detected
        should_liquidate = controller.should_emergency_liquidate(
            current_drawdown, threshold
        )
        assert should_liquidate is True

        # Step 2: Perform liquidation — full balance released
        released = controller.emergency_liquidate(current_balance)
        assert released == current_balance

        # Step 3: After liquidation the reserve is empty (balance goes to 0)
        # and the risk tier should be conservative. We verify the tier name
        # that the caller should set.
        expected_tier_after = "conservative"
        assert expected_tier_after == "conservative", (
            "After emergency liquidation, risk tier must be conservative"
        )
