"""Tests for MAE/MFE Workflow module."""

from __future__ import annotations

import polars as pl
import pytest
from tradepose_client.analysis.trades.workflow import (
    EntryDelayRecommendation,
    MAEMFEQuantiles,
    PositionSizingRecommendation,
    QuantileStats,
    SLTPRecommendation,
    TrailingStopRecommendation,
    analyze_entry_delay,
    analyze_position_sizing,
    analyze_sltp,
    analyze_trailing_stop,
    calculate_mae_mfe_quantiles,
    calculate_quantiles,
    mfe_g_mfe_overlap_score,
)


class TestQuantileStats:
    """Tests for QuantileStats dataclass."""

    def test_iqr_calculation(self) -> None:
        """Test IQR is correctly calculated as Q3 - Q1."""
        stats = QuantileStats(
            q1=10.0,
            q2=20.0,
            q3=30.0,
            mean=20.0,
            std=10.0,
            min=0.0,
            max=50.0,
        )
        assert stats.iqr == 20.0  # 30 - 10

    def test_q1_q2_mid(self) -> None:
        """Test midpoint between Q1 and Q2."""
        stats = QuantileStats(
            q1=10.0,
            q2=20.0,
            q3=30.0,
            mean=20.0,
            std=10.0,
            min=0.0,
            max=50.0,
        )
        assert stats.q1_q2_mid == 15.0  # (10 + 20) / 2

    def test_q2_q3_mid(self) -> None:
        """Test midpoint between Q2 and Q3."""
        stats = QuantileStats(
            q1=10.0,
            q2=20.0,
            q3=30.0,
            mean=20.0,
            std=10.0,
            min=0.0,
            max=50.0,
        )
        assert stats.q2_q3_mid == 25.0  # (20 + 30) / 2


class TestCalculateQuantiles:
    """Tests for calculate_quantiles function."""

    def test_basic_quantiles(self) -> None:
        """Test quantile calculation on simple series."""
        series = pl.Series([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0])
        stats = calculate_quantiles(series)

        assert stats.min == 1.0
        assert stats.max == 10.0
        assert stats.mean == pytest.approx(5.5)
        # Polars default quantile interpolation gives 6.0 for median of this series
        assert stats.q2 == pytest.approx(6.0)


class TestMAEMFEQuantiles:
    """Tests for MAE/MFE quantiles calculation."""

    def test_calculate_mae_mfe_quantiles(self, sample_trades_df: pl.DataFrame) -> None:
        """Test quantile calculation for MAE/MFE."""
        quantiles = calculate_mae_mfe_quantiles(sample_trades_df)

        assert isinstance(quantiles, MAEMFEQuantiles)
        assert isinstance(quantiles.mae, QuantileStats)
        assert isinstance(quantiles.mfe, QuantileStats)
        assert quantiles.mae.q1 < quantiles.mae.q2 < quantiles.mae.q3

    def test_missing_mae_column_error(self) -> None:
        """Test error when MAE column is missing."""
        df = pl.DataFrame({"mfe": [1.0, 2.0, 3.0]})

        with pytest.raises(ValueError, match="mae"):
            calculate_mae_mfe_quantiles(df)

    def test_missing_mfe_column_error(self) -> None:
        """Test error when MFE column is missing."""
        df = pl.DataFrame({"mae": [1.0, 2.0, 3.0]})

        with pytest.raises(ValueError, match="mfe"):
            calculate_mae_mfe_quantiles(df)


class TestEntryDelayAnalysis:
    """Tests for Step B: Entry Timing Analysis."""

    def test_entry_delay_recommendation(self, sample_trades_df: pl.DataFrame) -> None:
        """Test entry delay recommendation generation."""
        rec = analyze_entry_delay(sample_trades_df)

        assert isinstance(rec, EntryDelayRecommendation)
        assert isinstance(rec.should_delay, bool)
        assert isinstance(rec.mae_mfe_ratio, float)
        assert rec.reason != ""

    def test_entry_delay_with_high_mae(self) -> None:
        """Test that high MAE suggests delay."""
        # Create DataFrame with high MAE, low MFE
        df = pl.DataFrame(
            {
                "mae": [10.0, 15.0, 20.0, 25.0, 30.0],
                "mfe": [2.0, 3.0, 4.0, 5.0, 6.0],
                "pnl": [-5.0, -8.0, -10.0, -12.0, -15.0],
            }
        )

        rec = analyze_entry_delay(df)

        assert rec.should_delay is True
        assert rec.mae_mfe_ratio > 1.0

    def test_entry_delay_with_low_mae(self) -> None:
        """Test that low MAE suggests no delay."""
        # Create DataFrame with low MAE, high MFE
        df = pl.DataFrame(
            {
                "mae": [1.0, 2.0, 3.0, 4.0, 5.0],
                "mfe": [10.0, 15.0, 20.0, 25.0, 30.0],
                "pnl": [5.0, 8.0, 10.0, 12.0, 15.0],
            }
        )

        rec = analyze_entry_delay(df)

        assert rec.should_delay is False
        assert rec.mae_mfe_ratio < 1.0


class TestSLTPAnalysis:
    """Tests for Step C: Stop Loss / Take Profit Analysis."""

    def test_sltp_recommendation(self, sample_trades_df: pl.DataFrame) -> None:
        """Test SL/TP recommendation generation."""
        rec = analyze_sltp(sample_trades_df)

        assert isinstance(rec, SLTPRecommendation)
        assert rec.sl_raw > 0
        assert rec.tp_raw > 0
        assert rec.risk_reward_ratio > 0

    def test_sltp_with_g_mfe(self, sample_trades_df: pl.DataFrame) -> None:
        """Test SL/TP with g_mfe for wave trading."""
        rec = analyze_sltp(sample_trades_df, use_g_mfe=True)

        assert isinstance(rec, SLTPRecommendation)
        assert rec.tp_raw > 0

    def test_breakeven_win_rate(self) -> None:
        """Test expected win rate for breakeven calculation."""
        df = pl.DataFrame(
            {
                "mae": [10.0] * 10,
                "mfe": [20.0] * 10,
                "pnl": [5.0] * 10,
            }
        )

        rec = analyze_sltp(df)

        # With SL~10 and TP~20, breakeven WR should be around 33%
        assert 0.2 < rec.expected_win_rate_for_breakeven < 0.5


class TestTrailingStopAnalysis:
    """Tests for Step D: Trailing Stop / Breakeven Analysis."""

    def test_trailing_stop_recommendation(self, sample_trades_df: pl.DataFrame) -> None:
        """Test trailing stop recommendation generation."""
        rec = analyze_trailing_stop(sample_trades_df)

        assert isinstance(rec, TrailingStopRecommendation)
        assert rec.breakeven_trigger_q2 >= 0
        assert rec.trailing_distance_q2 >= 0

    def test_inverse_relationship_detection(self) -> None:
        """Test detection of inverse MAE/MFE relationship."""
        # Create DataFrame with inverse relationship
        df = pl.DataFrame(
            {
                "mae": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
                "mfe": [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0],
                "mhl": [5.0] * 10,
                "pnl": [-5.0, -4.0, -3.0, -2.0, -1.0, 1.0, 2.0, 3.0, 4.0, 5.0],
            }
        )

        rec = analyze_trailing_stop(df)

        # Strong negative correlation should be detected
        assert rec.has_inverse_relationship is True


class TestPositionSizingAnalysis:
    """Tests for Step E: Position Sizing Analysis."""

    def test_position_sizing_recommendation(self, sample_trades_df: pl.DataFrame) -> None:
        """Test position sizing recommendation generation."""
        rec = analyze_position_sizing(sample_trades_df)

        assert isinstance(rec, PositionSizingRecommendation)
        assert rec.adverse_add_zone_start <= rec.adverse_add_zone_end
        assert rec.favorable_add_zone_start <= rec.favorable_add_zone_end

    def test_position_sizing_with_tp(self, sample_trades_df: pl.DataFrame) -> None:
        """Test position sizing with TP level for reduce zone."""
        rec = analyze_position_sizing(sample_trades_df, tp_level=10.0)

        assert rec.reduce_zone > rec.favorable_add_zone_end


class TestMFEOverlapScore:
    """Tests for MFE/g_mfe overlap score calculation."""

    def test_overlap_score_with_g_mfe(self, sample_trades_df: pl.DataFrame) -> None:
        """Test overlap score calculation when g_mfe is available."""
        score = mfe_g_mfe_overlap_score(sample_trades_df)

        assert 0.0 <= score <= 1.0

    def test_overlap_score_without_g_mfe(self) -> None:
        """Test overlap score defaults to 1.0 when g_mfe not available."""
        df = pl.DataFrame(
            {
                "mae": [1.0, 2.0, 3.0],
                "mfe": [2.0, 3.0, 4.0],
            }
        )

        score = mfe_g_mfe_overlap_score(df)

        assert score == 1.0

    def test_overlap_score_perfect_overlap(self) -> None:
        """Test overlap score is 1.0 when g_mfe equals mfe."""
        df = pl.DataFrame(
            {
                "mae": [1.0, 2.0, 3.0],
                "mfe": [2.0, 3.0, 4.0],
                "g_mfe": [2.0, 3.0, 4.0],  # Same as mfe
            }
        )

        score = mfe_g_mfe_overlap_score(df)

        assert score == pytest.approx(1.0)
