"""Property-based tests for the Backtester.

Feature: autonomous-trading-engine

Tests Property 36 (backtester produces equivalent metrics) from the
design specification.  Generates random sets of historical trades and
verifies that the backtester's metric computation matches the
PerformanceComputer for the same trade data.
"""

from __future__ import annotations

from datetime import date, timedelta

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

from services.trading.backtester import BacktestConfig, BacktestEngine
from services.trading.models import ClosedTrade
from services.trading.performance_tracker import PerformanceComputer

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

_tickers = st.sampled_from(["AAPL", "MSFT", "GOOG", "AMZN", "TSLA", "NVDA"])

_entry_price_st = st.floats(
    min_value=1.0, max_value=5000.0, allow_nan=False, allow_infinity=False
)

_exit_price_st = st.floats(
    min_value=1.0, max_value=5000.0, allow_nan=False, allow_infinity=False
)

_quantity_st = st.integers(min_value=1, max_value=1000)

_hold_duration_st = st.timedeltas(
    min_value=timedelta(minutes=1), max_value=timedelta(days=365)
)


@st.composite
def closed_trade_st(draw: st.DrawFn) -> ClosedTrade:
    """Generate a random ClosedTrade with consistent pnl fields."""
    ticker = draw(_tickers)
    entry_price = draw(_entry_price_st)
    exit_price = draw(_exit_price_st)
    quantity = draw(_quantity_st)
    hold_duration = draw(_hold_duration_st)

    pnl = (exit_price - entry_price) * quantity
    pnl_pct = (exit_price - entry_price) / entry_price if entry_price > 0 else 0.0

    return ClosedTrade(
        ticker=ticker,
        entry_price=entry_price,
        exit_price=exit_price,
        quantity=quantity,
        pnl=pnl,
        pnl_pct=pnl_pct,
        hold_duration=hold_duration,
    )


_trades_st = st.lists(closed_trade_st(), min_size=0, max_size=50)

_daily_returns_st = st.lists(
    st.floats(min_value=-0.20, max_value=0.20, allow_nan=False, allow_infinity=False),
    min_size=0,
    max_size=252,
)

_initial_capital_st = st.floats(
    min_value=100.0, max_value=100000.0, allow_nan=False, allow_infinity=False
)

_risk_tier_st = st.sampled_from(["conservative", "moderate", "aggressive"])


# ---------------------------------------------------------------------------
# Property 36: Backtester produces equivalent metrics
# **Validates: Requirements 15.3**
# ---------------------------------------------------------------------------


class TestProperty36BacktesterEquivalentMetrics:
    """Property 36: Backtester produces equivalent metrics.

    **Validates: Requirements 15.3**

    For any set of closed trades and daily returns, the backtester's
    win_rate, profit_factor, and trade_count must be identical to
    those computed directly by PerformanceComputer.
    """

    @settings(max_examples=100)
    @given(
        trades=_trades_st,
        daily_returns=_daily_returns_st,
        initial_capital=_initial_capital_st,
        risk_tier=_risk_tier_st,
    )
    def test_win_rate_matches_performance_tracker(
        self,
        trades: list[ClosedTrade],
        daily_returns: list[float],
        initial_capital: float,
        risk_tier: str,
    ) -> None:
        """win_rate from BacktestEngine matches PerformanceComputer."""
        config = BacktestConfig(
            start_date=date(2024, 1, 1),
            end_date=date(2024, 12, 31),
            initial_capital=initial_capital,
            risk_tier=risk_tier,
        )

        engine = BacktestEngine()
        result = engine.compute_result(config, trades, daily_returns, [])

        perf = PerformanceComputer()
        metrics = perf.compute_metrics(
            closed_trades=trades,
            portfolio_value=initial_capital,
            active_pool=initial_capital,
            reserve_pool=0.0,
            daily_pnl=0.0,
            unrealized_pnl=0.0,
            portfolio_heat=0.0,
            daily_returns=daily_returns,
        )

        assert result.win_rate == metrics.win_rate, (
            f"win_rate mismatch: backtest={result.win_rate}, "
            f"perf_tracker={metrics.win_rate}"
        )

    @settings(max_examples=100)
    @given(
        trades=_trades_st,
        daily_returns=_daily_returns_st,
        initial_capital=_initial_capital_st,
        risk_tier=_risk_tier_st,
    )
    def test_profit_factor_matches_performance_tracker(
        self,
        trades: list[ClosedTrade],
        daily_returns: list[float],
        initial_capital: float,
        risk_tier: str,
    ) -> None:
        """profit_factor from BacktestEngine matches PerformanceComputer."""
        config = BacktestConfig(
            start_date=date(2024, 1, 1),
            end_date=date(2024, 12, 31),
            initial_capital=initial_capital,
            risk_tier=risk_tier,
        )

        engine = BacktestEngine()
        result = engine.compute_result(config, trades, daily_returns, [])

        perf = PerformanceComputer()
        metrics = perf.compute_metrics(
            closed_trades=trades,
            portfolio_value=initial_capital,
            active_pool=initial_capital,
            reserve_pool=0.0,
            daily_pnl=0.0,
            unrealized_pnl=0.0,
            portfolio_heat=0.0,
            daily_returns=daily_returns,
        )

        assert result.profit_factor == metrics.profit_factor, (
            f"profit_factor mismatch: backtest={result.profit_factor}, "
            f"perf_tracker={metrics.profit_factor}"
        )

    @settings(max_examples=100)
    @given(
        trades=_trades_st,
        daily_returns=_daily_returns_st,
        initial_capital=_initial_capital_st,
        risk_tier=_risk_tier_st,
    )
    def test_trade_count_matches_number_of_trades(
        self,
        trades: list[ClosedTrade],
        daily_returns: list[float],
        initial_capital: float,
        risk_tier: str,
    ) -> None:
        """trade_count from BacktestEngine equals len(trades)."""
        config = BacktestConfig(
            start_date=date(2024, 1, 1),
            end_date=date(2024, 12, 31),
            initial_capital=initial_capital,
            risk_tier=risk_tier,
        )

        engine = BacktestEngine()
        result = engine.compute_result(config, trades, daily_returns, [])

        # trade_count should equal the number of trades passed in,
        # which is also win_count + loss_count from PerformanceComputer
        perf = PerformanceComputer()
        metrics = perf.compute_metrics(
            closed_trades=trades,
            portfolio_value=initial_capital,
            active_pool=initial_capital,
            reserve_pool=0.0,
            daily_pnl=0.0,
            unrealized_pnl=0.0,
            portfolio_heat=0.0,
            daily_returns=daily_returns,
        )

        assert result.trade_count == len(trades), (
            f"trade_count mismatch: backtest={result.trade_count}, "
            f"expected={len(trades)}"
        )
        assert result.trade_count == metrics.win_count + metrics.loss_count, (
            f"trade_count={result.trade_count} != "
            f"win_count({metrics.win_count}) + loss_count({metrics.loss_count})"
        )
