"""Tests for MFE/MAE preservation in Trade class."""

from datetime import datetime

import pytest

from ml4t.backtest.analytics.trades import MAEMFEAnalyzer, TradeAnalyzer
from ml4t.backtest.types import Trade


class TestTradeMfeMAeFields:
    """Test MFE/MAE fields on Trade class."""

    def test_trade_has_mfe_mae_fields(self):
        """Test Trade class has MFE/MAE fields."""
        trade = Trade(
            symbol="AAPL",
            entry_time=datetime(2024, 1, 1, 9, 30),
            exit_time=datetime(2024, 1, 5, 16, 0),
            entry_price=150.0,
            exit_price=155.0,
            quantity=100.0,
            pnl=500.0,
            pnl_percent=0.0333,
            bars_held=5,
        )
        # Should have default values
        assert trade.mfe == 0.0
        assert trade.mae == 0.0

    def test_trade_with_mfe_mae_values(self):
        """Test Trade preserves MFE/MAE values."""
        trade = Trade(
            symbol="AAPL",
            entry_time=datetime(2024, 1, 1, 9, 30),
            exit_time=datetime(2024, 1, 5, 16, 0),
            entry_price=150.0,
            exit_price=155.0,
            quantity=100.0,
            pnl=500.0,
            pnl_percent=0.0333,  # 3.33% gain
            bars_held=5,
            mfe=0.05,  # Hit +5% during trade
            mae=-0.02,  # Hit -2% during trade
        )
        assert trade.mfe == 0.05
        assert trade.mae == -0.02


class TestTradeAnalyzerMfeMae:
    """Test TradeAnalyzer MFE/MAE methods."""

    @pytest.fixture
    def sample_trades(self):
        """Create sample trades with various MFE/MAE values."""
        return [
            Trade(
                symbol="AAPL",
                entry_time=datetime(2024, 1, 1),
                exit_time=datetime(2024, 1, 2),
                entry_price=100.0,
                exit_price=105.0,
                quantity=100.0,
                pnl=500.0,
                pnl_percent=0.05,  # 5% gain
                bars_held=1,
                mfe=0.08,  # Hit +8% peak
                mae=-0.01,  # Minimal drawdown
            ),
            Trade(
                symbol="GOOG",
                entry_time=datetime(2024, 1, 3),
                exit_time=datetime(2024, 1, 4),
                entry_price=200.0,
                exit_price=190.0,
                quantity=50.0,
                pnl=-500.0,
                pnl_percent=-0.05,  # 5% loss
                bars_held=1,
                mfe=0.02,  # Briefly profitable
                mae=-0.08,  # Hit -8% at worst
            ),
            Trade(
                symbol="MSFT",
                entry_time=datetime(2024, 1, 5),
                exit_time=datetime(2024, 1, 6),
                entry_price=300.0,
                exit_price=306.0,
                quantity=30.0,
                pnl=180.0,
                pnl_percent=0.02,  # 2% gain
                bars_held=1,
                mfe=0.04,  # Hit +4% peak
                mae=-0.03,  # Hit -3% drawdown
            ),
        ]

    def test_avg_mfe(self, sample_trades):
        """Test average MFE calculation."""
        analyzer = TradeAnalyzer(sample_trades)
        # Average of 0.08, 0.02, 0.04 = 0.0467
        expected = (0.08 + 0.02 + 0.04) / 3
        assert abs(analyzer.avg_mfe - expected) < 0.0001

    def test_avg_mae(self, sample_trades):
        """Test average MAE calculation."""
        analyzer = TradeAnalyzer(sample_trades)
        # Average of -0.01, -0.08, -0.03 = -0.04
        expected = (-0.01 + -0.08 + -0.03) / 3
        assert abs(analyzer.avg_mae - expected) < 0.0001

    def test_mfe_capture_ratio(self, sample_trades):
        """Test MFE capture ratio calculation."""
        analyzer = TradeAnalyzer(sample_trades)
        # Trade 1: 0.05 / 0.08 = 0.625 (captured 62.5% of peak)
        # Trade 2: -0.05 / 0.02 = -2.5 (lost more than peak, still included)
        # Trade 3: 0.02 / 0.04 = 0.5 (captured 50% of peak)
        # Average: (0.625 + -2.5 + 0.5) / 3 = -0.458
        ratio = analyzer.mfe_capture_ratio
        # All three have positive MFE so all are included
        assert ratio < 0  # Negative due to losing trade

    def test_mae_recovery_ratio(self, sample_trades):
        """Test MAE recovery ratio for losing trades."""
        analyzer = TradeAnalyzer(sample_trades)
        # Only losing trade (GOOG): MAE=-0.08, final=-0.05
        # Recovery = (final - MAE) / |MAE| = (-0.05 - -0.08) / 0.08 = 0.375
        ratio = analyzer.mae_recovery_ratio
        assert ratio == pytest.approx(0.375)

    def test_empty_trades(self):
        """Test MFE/MAE methods with empty trades."""
        analyzer = TradeAnalyzer([])
        assert analyzer.avg_mfe == 0.0
        assert analyzer.avg_mae == 0.0
        assert analyzer.mfe_capture_ratio == 0.0
        assert analyzer.mae_recovery_ratio == 0.0

    def test_to_dict_includes_mfe_mae(self, sample_trades):
        """Test that to_dict includes MFE/MAE metrics."""
        analyzer = TradeAnalyzer(sample_trades)
        d = analyzer.to_dict()

        assert "avg_mfe" in d
        assert "avg_mae" in d
        assert "mfe_capture_ratio" in d
        assert "mae_recovery_ratio" in d


class TestTradeAnalyzerMfeCaptureEdgeCases:
    """Test edge cases for MFE capture ratio."""

    def test_zero_mfe_excluded(self):
        """Test that trades with zero MFE are excluded from capture ratio."""
        trades = [
            Trade(
                symbol="A",
                entry_time=datetime(2024, 1, 1),
                exit_time=datetime(2024, 1, 2),
                entry_price=100.0,
                exit_price=95.0,
                quantity=100.0,
                pnl=-500.0,
                pnl_percent=-0.05,
                bars_held=1,
                mfe=0.0,  # Never profitable
                mae=-0.06,
            ),
        ]
        analyzer = TradeAnalyzer(trades)
        # Zero MFE means we can't calculate capture ratio
        assert analyzer.mfe_capture_ratio == 0.0

    def test_all_winners_high_capture(self):
        """Test capture ratio for trades that exit near peak."""
        trades = [
            Trade(
                symbol="A",
                entry_time=datetime(2024, 1, 1),
                exit_time=datetime(2024, 1, 2),
                entry_price=100.0,
                exit_price=110.0,
                quantity=100.0,
                pnl=1000.0,
                pnl_percent=0.10,  # 10% gain
                bars_held=1,
                mfe=0.10,  # Exited at peak!
                mae=-0.01,
            ),
        ]
        analyzer = TradeAnalyzer(trades)
        # 0.10 / 0.10 = 1.0 (perfect capture)
        assert analyzer.mfe_capture_ratio == 1.0


class TestMAEMFEAnalyzer:
    """Test MAEMFEAnalyzer class."""

    @pytest.fixture
    def sample_trades(self):
        """Create sample trades with various MFE/MAE values."""
        return [
            Trade(
                symbol="AAPL",
                entry_time=datetime(2024, 1, 1),
                exit_time=datetime(2024, 1, 2),
                entry_price=100.0,
                exit_price=105.0,
                quantity=100.0,
                pnl=500.0,
                pnl_percent=0.05,  # 5% gain
                bars_held=1,
                mfe=0.08,  # Hit +8% peak
                mae=-0.01,  # Minimal drawdown
            ),
            Trade(
                symbol="GOOG",
                entry_time=datetime(2024, 1, 3),
                exit_time=datetime(2024, 1, 4),
                entry_price=200.0,
                exit_price=190.0,
                quantity=50.0,
                pnl=-500.0,
                pnl_percent=-0.05,  # 5% loss
                bars_held=1,
                mfe=0.02,  # Briefly profitable
                mae=-0.08,  # Hit -8% at worst
            ),
            Trade(
                symbol="MSFT",
                entry_time=datetime(2024, 1, 5),
                exit_time=datetime(2024, 1, 6),
                entry_price=300.0,
                exit_price=306.0,
                quantity=30.0,
                pnl=180.0,
                pnl_percent=0.02,  # 2% gain
                bars_held=1,
                mfe=0.04,  # Hit +4% peak
                mae=-0.03,  # Hit -3% drawdown
            ),
        ]

    def test_basic_stats(self, sample_trades):
        """Test basic MAE/MFE statistics."""
        analyzer = MAEMFEAnalyzer(sample_trades)

        assert analyzer.num_trades == 3

        # MAE stats: -0.01, -0.08, -0.03 -> mean = -0.04
        expected_mae_mean = (-0.01 + -0.08 + -0.03) / 3
        assert abs(analyzer.mae_mean - expected_mae_mean) < 0.0001

        # MFE stats: 0.08, 0.02, 0.04 -> mean = 0.0467
        expected_mfe_mean = (0.08 + 0.02 + 0.04) / 3
        assert abs(analyzer.mfe_mean - expected_mfe_mean) < 0.0001

    def test_percentiles(self, sample_trades):
        """Test MAE/MFE percentile calculations."""
        analyzer = MAEMFEAnalyzer(sample_trades)

        # MAE sorted: -0.08, -0.03, -0.01
        # 50th percentile should be around -0.03
        mae_median = analyzer.mae_percentile(50)
        assert mae_median == analyzer.mae_median

        # MFE sorted: 0.02, 0.04, 0.08
        # 50th percentile should be around 0.04
        mfe_median = analyzer.mfe_percentile(50)
        assert mfe_median == analyzer.mfe_median

    def test_edge_ratio(self, sample_trades):
        """Test edge ratio calculation."""
        analyzer = MAEMFEAnalyzer(sample_trades)

        # edge_ratio = mfe_mean / |mae_mean|
        expected = analyzer.mfe_mean / abs(analyzer.mae_mean)
        assert abs(analyzer.edge_ratio - expected) < 0.0001

    def test_suggest_stop_loss(self, sample_trades):
        """Test stop loss suggestion."""
        analyzer = MAEMFEAnalyzer(sample_trades)

        # Get stop that preserves 90% of winning trades
        stop = analyzer.suggest_stop_loss(percentile=90)

        # Should be negative (stop loss level)
        assert stop < 0

    def test_suggest_take_profit(self, sample_trades):
        """Test take profit suggestion."""
        analyzer = MAEMFEAnalyzer(sample_trades)

        # Get target that 75% of trades reach
        target = analyzer.suggest_take_profit(percentile=75)

        # Should be positive (profit target)
        assert target > 0

    def test_optimal_exit_levels(self, sample_trades):
        """Test optimal exit level suggestions."""
        analyzer = MAEMFEAnalyzer(sample_trades)

        levels = analyzer.optimal_exit_levels(stop_percentile=90, target_percentile=75)

        assert "stop_loss" in levels
        assert "take_profit" in levels
        assert "risk_reward" in levels
        assert levels["stop_loss"] < 0
        assert levels["take_profit"] > 0
        assert levels["risk_reward"] > 0

    def test_distribution_data(self, sample_trades):
        """Test distribution data export."""
        analyzer = MAEMFEAnalyzer(sample_trades)

        data = analyzer.distribution_data()

        assert "mae" in data
        assert "mfe" in data
        assert len(data["mae"]) == 3
        assert len(data["mfe"]) == 3

    def test_to_dict(self, sample_trades):
        """Test to_dict export."""
        analyzer = MAEMFEAnalyzer(sample_trades)

        d = analyzer.to_dict()

        # Check key fields exist
        assert "num_trades" in d
        assert "mae_mean" in d
        assert "mae_median" in d
        assert "mfe_mean" in d
        assert "mfe_median" in d
        assert "edge_ratio" in d
        assert "efficiency" in d
        assert "suggested_stop_loss" in d
        assert "suggested_take_profit" in d

    def test_empty_trades(self):
        """Test with empty trade list."""
        analyzer = MAEMFEAnalyzer([])

        assert analyzer.num_trades == 0
        assert analyzer.mae_mean == 0.0
        assert analyzer.mfe_mean == 0.0
        assert analyzer.edge_ratio == 0.0
        assert analyzer.suggest_stop_loss() == 0.0
        assert analyzer.suggest_take_profit() == 0.0
