"""
Trading Metrics - Phase 2
Comprehensive metrics for trading performance evaluation
"""

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


@dataclass
class TradingMetrics:
    """Complete trading metrics for Phase 2"""

    # Basic counts
    total_trades: int = 0
    winning_trades: int = 0
    losing_trades: int = 0
    breakeven_trades: int = 0

    # Win rate
    winrate: float = 0.0

    # Profit metrics
    gross_profit: float = 0.0
    gross_loss: float = 0.0
    net_profit: float = 0.0
    profit_factor: float = 0.0

    # Average metrics
    avg_win: float = 0.0
    avg_loss: float = 0.0
    avg_trade: float = 0.0
    avg_rr_achieved: float = 0.0

    # Extremes
    largest_win: float = 0.0
    largest_loss: float = 0.0

    # Risk metrics
    max_drawdown: float = 0.0
    max_drawdown_pct: float = 0.0
    max_drawdown_duration: int = 0  # In bars/trades

    # Streaks
    max_consecutive_wins: int = 0
    max_consecutive_losses: int = 0
    current_streak: int = 0

    # Advanced ratios
    sharpe_ratio: float = 0.0
    sortino_ratio: float = 0.0
    calmar_ratio: float = 0.0

    # Win rate by R:R
    winrate_by_rr: Dict[str, float] = field(default_factory=dict)

    # Duration
    avg_trade_duration: float = 0.0  # In minutes
    avg_win_duration: float = 0.0
    avg_loss_duration: float = 0.0

    # Time period
    start_date: Optional[datetime] = None
    end_date: Optional[datetime] = None
    trading_days: int = 0

    def to_dict(self) -> Dict:
        """Convert to dictionary"""
        return {
            'total_trades': self.total_trades,
            'winning_trades': self.winning_trades,
            'losing_trades': self.losing_trades,
            'winrate': self.winrate,
            'gross_profit': self.gross_profit,
            'gross_loss': self.gross_loss,
            'net_profit': self.net_profit,
            'profit_factor': self.profit_factor,
            'avg_win': self.avg_win,
            'avg_loss': self.avg_loss,
            'avg_trade': self.avg_trade,
            'avg_rr_achieved': self.avg_rr_achieved,
            'largest_win': self.largest_win,
            'largest_loss': self.largest_loss,
            'max_drawdown': self.max_drawdown,
            'max_drawdown_pct': self.max_drawdown_pct,
            'max_consecutive_wins': self.max_consecutive_wins,
            'max_consecutive_losses': self.max_consecutive_losses,
            'sharpe_ratio': self.sharpe_ratio,
            'sortino_ratio': self.sortino_ratio,
            'calmar_ratio': self.calmar_ratio,
            'winrate_by_rr': self.winrate_by_rr,
            'avg_trade_duration': self.avg_trade_duration
        }

    def print_summary(self):
        """Print formatted summary"""
        print("\n" + "="*50)
        print("TRADING METRICS SUMMARY")
        print("="*50)
        print(f"Total Trades: {self.total_trades}")
        print(f"Win Rate: {self.winrate:.2%}")
        print(f"Profit Factor: {self.profit_factor:.2f}")
        print(f"\nNet Profit: ${self.net_profit:,.2f}")
        print(f"Gross Profit: ${self.gross_profit:,.2f}")
        print(f"Gross Loss: ${self.gross_loss:,.2f}")
        print(f"\nAvg Win: ${self.avg_win:,.2f}")
        print(f"Avg Loss: ${self.avg_loss:,.2f}")
        print(f"Avg R:R Achieved: {self.avg_rr_achieved:.2f}")
        print(f"\nMax Drawdown: ${self.max_drawdown:,.2f} ({self.max_drawdown_pct:.2%})")
        print(f"Max Consecutive Losses: {self.max_consecutive_losses}")
        print(f"\nSharpe Ratio: {self.sharpe_ratio:.2f}")
        print(f"Sortino Ratio: {self.sortino_ratio:.2f}")

        if self.winrate_by_rr:
            print("\nWin Rate by R:R:")
            for rr, rate in self.winrate_by_rr.items():
                print(f"  {rr}: {rate:.2%}")

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


@dataclass
class TradeRecord:
    """Individual trade record"""
    id: int
    entry_time: datetime
    exit_time: Optional[datetime] = None
    direction: str = 'long'       # 'long' or 'short'
    entry_price: float = 0.0
    exit_price: float = 0.0
    sl_price: float = 0.0
    tp_price: float = 0.0
    sl_distance: float = 0.0
    tp_distance: float = 0.0
    rr_config: str = 'rr_2_1'
    result: str = 'open'          # 'tp', 'sl', 'timeout', 'open'
    pnl: float = 0.0
    pnl_pct: float = 0.0
    pnl_r: float = 0.0           # PnL in R units
    duration_minutes: float = 0.0
    horizon: str = '15m'
    amd_phase: Optional[str] = None
    volatility_regime: Optional[str] = None
    confidence: float = 0.0
    prob_tp_first: float = 0.0

    def to_dict(self) -> Dict:
        return {
            'id': self.id,
            'entry_time': self.entry_time.isoformat() if self.entry_time else None,
            'exit_time': self.exit_time.isoformat() if self.exit_time else None,
            'direction': self.direction,
            'entry_price': self.entry_price,
            'exit_price': self.exit_price,
            'sl_price': self.sl_price,
            'tp_price': self.tp_price,
            'rr_config': self.rr_config,
            'result': self.result,
            'pnl': self.pnl,
            'pnl_r': self.pnl_r,
            'duration_minutes': self.duration_minutes,
            'horizon': self.horizon,
            'amd_phase': self.amd_phase,
            'volatility_regime': self.volatility_regime,
            'confidence': self.confidence,
            'prob_tp_first': self.prob_tp_first
        }


class MetricsCalculator:
    """Calculator for trading metrics"""

    def __init__(self, risk_free_rate: float = 0.02):
        """
        Initialize calculator

        Args:
            risk_free_rate: Annual risk-free rate for Sharpe calculation
        """
        self.risk_free_rate = risk_free_rate

    def calculate_metrics(
        self,
        trades: List[TradeRecord],
        initial_capital: float = 10000.0
    ) -> TradingMetrics:
        """
        Calculate all trading metrics from trade list

        Args:
            trades: List of TradeRecord objects
            initial_capital: Starting capital

        Returns:
            TradingMetrics object
        """
        if not trades:
            return TradingMetrics()

        metrics = TradingMetrics()

        # Filter closed trades
        closed_trades = [t for t in trades if t.result != 'open']
        if not closed_trades:
            return metrics

        # Basic counts
        metrics.total_trades = len(closed_trades)

        pnls = [t.pnl for t in closed_trades]
        pnl_array = np.array(pnls)

        metrics.winning_trades = sum(1 for pnl in pnls if pnl > 0)
        metrics.losing_trades = sum(1 for pnl in pnls if pnl < 0)
        metrics.breakeven_trades = sum(1 for pnl in pnls if pnl == 0)

        # Win rate
        metrics.winrate = metrics.winning_trades / metrics.total_trades if metrics.total_trades > 0 else 0

        # Profit metrics
        wins = [pnl for pnl in pnls if pnl > 0]
        losses = [pnl for pnl in pnls if pnl < 0]

        metrics.gross_profit = sum(wins) if wins else 0
        metrics.gross_loss = abs(sum(losses)) if losses else 0
        metrics.net_profit = metrics.gross_profit - metrics.gross_loss
        metrics.profit_factor = metrics.gross_profit / metrics.gross_loss if metrics.gross_loss > 0 else float('inf')

        # Averages
        metrics.avg_win = np.mean(wins) if wins else 0
        metrics.avg_loss = abs(np.mean(losses)) if losses else 0
        metrics.avg_trade = np.mean(pnls)

        # R:R achieved
        r_values = [t.pnl_r for t in closed_trades if t.pnl_r != 0]
        metrics.avg_rr_achieved = np.mean(r_values) if r_values else 0

        # Extremes
        metrics.largest_win = max(pnls) if pnls else 0
        metrics.largest_loss = min(pnls) if pnls else 0

        # Streaks
        metrics.max_consecutive_wins, metrics.max_consecutive_losses = self._calculate_streaks(pnls)

        # Drawdown
        equity_curve = self._calculate_equity_curve(pnls, initial_capital)
        metrics.max_drawdown, metrics.max_drawdown_pct, metrics.max_drawdown_duration = \
            self._calculate_drawdown(equity_curve, initial_capital)

        # Risk-adjusted returns
        metrics.sharpe_ratio = self._calculate_sharpe(pnls, initial_capital)
        metrics.sortino_ratio = self._calculate_sortino(pnls, initial_capital)
        metrics.calmar_ratio = self._calculate_calmar(pnls, metrics.max_drawdown, initial_capital)

        # Win rate by R:R
        metrics.winrate_by_rr = self.calculate_winrate_by_rr(closed_trades)

        # Duration
        durations = [t.duration_minutes for t in closed_trades if t.duration_minutes > 0]
        if durations:
            metrics.avg_trade_duration = np.mean(durations)

        win_durations = [t.duration_minutes for t in closed_trades if t.pnl > 0 and t.duration_minutes > 0]
        loss_durations = [t.duration_minutes for t in closed_trades if t.pnl < 0 and t.duration_minutes > 0]

        metrics.avg_win_duration = np.mean(win_durations) if win_durations else 0
        metrics.avg_loss_duration = np.mean(loss_durations) if loss_durations else 0

        # Time period
        if closed_trades:
            times = [t.entry_time for t in closed_trades if t.entry_time]
            if times:
                metrics.start_date = min(times)
                metrics.end_date = max(times)
                metrics.trading_days = (metrics.end_date - metrics.start_date).days

        return metrics

    def calculate_winrate_by_rr(
        self,
        trades: List[TradeRecord],
        rr_configs: List[str] = None
    ) -> Dict[str, float]:
        """
        Calculate win rate for each R:R configuration

        Args:
            trades: List of trade records
            rr_configs: List of R:R config names to calculate

        Returns:
            Dictionary mapping R:R config to win rate
        """
        if not trades:
            return {}

        if rr_configs is None:
            rr_configs = list(set(t.rr_config for t in trades))

        winrates = {}
        for rr in rr_configs:
            rr_trades = [t for t in trades if t.rr_config == rr]
            if rr_trades:
                wins = sum(1 for t in rr_trades if t.pnl > 0)
                winrates[rr] = wins / len(rr_trades)
            else:
                winrates[rr] = 0.0

        return winrates

    def calculate_profit_factor(
        self,
        trades: List[TradeRecord]
    ) -> float:
        """Calculate profit factor"""
        if not trades:
            return 0.0

        gross_profit = sum(t.pnl for t in trades if t.pnl > 0)
        gross_loss = abs(sum(t.pnl for t in trades if t.pnl < 0))

        if gross_loss == 0:
            return float('inf') if gross_profit > 0 else 0.0

        return gross_profit / gross_loss

    def segment_metrics(
        self,
        trades: List[TradeRecord],
        initial_capital: float = 10000.0
    ) -> Dict[str, Dict[str, TradingMetrics]]:
        """
        Calculate metrics segmented by different factors

        Args:
            trades: List of trade records
            initial_capital: Starting capital

        Returns:
            Nested dictionary with segmented metrics
        """
        segments = {
            'by_horizon': {},
            'by_rr_config': {},
            'by_amd_phase': {},
            'by_volatility': {},
            'by_direction': {}
        }

        if not trades:
            return segments

        # By horizon
        horizons = set(t.horizon for t in trades)
        for h in horizons:
            h_trades = [t for t in trades if t.horizon == h]
            segments['by_horizon'][h] = self.calculate_metrics(h_trades, initial_capital)

        # By R:R config
        rr_configs = set(t.rr_config for t in trades)
        for rr in rr_configs:
            rr_trades = [t for t in trades if t.rr_config == rr]
            segments['by_rr_config'][rr] = self.calculate_metrics(rr_trades, initial_capital)

        # By AMD phase
        phases = set(t.amd_phase for t in trades if t.amd_phase)
        for phase in phases:
            phase_trades = [t for t in trades if t.amd_phase == phase]
            segments['by_amd_phase'][phase] = self.calculate_metrics(phase_trades, initial_capital)

        # By volatility regime
        regimes = set(t.volatility_regime for t in trades if t.volatility_regime)
        for regime in regimes:
            regime_trades = [t for t in trades if t.volatility_regime == regime]
            segments['by_volatility'][regime] = self.calculate_metrics(regime_trades, initial_capital)

        # By direction
        for direction in ['long', 'short']:
            dir_trades = [t for t in trades if t.direction == direction]
            if dir_trades:
                segments['by_direction'][direction] = self.calculate_metrics(dir_trades, initial_capital)

        return segments

    def _calculate_equity_curve(
        self,
        pnls: List[float],
        initial_capital: float
    ) -> np.ndarray:
        """Calculate cumulative equity curve"""
        equity = np.zeros(len(pnls) + 1)
        equity[0] = initial_capital
        for i, pnl in enumerate(pnls):
            equity[i + 1] = equity[i] + pnl
        return equity

    def _calculate_drawdown(
        self,
        equity_curve: np.ndarray,
        initial_capital: float
    ) -> Tuple[float, float, int]:
        """Calculate maximum drawdown and duration"""
        # Running maximum
        running_max = np.maximum.accumulate(equity_curve)

        # Drawdown at each point
        drawdown = running_max - equity_curve
        drawdown_pct = drawdown / running_max

        # Maximum drawdown
        max_dd = np.max(drawdown)
        max_dd_pct = np.max(drawdown_pct)

        # Drawdown duration (longest period below peak)
        in_drawdown = drawdown > 0
        max_duration = 0
        current_duration = 0

        for in_dd in in_drawdown:
            if in_dd:
                current_duration += 1
                max_duration = max(max_duration, current_duration)
            else:
                current_duration = 0

        return max_dd, max_dd_pct, max_duration

    def _calculate_streaks(self, pnls: List[float]) -> Tuple[int, int]:
        """Calculate maximum win and loss streaks"""
        max_wins = 0
        max_losses = 0
        current_wins = 0
        current_losses = 0

        for pnl in pnls:
            if pnl > 0:
                current_wins += 1
                current_losses = 0
                max_wins = max(max_wins, current_wins)
            elif pnl < 0:
                current_losses += 1
                current_wins = 0
                max_losses = max(max_losses, current_losses)
            else:
                current_wins = 0
                current_losses = 0

        return max_wins, max_losses

    def _calculate_sharpe(
        self,
        pnls: List[float],
        initial_capital: float,
        periods_per_year: int = 252
    ) -> float:
        """Calculate Sharpe ratio"""
        if len(pnls) < 2:
            return 0.0

        returns = np.array(pnls) / initial_capital
        mean_return = np.mean(returns)
        std_return = np.std(returns)

        if std_return == 0:
            return 0.0

        # Annualized Sharpe
        excess_return = mean_return - (self.risk_free_rate / periods_per_year)
        sharpe = (excess_return / std_return) * np.sqrt(periods_per_year)

        return sharpe

    def _calculate_sortino(
        self,
        pnls: List[float],
        initial_capital: float,
        periods_per_year: int = 252
    ) -> float:
        """Calculate Sortino ratio (only downside deviation)"""
        if len(pnls) < 2:
            return 0.0

        returns = np.array(pnls) / initial_capital
        mean_return = np.mean(returns)

        # Downside deviation (only negative returns)
        negative_returns = returns[returns < 0]
        if len(negative_returns) == 0:
            return float('inf') if mean_return > 0 else 0.0

        downside_std = np.std(negative_returns)
        if downside_std == 0:
            return 0.0

        excess_return = mean_return - (self.risk_free_rate / periods_per_year)
        sortino = (excess_return / downside_std) * np.sqrt(periods_per_year)

        return sortino

    def _calculate_calmar(
        self,
        pnls: List[float],
        max_drawdown: float,
        initial_capital: float
    ) -> float:
        """Calculate Calmar ratio (return / max drawdown)"""
        if max_drawdown == 0:
            return 0.0

        total_return = sum(pnls) / initial_capital
        calmar = total_return / (max_drawdown / initial_capital)

        return calmar


if __name__ == "__main__":
    # Test metrics calculator
    from datetime import datetime, timedelta
    import random

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

    for i in range(100):
        # Random outcome
        result = random.choices(['tp', 'sl'], weights=[0.45, 0.55])[0]

        sl_dist = 5.0
        tp_dist = 10.0

        if result == 'tp':
            pnl = tp_dist
            pnl_r = 2.0
        else:
            pnl = -sl_dist
            pnl_r = -1.0

        entry_time = base_time + timedelta(hours=i * 2)
        exit_time = entry_time + timedelta(minutes=random.randint(5, 60))

        trade = TradeRecord(
            id=i,
            entry_time=entry_time,
            exit_time=exit_time,
            direction='long',
            entry_price=2000.0,
            exit_price=2000.0 + pnl,
            sl_price=2000.0 - sl_dist,
            tp_price=2000.0 + tp_dist,
            sl_distance=sl_dist,
            tp_distance=tp_dist,
            rr_config='rr_2_1',
            result=result,
            pnl=pnl,
            pnl_r=pnl_r,
            duration_minutes=(exit_time - entry_time).seconds / 60,
            horizon='15m',
            amd_phase=random.choice(['accumulation', 'manipulation', 'distribution']),
            volatility_regime=random.choice(['low', 'medium', 'high']),
            confidence=random.uniform(0.5, 0.8),
            prob_tp_first=random.uniform(0.4, 0.7)
        )
        trades.append(trade)

    # Calculate metrics
    calculator = MetricsCalculator()
    metrics = calculator.calculate_metrics(trades, initial_capital=10000)

    # Print summary
    metrics.print_summary()

    # Segmented metrics
    print("\n=== Segmented Metrics ===")
    segments = calculator.segment_metrics(trades, initial_capital=10000)

    print("\nBy AMD Phase:")
    for phase, m in segments['by_amd_phase'].items():
        print(f"  {phase}: WR={m.winrate:.2%}, PF={m.profit_factor:.2f}, N={m.total_trades}")

    print("\nBy Volatility:")
    for regime, m in segments['by_volatility'].items():
        print(f"  {regime}: WR={m.winrate:.2%}, PF={m.profit_factor:.2f}, N={m.total_trades}")
