import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
import logging

logger = logging.getLogger(__name__)

class PerformanceAnalyzer:
    """
    Comprehensive performance analysis for trading strategies and portfolios
    """
    
    def __init__(self, risk_free_rate: float = 0.02):
        self.risk_free_rate = risk_free_rate
        
    def calculate_metrics(
        self, 
        portfolio_id: str,
        start_date: datetime,
        end_date: datetime
    ) -> Dict:
        """
        Calculate comprehensive performance metrics
        """
        # In real implementation, would fetch data from database
        # For now, generate sample data
        returns = self._generate_sample_returns(start_date, end_date)
        
        metrics = {
            'total_return': self._calculate_total_return(returns),
            'annualized_return': self._calculate_annualized_return(returns),
            'volatility': self._calculate_volatility(returns),
            'sharpe_ratio': self._calculate_sharpe_ratio(returns),
            'sortino_ratio': self._calculate_sortino_ratio(returns),
            'max_drawdown': self._calculate_max_drawdown(returns),
            'calmar_ratio': self._calculate_calmar_ratio(returns),
            'win_rate': self._calculate_win_rate(returns),
            'profit_factor': self._calculate_profit_factor(returns),
            'avg_win': np.mean(returns[returns > 0]) if any(returns > 0) else 0,
            'avg_loss': np.mean(returns[returns < 0]) if any(returns < 0) else 0,
            'total_trades': len(returns),
            'best_day': np.max(returns),
            'worst_day': np.min(returns),
            'skewness': self._calculate_skewness(returns),
            'kurtosis': self._calculate_kurtosis(returns)
        }
        
        return metrics
    
    def calculate_risk_metrics(
        self,
        portfolio_id: str,
        window: int = 252,
        confidence_levels: List[float] = [0.95, 0.99]
    ) -> Dict:
        """
        Calculate risk metrics including VaR and CVaR
        """
        # Generate sample returns
        returns = self._generate_sample_returns(
            datetime.now() - timedelta(days=window),
            datetime.now()
        )
        
        risk_metrics = {
            'volatility': self._calculate_volatility(returns),
            'downside_deviation': self._calculate_downside_deviation(returns),
            'beta': self._calculate_beta(returns),  # Would need market returns
            'tracking_error': 0.0,  # Placeholder
        }
        
        # Calculate VaR and CVaR for each confidence level
        for confidence in confidence_levels:
            var = self._calculate_var(returns, confidence)
            cvar = self._calculate_cvar(returns, confidence)
            risk_metrics[f'var_{int(confidence*100)}'] = var
            risk_metrics[f'cvar_{int(confidence*100)}'] = cvar
        
        return risk_metrics
    
    def analyze_backtest(self, backtest_id: str) -> Dict:
        """
        Analyze backtest results
        """
        # In real implementation, would fetch backtest data
        # For now, return comprehensive mock analysis
        
        return {
            'metrics': {
                'total_return': 0.156,
                'sharpe_ratio': 1.45,
                'max_drawdown': 0.087,
                'win_rate': 0.58,
                'profit_factor': 1.78
            },
            'statistics': {
                'total_trades': 245,
                'winning_trades': 142,
                'losing_trades': 103,
                'avg_holding_period': 3.5,
                'max_consecutive_wins': 8,
                'max_consecutive_losses': 5
            },
            'risk_analysis': {
                'var_95': 0.024,
                'cvar_95': 0.031,
                'downside_deviation': 0.018,
                'ulcer_index': 0.045
            },
            'trade_analysis': {
                'best_trade': 0.087,
                'worst_trade': -0.043,
                'avg_win': 0.023,
                'avg_loss': -0.015,
                'largest_winner': 0.087,
                'largest_loser': -0.043
            }
        }
    
    # Helper methods
    def _generate_sample_returns(self, start_date: datetime, end_date: datetime) -> np.ndarray:
        """Generate sample returns for testing"""
        days = (end_date - start_date).days
        # Generate returns with realistic properties
        returns = np.random.normal(0.0005, 0.02, days)
        # Add some autocorrelation
        for i in range(1, len(returns)):
            returns[i] = 0.1 * returns[i-1] + 0.9 * returns[i]
        return returns
    
    def _calculate_total_return(self, returns: np.ndarray) -> float:
        """Calculate total cumulative return"""
        return np.prod(1 + returns) - 1
    
    def _calculate_annualized_return(self, returns: np.ndarray) -> float:
        """Calculate annualized return"""
        total_return = self._calculate_total_return(returns)
        years = len(returns) / 252
        return (1 + total_return) ** (1 / years) - 1
    
    def _calculate_volatility(self, returns: np.ndarray) -> float:
        """Calculate annualized volatility"""
        return np.std(returns) * np.sqrt(252)
    
    def _calculate_sharpe_ratio(self, returns: np.ndarray) -> float:
        """Calculate Sharpe ratio"""
        excess_returns = returns - self.risk_free_rate / 252
        return np.mean(excess_returns) / np.std(excess_returns) * np.sqrt(252)
    
    def _calculate_sortino_ratio(self, returns: np.ndarray) -> float:
        """Calculate Sortino ratio"""
        excess_returns = returns - self.risk_free_rate / 252
        downside_returns = excess_returns[excess_returns < 0]
        downside_std = np.std(downside_returns) if len(downside_returns) > 0 else 1e-6
        return np.mean(excess_returns) / downside_std * np.sqrt(252)
    
    def _calculate_max_drawdown(self, returns: np.ndarray) -> float:
        """Calculate maximum drawdown"""
        cumulative = (1 + returns).cumprod()
        running_max = np.maximum.accumulate(cumulative)
        drawdown = (cumulative - running_max) / running_max
        return np.min(drawdown)
    
    def _calculate_calmar_ratio(self, returns: np.ndarray) -> float:
        """Calculate Calmar ratio"""
        annual_return = self._calculate_annualized_return(returns)
        max_dd = abs(self._calculate_max_drawdown(returns))
        return annual_return / max_dd if max_dd > 0 else 0
    
    def _calculate_win_rate(self, returns: np.ndarray) -> float:
        """Calculate win rate"""
        return np.sum(returns > 0) / len(returns) if len(returns) > 0 else 0
    
    def _calculate_profit_factor(self, returns: np.ndarray) -> float:
        """Calculate profit factor"""
        gains = returns[returns > 0]
        losses = returns[returns < 0]
        total_gains = np.sum(gains) if len(gains) > 0 else 0
        total_losses = abs(np.sum(losses)) if len(losses) > 0 else 1e-6
        return total_gains / total_losses
    
    def _calculate_downside_deviation(self, returns: np.ndarray, mar: float = 0) -> float:
        """Calculate downside deviation"""
        downside_returns = returns[returns < mar]
        return np.std(downside_returns) * np.sqrt(252) if len(downside_returns) > 0 else 0
    
    def _calculate_var(self, returns: np.ndarray, confidence: float) -> float:
        """Calculate Value at Risk"""
        return np.percentile(returns, (1 - confidence) * 100)
    
    def _calculate_cvar(self, returns: np.ndarray, confidence: float) -> float:
        """Calculate Conditional Value at Risk"""
        var = self._calculate_var(returns, confidence)
        return np.mean(returns[returns <= var])
    
    def _calculate_beta(self, returns: np.ndarray, market_returns: Optional[np.ndarray] = None) -> float:
        """Calculate beta relative to market"""
        if market_returns is None:
            # Generate mock market returns
            market_returns = np.random.normal(0.0003, 0.015, len(returns))
        
        covariance = np.cov(returns, market_returns)[0, 1]
        market_variance = np.var(market_returns)
        return covariance / market_variance if market_variance > 0 else 1.0
    
    def _calculate_skewness(self, returns: np.ndarray) -> float:
        """Calculate skewness of returns"""
        mean = np.mean(returns)
        std = np.std(returns)
        return np.mean(((returns - mean) / std) ** 3) if std > 0 else 0
    
    def _calculate_kurtosis(self, returns: np.ndarray) -> float:
        """Calculate kurtosis of returns"""
        mean = np.mean(returns)
        std = np.std(returns)
        return np.mean(((returns - mean) / std) ** 4) - 3 if std > 0 else 0