"""
Effectiveness Validator - Backtesting Validation Module
========================================================
Validates the 80% effectiveness target for trading strategies.

This module provides comprehensive validation of trading strategy
effectiveness including:
- Overall effectiveness validation
- Per-symbol effectiveness breakdown
- Per-regime effectiveness analysis
- Confidence-filtered effectiveness

Target: 80% effectiveness rate for high-confidence trades

Author: ML-Specialist (NEXUS v4.0)
Version: 1.0.0
Created: 2026-01-25
"""

import numpy as np
import pandas as pd
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple, Any, Union
from datetime import datetime
from loguru import logger

from .metrics import TradeRecord, MetricsCalculator, PerformanceMetrics


@dataclass
class ValidationResult:
    """
    Result of strategy effectiveness validation.

    Contains pass/fail status, actual metrics, gaps to targets,
    and detailed breakdown by various dimensions.
    """

    # Overall validation
    passed: bool = False
    effectiveness: float = 0.0
    target: float = 0.80

    # Gap analysis
    gap: float = 0.0
    gap_pct: float = 0.0

    # Confidence levels
    confidence_level: str = 'low'  # 'low', 'medium', 'high', 'very_high'
    statistical_significance: bool = False
    p_value: Optional[float] = None

    # Sample info
    total_trades: int = 0
    valid_trades: int = 0
    filtered_trades: int = 0

    # Detailed metrics
    actual_metrics: Dict[str, float] = field(default_factory=dict)
    target_metrics: Dict[str, float] = field(default_factory=dict)
    gaps: Dict[str, float] = field(default_factory=dict)

    # Breakdowns
    per_symbol: Dict[str, Dict[str, Any]] = field(default_factory=dict)
    per_regime: Dict[str, Dict[str, Any]] = field(default_factory=dict)
    per_confidence_band: Dict[str, Dict[str, Any]] = field(default_factory=dict)

    # Recommendations
    recommendations: List[str] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        return {
            'passed': self.passed,
            'effectiveness': float(self.effectiveness),
            'target': float(self.target),
            'gap': float(self.gap),
            'gap_pct': float(self.gap_pct),
            'confidence_level': self.confidence_level,
            'statistical_significance': self.statistical_significance,
            'p_value': self.p_value,
            'total_trades': self.total_trades,
            'valid_trades': self.valid_trades,
            'actual_metrics': self.actual_metrics,
            'target_metrics': self.target_metrics,
            'gaps': self.gaps,
            'per_symbol': self.per_symbol,
            'per_regime': self.per_regime,
            'per_confidence_band': self.per_confidence_band,
            'recommendations': self.recommendations
        }

    def print_summary(self):
        """Print formatted validation summary."""
        status = "PASSED" if self.passed else "FAILED"
        status_color = "green" if self.passed else "red"

        print("\n" + "=" * 70)
        print(f"EFFECTIVENESS VALIDATION: {status}")
        print("=" * 70)

        print(f"\nOverall Effectiveness: {self.effectiveness:.2%}")
        print(f"Target: {self.target:.2%}")
        print(f"Gap: {self.gap:.2%} ({self.gap_pct:+.1f}%)")
        print(f"Confidence Level: {self.confidence_level.upper()}")

        print(f"\nSample Size:")
        print(f"  Total Trades: {self.total_trades}")
        print(f"  Valid Trades: {self.valid_trades}")
        print(f"  Filtered: {self.filtered_trades}")

        if self.statistical_significance:
            print(f"\nStatistically Significant (p={self.p_value:.4f})")
        else:
            print(f"\nNOT Statistically Significant (p={self.p_value:.4f})")

        if self.per_symbol:
            print("\n--- Per Symbol ---")
            for symbol, stats in self.per_symbol.items():
                eff = stats.get('effectiveness', 0)
                n = stats.get('trades', 0)
                passed = stats.get('passed', False)
                status = "PASS" if passed else "FAIL"
                print(f"  {symbol}: {eff:.2%} (n={n}) [{status}]")

        if self.per_regime:
            print("\n--- Per Regime ---")
            for regime, stats in self.per_regime.items():
                eff = stats.get('effectiveness', 0)
                n = stats.get('trades', 0)
                passed = stats.get('passed', False)
                status = "PASS" if passed else "FAIL"
                print(f"  {regime}: {eff:.2%} (n={n}) [{status}]")

        if self.per_confidence_band:
            print("\n--- Per Confidence Band ---")
            for band, stats in sorted(self.per_confidence_band.items()):
                eff = stats.get('effectiveness', 0)
                n = stats.get('trades', 0)
                passed = stats.get('passed', False)
                status = "PASS" if passed else "FAIL"
                print(f"  {band}: {eff:.2%} (n={n}) [{status}]")

        if self.recommendations:
            print("\n--- Recommendations ---")
            for i, rec in enumerate(self.recommendations, 1):
                print(f"  {i}. {rec}")

        print("=" * 70 + "\n")


class EffectivenessValidator:
    """
    Validates trading strategy effectiveness against targets.

    The validator checks if a trading strategy meets the 80%
    effectiveness target across different dimensions:
    - Overall effectiveness
    - Per-symbol effectiveness
    - Per-regime effectiveness (bull/bear/sideways)
    - Confidence-filtered effectiveness

    Usage:
        validator = EffectivenessValidator(target=0.80)

        # Validate from backtest result
        result = validator.validate_strategy(backtest_result)

        # Validate from trades list
        result = validator.validate_trades(trades)

        # Print summary
        result.print_summary()
    """

    DEFAULT_TARGET = 0.80
    MIN_TRADES_FOR_SIGNIFICANCE = 30
    CONFIDENCE_BANDS = [0.5, 0.6, 0.7, 0.8, 0.9]

    def __init__(
        self,
        target: float = 0.80,
        min_trades: int = 30,
        confidence_bands: Optional[List[float]] = None,
        regime_field: str = 'volatility_regime',
        symbol_field: str = 'symbol'
    ):
        """
        Initialize the effectiveness validator.

        Args:
            target: Target effectiveness rate (default 80%)
            min_trades: Minimum trades for statistical significance
            confidence_bands: Confidence thresholds for filtering
            regime_field: Field name for regime in trades
            symbol_field: Field name for symbol in trades
        """
        self.target = target
        self.min_trades = min_trades
        self.confidence_bands = confidence_bands or self.CONFIDENCE_BANDS
        self.regime_field = regime_field
        self.symbol_field = symbol_field
        self.metrics_calculator = MetricsCalculator()

        logger.info(f"EffectivenessValidator initialized with target={target:.0%}")

    def validate_strategy(
        self,
        backtest_result: Any,
        include_breakdowns: bool = True
    ) -> ValidationResult:
        """
        Validate strategy effectiveness from backtest result.

        Args:
            backtest_result: BacktestResult object with trades
            include_breakdowns: Whether to include per-symbol/regime breakdowns

        Returns:
            ValidationResult with pass/fail and detailed metrics
        """
        trades = getattr(backtest_result, 'trades', [])
        return self.validate_trades(trades, include_breakdowns)

    def validate_trades(
        self,
        trades: List[TradeRecord],
        include_breakdowns: bool = True
    ) -> ValidationResult:
        """
        Validate effectiveness from list of trades.

        Args:
            trades: List of TradeRecord objects
            include_breakdowns: Whether to include per-symbol/regime breakdowns

        Returns:
            ValidationResult with pass/fail and detailed metrics
        """
        result = ValidationResult(target=self.target)

        # Filter closed trades
        closed_trades = [t for t in trades if t.result != 'open']
        result.total_trades = len(trades)
        result.valid_trades = len(closed_trades)
        result.filtered_trades = len(trades) - len(closed_trades)

        if not closed_trades:
            result.recommendations.append("No closed trades available for validation")
            return result

        # Calculate overall effectiveness (win rate)
        winning_trades = sum(1 for t in closed_trades if t.pnl > 0)
        result.effectiveness = winning_trades / len(closed_trades)

        # Calculate gap
        result.gap = result.effectiveness - self.target
        result.gap_pct = (result.gap / self.target) * 100 if self.target > 0 else 0

        # Determine if passed
        result.passed = result.effectiveness >= self.target

        # Statistical significance test
        result.statistical_significance, result.p_value = self._test_significance(
            winning_trades, len(closed_trades), self.target
        )

        # Confidence level
        result.confidence_level = self._determine_confidence_level(
            result.effectiveness, len(closed_trades), result.p_value
        )

        # Actual vs target metrics
        result.actual_metrics = {
            'effectiveness': result.effectiveness,
            'win_rate': result.effectiveness,
            'total_trades': len(closed_trades),
            'winning_trades': winning_trades,
            'losing_trades': len(closed_trades) - winning_trades
        }

        result.target_metrics = {
            'effectiveness': self.target,
            'min_trades': self.min_trades
        }

        result.gaps = {
            'effectiveness': result.gap,
            'trades_gap': max(0, self.min_trades - len(closed_trades))
        }

        # Breakdowns
        if include_breakdowns:
            result.per_symbol = self._validate_per_symbol(closed_trades)
            result.per_regime = self._validate_per_regime(closed_trades)
            result.per_confidence_band = self._validate_per_confidence_band(closed_trades)

        # Generate recommendations
        result.recommendations = self._generate_recommendations(result, closed_trades)

        return result

    def validate_high_confidence_trades(
        self,
        trades: List[TradeRecord],
        confidence_threshold: float = 0.70
    ) -> ValidationResult:
        """
        Validate effectiveness for high-confidence trades only.

        High-confidence trades should have a higher effectiveness rate
        as the model is more certain about these predictions.

        Args:
            trades: List of TradeRecord objects
            confidence_threshold: Minimum confidence for inclusion

        Returns:
            ValidationResult for high-confidence trades
        """
        # Filter by confidence
        high_conf_trades = [
            t for t in trades
            if getattr(t, 'confidence', 0) >= confidence_threshold
            or getattr(t, 'prob_tp_first', 0) >= confidence_threshold
        ]

        result = self.validate_trades(high_conf_trades, include_breakdowns=False)
        result.actual_metrics['confidence_threshold'] = confidence_threshold
        result.actual_metrics['original_trades'] = len(trades)
        result.actual_metrics['filtered_trades'] = len(high_conf_trades)
        result.actual_metrics['filter_ratio'] = len(high_conf_trades) / len(trades) if trades else 0

        return result

    def _validate_per_symbol(
        self,
        trades: List[TradeRecord]
    ) -> Dict[str, Dict[str, Any]]:
        """Validate effectiveness per trading symbol."""
        per_symbol = {}

        # Group by symbol (try multiple possible fields)
        symbols = {}
        for t in trades:
            symbol = getattr(t, 'symbol', None) or getattr(t, 'horizon', 'unknown')
            if symbol not in symbols:
                symbols[symbol] = []
            symbols[symbol].append(t)

        for symbol, symbol_trades in symbols.items():
            winning = sum(1 for t in symbol_trades if t.pnl > 0)
            effectiveness = winning / len(symbol_trades) if symbol_trades else 0

            per_symbol[symbol] = {
                'effectiveness': effectiveness,
                'trades': len(symbol_trades),
                'winning': winning,
                'losing': len(symbol_trades) - winning,
                'passed': effectiveness >= self.target,
                'gap': effectiveness - self.target
            }

        return per_symbol

    def _validate_per_regime(
        self,
        trades: List[TradeRecord]
    ) -> Dict[str, Dict[str, Any]]:
        """Validate effectiveness per market regime."""
        per_regime = {}

        # Group by regime
        regimes = {}
        for t in trades:
            regime = getattr(t, self.regime_field, None) or getattr(t, 'amd_phase', 'unknown')
            if regime is None:
                regime = 'unknown'
            if regime not in regimes:
                regimes[regime] = []
            regimes[regime].append(t)

        for regime, regime_trades in regimes.items():
            winning = sum(1 for t in regime_trades if t.pnl > 0)
            effectiveness = winning / len(regime_trades) if regime_trades else 0

            per_regime[regime] = {
                'effectiveness': effectiveness,
                'trades': len(regime_trades),
                'winning': winning,
                'losing': len(regime_trades) - winning,
                'passed': effectiveness >= self.target,
                'gap': effectiveness - self.target
            }

        return per_regime

    def _validate_per_confidence_band(
        self,
        trades: List[TradeRecord]
    ) -> Dict[str, Dict[str, Any]]:
        """Validate effectiveness per confidence band."""
        per_band = {}

        # Get confidence values
        confidences = []
        for t in trades:
            conf = getattr(t, 'confidence', 0) or getattr(t, 'prob_tp_first', 0) or 0
            confidences.append(conf)

        # Group by confidence bands
        bands = self.confidence_bands + [1.0]  # Add upper bound

        for i in range(len(bands) - 1):
            lower = bands[i]
            upper = bands[i + 1]
            band_name = f"{lower:.0%}-{upper:.0%}"

            band_trades = [
                t for t, c in zip(trades, confidences)
                if lower <= c < upper
            ]

            if band_trades:
                winning = sum(1 for t in band_trades if t.pnl > 0)
                effectiveness = winning / len(band_trades)

                per_band[band_name] = {
                    'effectiveness': effectiveness,
                    'trades': len(band_trades),
                    'winning': winning,
                    'losing': len(band_trades) - winning,
                    'passed': effectiveness >= self.target,
                    'gap': effectiveness - self.target,
                    'confidence_range': (lower, upper)
                }

        return per_band

    def _test_significance(
        self,
        successes: int,
        total: int,
        target: float
    ) -> Tuple[bool, float]:
        """
        Test if effectiveness is statistically significant.

        Uses binomial test to check if observed win rate
        is significantly different from target.

        Args:
            successes: Number of winning trades
            total: Total number of trades
            target: Target effectiveness rate

        Returns:
            Tuple of (is_significant, p_value)
        """
        if total < self.min_trades:
            return False, 1.0

        try:
            from scipy import stats
            # One-sided test: is effectiveness >= target?
            result = stats.binom_test(successes, total, target, alternative='greater')
            return result < 0.05, result
        except ImportError:
            # Fallback: simple approximation using normal distribution
            p_hat = successes / total
            se = np.sqrt(target * (1 - target) / total)
            z = (p_hat - target) / se if se > 0 else 0
            p_value = 1 - 0.5 * (1 + np.math.erf(z / np.sqrt(2)))
            return p_value < 0.05, p_value
        except Exception:
            return False, 1.0

    def _determine_confidence_level(
        self,
        effectiveness: float,
        n_trades: int,
        p_value: Optional[float]
    ) -> str:
        """
        Determine confidence level of the validation result.

        Args:
            effectiveness: Observed effectiveness
            n_trades: Number of trades
            p_value: Statistical p-value

        Returns:
            Confidence level string
        """
        if n_trades < 10:
            return 'very_low'
        elif n_trades < self.min_trades:
            return 'low'
        elif p_value is not None and p_value > 0.10:
            return 'medium'
        elif p_value is not None and p_value <= 0.01:
            return 'very_high'
        elif p_value is not None and p_value <= 0.05:
            return 'high'
        else:
            return 'medium'

    def _generate_recommendations(
        self,
        result: ValidationResult,
        trades: List[TradeRecord]
    ) -> List[str]:
        """
        Generate actionable recommendations based on validation results.

        Args:
            result: Current validation result
            trades: List of trades analyzed

        Returns:
            List of recommendation strings
        """
        recommendations = []

        # Sample size recommendations
        if result.valid_trades < self.min_trades:
            recommendations.append(
                f"Increase sample size: {result.valid_trades} trades is below "
                f"minimum {self.min_trades} for statistical significance"
            )

        # Overall effectiveness recommendations
        if not result.passed:
            gap_pct = abs(result.gap_pct)
            if gap_pct > 20:
                recommendations.append(
                    f"Major improvement needed: effectiveness is {gap_pct:.0f}% below target"
                )
            elif gap_pct > 10:
                recommendations.append(
                    f"Moderate improvement needed: effectiveness is {gap_pct:.0f}% below target"
                )
            else:
                recommendations.append(
                    f"Minor tuning needed: effectiveness is only {gap_pct:.0f}% below target"
                )

        # Per-regime recommendations
        if result.per_regime:
            failing_regimes = [
                regime for regime, stats in result.per_regime.items()
                if not stats.get('passed', False) and stats.get('trades', 0) >= 5
            ]
            if failing_regimes:
                recommendations.append(
                    f"Focus on improving performance in regimes: {', '.join(failing_regimes)}"
                )

        # Confidence band recommendations
        if result.per_confidence_band:
            # Check if high confidence trades perform better
            high_conf_bands = {
                k: v for k, v in result.per_confidence_band.items()
                if v.get('confidence_range', (0, 0))[0] >= 0.7
            }
            low_conf_bands = {
                k: v for k, v in result.per_confidence_band.items()
                if v.get('confidence_range', (1, 1))[1] <= 0.6
            }

            if high_conf_bands and low_conf_bands:
                high_eff = np.mean([v['effectiveness'] for v in high_conf_bands.values()])
                low_eff = np.mean([v['effectiveness'] for v in low_conf_bands.values()])

                if high_eff > low_eff + 0.10:
                    recommendations.append(
                        f"Consider filtering low-confidence trades: "
                        f"high-conf={high_eff:.0%} vs low-conf={low_eff:.0%}"
                    )
                elif high_eff <= low_eff:
                    recommendations.append(
                        "Confidence scores may need recalibration: "
                        "high-confidence trades not outperforming low-confidence"
                    )

        # Statistical significance recommendations
        if not result.statistical_significance:
            recommendations.append(
                "Results are not statistically significant - "
                "consider gathering more data before making decisions"
            )

        return recommendations

    def compare_strategies(
        self,
        results: Dict[str, ValidationResult]
    ) -> Dict[str, Any]:
        """
        Compare effectiveness across multiple strategies.

        Args:
            results: Dictionary mapping strategy names to ValidationResults

        Returns:
            Comparison summary
        """
        comparison = {
            'strategies': {},
            'best_overall': None,
            'best_effectiveness': 0,
            'all_passing': True
        }

        for name, result in results.items():
            comparison['strategies'][name] = {
                'effectiveness': result.effectiveness,
                'passed': result.passed,
                'trades': result.valid_trades,
                'confidence': result.confidence_level
            }

            if result.effectiveness > comparison['best_effectiveness']:
                comparison['best_effectiveness'] = result.effectiveness
                comparison['best_overall'] = name

            if not result.passed:
                comparison['all_passing'] = False

        return comparison


def validate_backtest_effectiveness(
    backtest_result: Any,
    target: float = 0.80,
    confidence_threshold: float = 0.70
) -> Tuple[ValidationResult, ValidationResult]:
    """
    Convenience function to validate backtest effectiveness.

    Validates both overall effectiveness and high-confidence
    trades effectiveness.

    Args:
        backtest_result: BacktestResult object
        target: Target effectiveness rate
        confidence_threshold: Threshold for high-confidence filtering

    Returns:
        Tuple of (overall_result, high_confidence_result)
    """
    validator = EffectivenessValidator(target=target)

    overall_result = validator.validate_strategy(backtest_result)
    high_conf_result = validator.validate_high_confidence_trades(
        backtest_result.trades,
        confidence_threshold=confidence_threshold
    )

    return overall_result, high_conf_result


if __name__ == "__main__":
    # Test effectiveness validator
    from datetime import datetime, timedelta
    import random

    print("Testing EffectivenessValidator")
    print("=" * 60)

    # Generate sample trades
    trades = []
    base_time = datetime(2024, 1, 1, 9, 0)

    random.seed(42)
    for i in range(150):
        # Higher confidence trades have higher win rate
        confidence = random.uniform(0.5, 0.9)
        base_win_prob = 0.65 + (confidence - 0.5) * 0.3  # 65-80% based on conf

        is_win = random.random() < base_win_prob

        result = 'tp' if is_win else 'sl'
        pnl = 10.0 if is_win else -5.0
        pnl_r = 2.0 if is_win else -1.0

        entry_time = base_time + timedelta(hours=i * 2)

        trade = TradeRecord(
            id=i,
            entry_time=entry_time,
            exit_time=entry_time + timedelta(minutes=random.randint(5, 60)),
            direction=random.choice(['long', 'short']),
            entry_price=2000.0,
            exit_price=2000.0 + pnl,
            sl_price=1995.0,
            tp_price=2010.0,
            sl_distance=5.0,
            tp_distance=10.0,
            rr_config='rr_2_1',
            result=result,
            pnl=pnl,
            pnl_r=pnl_r,
            duration_minutes=random.randint(5, 60),
            horizon=random.choice(['15m', '1h']),
            amd_phase=random.choice(['accumulation', 'manipulation', 'distribution']),
            volatility_regime=random.choice(['low', 'medium', 'high']),
            confidence=confidence,
            prob_tp_first=confidence
        )
        trades.append(trade)

    # Validate
    validator = EffectivenessValidator(target=0.80)

    print("\n--- Overall Validation ---")
    result = validator.validate_trades(trades)
    result.print_summary()

    print("\n--- High Confidence Validation (>70%) ---")
    high_conf_result = validator.validate_high_confidence_trades(trades, 0.70)
    print(f"Effectiveness: {high_conf_result.effectiveness:.2%}")
    print(f"Trades: {high_conf_result.valid_trades}")
    print(f"Passed: {high_conf_result.passed}")

    print("\n--- High Confidence Validation (>80%) ---")
    very_high_conf_result = validator.validate_high_confidence_trades(trades, 0.80)
    print(f"Effectiveness: {very_high_conf_result.effectiveness:.2%}")
    print(f"Trades: {very_high_conf_result.valid_trades}")
    print(f"Passed: {very_high_conf_result.passed}")

    print("\nEffectivenessValidator test complete!")
