#!/usr/bin/env python3
"""
Monte Carlo Simulator - Simulations probabilistes pour les prédictions
Génère des intervalles de confiance et évalue la robustesse des signaux
"""

import numpy as np
from typing import Dict, List, Optional, Tuple
import logging

logger = logging.getLogger("MonteCarloSimulator")

class MonteCarloSimulator:
    """Simule des scénarios futurs par Monte Carlo pour évaluer la confiance"""
    
    def __init__(self, num_simulations: int = 1000):
        self.num_simulations = num_simulations
        self.random_state = np.random.RandomState(42)  # Pour reproductibilité
        
        logger.info(f"✅ Monte Carlo Simulator initialisé ({num_simulations} simulations)")
    
    def simulate_future_prices(self, current_price: float, historical_returns: List[float],
                               periods_ahead: int = 10) -> Dict:
        """
        Simule les prix futurs par Monte Carlo
        
        Args:
            current_price: Prix actuel
            historical_returns: Rendements historiques (%)
            periods_ahead: Nombre de périodes à simuler
            
        Returns:
            Dict avec statistiques des simulations
        """
        if not historical_returns or len(historical_returns) < 10:
            return {
                'mean_price': current_price,
                'median_price': current_price,
                'confidence_95_low': current_price * 0.9,
                'confidence_95_high': current_price * 1.1,
                'probability_profit': 50.0,
                'expected_return': 0.0,
                'simulations': []
            }
        
        returns_array = np.array(historical_returns)
        mean_return = np.mean(returns_array) / 100  # Convertir en décimal
        std_return = np.std(returns_array) / 100
        
        # Simuler N trajectoires de prix
        simulated_prices = []
        final_prices = []
        
        for _ in range(self.num_simulations):
            price_path = [current_price]
            price = current_price
            
            for _ in range(periods_ahead):
                # Générer un rendement aléatoire (distribution normale)
                random_return = self.random_state.normal(mean_return, std_return)
                price = price * (1 + random_return)
                price_path.append(price)
            
            simulated_prices.append(price_path)
            final_prices.append(price)
        
        final_prices_array = np.array(final_prices)
        
        # Statistiques
        mean_price = np.mean(final_prices_array)
        median_price = np.median(final_prices_array)
        
        # Intervalles de confiance (95%)
        confidence_95_low = np.percentile(final_prices_array, 2.5)
        confidence_95_high = np.percentile(final_prices_array, 97.5)
        
        # Intervalles de confiance (68% - 1 sigma)
        confidence_68_low = np.percentile(final_prices_array, 16)
        confidence_68_high = np.percentile(final_prices_array, 84)
        
        # Probabilité de profit
        profitable_simulations = np.sum(final_prices_array > current_price)
        probability_profit = (profitable_simulations / self.num_simulations) * 100
        
        # Rendement attendu
        expected_return = ((mean_price - current_price) / current_price) * 100
        
        # Risk/Reward ratio
        potential_gain = confidence_95_high - current_price
        potential_loss = current_price - confidence_95_low
        risk_reward = potential_gain / potential_loss if potential_loss > 0 else 0
        
        return {
            'current_price': round(current_price, 8),
            'mean_price': round(mean_price, 8),
            'median_price': round(median_price, 8),
            'confidence_95_low': round(confidence_95_low, 8),
            'confidence_95_high': round(confidence_95_high, 8),
            'confidence_68_low': round(confidence_68_low, 8),
            'confidence_68_high': round(confidence_68_high, 8),
            'probability_profit': round(probability_profit, 1),
            'expected_return': round(expected_return, 2),
            'risk_reward_ratio': round(risk_reward, 2),
            'num_simulations': self.num_simulations,
            'periods_ahead': periods_ahead
        }
    
    def calculate_confidence_score(self, simulation_results: Dict) -> Dict:
        """
        Calcule un score de confiance basé sur les résultats Monte Carlo
        
        Args:
            simulation_results: Résultats de simulate_future_prices
            
        Returns:
            Dict avec score de confiance (0-100) et détails
        """
        prob_profit = simulation_results.get('probability_profit', 50)
        risk_reward = simulation_results.get('risk_reward_ratio', 1)
        expected_return = simulation_results.get('expected_return', 0)
        
        # Composantes du score
        
        # 1. Probabilité de profit (0-40 points)
        if prob_profit >= 75:
            prob_points = 40
        elif prob_profit >= 70:
            prob_points = 35
        elif prob_profit >= 65:
            prob_points = 30
        elif prob_profit >= 60:
            prob_points = 25
        elif prob_profit >= 55:
            prob_points = 20
        else:
            prob_points = max(0, (prob_profit - 40) / 15 * 20)
        
        # 2. Risk/Reward (0-30 points)
        if risk_reward >= 3:
            rr_points = 30
        elif risk_reward >= 2.5:
            rr_points = 25
        elif risk_reward >= 2:
            rr_points = 20
        elif risk_reward >= 1.5:
            rr_points = 15
        elif risk_reward >= 1:
            rr_points = 10
        else:
            rr_points = max(0, risk_reward * 10)
        
        # 3. Rendement attendu (0-30 points)
        if expected_return >= 10:
            return_points = 30
        elif expected_return >= 7:
            return_points = 25
        elif expected_return >= 5:
            return_points = 20
        elif expected_return >= 3:
            return_points = 15
        elif expected_return >= 1:
            return_points = 10
        elif expected_return > 0:
            return_points = 5
        else:
            return_points = 0
        
        # Score total
        confidence_score = prob_points + rr_points + return_points
        confidence_score = min(100, max(0, confidence_score))
        
        # Classification
        if confidence_score >= 80:
            quality = 'TRÈS HAUTE'
            recommendation = "🔥 Très haute confiance - Signal fort"
        elif confidence_score >= 65:
            quality = 'HAUTE'
            recommendation = "✅ Haute confiance - Bon signal"
        elif confidence_score >= 50:
            quality = 'MOYENNE'
            recommendation = "📊 Confiance moyenne - Signal modéré"
        elif confidence_score >= 35:
            quality = 'FAIBLE'
            recommendation = "⚠️ Faible confiance - Prudence"
        else:
            quality = 'TRÈS FAIBLE'
            recommendation = "🚫 Très faible confiance - Éviter"
        
        return {
            'confidence_score': round(confidence_score, 1),
            'confidence_quality': quality,
            'probability_profit': prob_profit,
            'risk_reward_ratio': risk_reward,
            'expected_return': expected_return,
            'recommendation': recommendation,
            'components': {
                'probability_points': round(prob_points, 1),
                'risk_reward_points': round(rr_points, 1),
                'expected_return_points': round(return_points, 1)
            }
        }
    
    def simulate_portfolio_scenarios(self, positions: List[Dict], 
                                    periods_ahead: int = 10) -> Dict:
        """
        Simule les scénarios futurs d'un portfolio
        
        Args:
            positions: Liste de positions {symbol, price, amount, historical_returns}
            periods_ahead: Périodes à simuler
            
        Returns:
            Dict avec statistiques du portfolio
        """
        if not positions:
            return {
                'portfolio_value': 0,
                'expected_value': 0,
                'confidence_95_low': 0,
                'confidence_95_high': 0,
                'probability_profit': 50,
                'var_95': 0
            }
        
        # Calculer la valeur actuelle du portfolio
        current_portfolio_value = sum(pos['price'] * pos['amount'] for pos in positions)
        
        # Simuler chaque position
        portfolio_simulations = []
        
        for _ in range(self.num_simulations):
            portfolio_value = 0
            
            for pos in positions:
                current_price = pos['price']
                amount = pos['amount']
                historical_returns = pos.get('historical_returns', [])
                
                if not historical_returns or len(historical_returns) < 10:
                    # Pas de données historiques, garder le prix
                    final_price = current_price
                else:
                    # Simuler le prix final
                    returns_array = np.array(historical_returns)
                    mean_return = np.mean(returns_array) / 100
                    std_return = np.std(returns_array) / 100
                    
                    price = current_price
                    for _ in range(periods_ahead):
                        random_return = self.random_state.normal(mean_return, std_return)
                        price = price * (1 + random_return)
                    
                    final_price = price
                
                portfolio_value += final_price * amount
            
            portfolio_simulations.append(portfolio_value)
        
        portfolio_array = np.array(portfolio_simulations)
        
        # Statistiques
        expected_value = np.mean(portfolio_array)
        median_value = np.median(portfolio_array)
        
        # Intervalles de confiance
        confidence_95_low = np.percentile(portfolio_array, 2.5)
        confidence_95_high = np.percentile(portfolio_array, 97.5)
        
        # Probabilité de profit
        profitable_sims = np.sum(portfolio_array > current_portfolio_value)
        probability_profit = (profitable_sims / self.num_simulations) * 100
        
        # Value at Risk (VaR 95%)
        var_95 = np.percentile(portfolio_array, 5) - current_portfolio_value
        var_95_percent = (var_95 / current_portfolio_value) * 100 if current_portfolio_value > 0 else 0
        
        # Expected return
        expected_return = ((expected_value - current_portfolio_value) / current_portfolio_value) * 100 if current_portfolio_value > 0 else 0
        
        return {
            'current_value': round(current_portfolio_value, 2),
            'expected_value': round(expected_value, 2),
            'median_value': round(median_value, 2),
            'confidence_95_low': round(confidence_95_low, 2),
            'confidence_95_high': round(confidence_95_high, 2),
            'probability_profit': round(probability_profit, 1),
            'expected_return': round(expected_return, 2),
            'var_95': round(var_95, 2),
            'var_95_percent': round(var_95_percent, 2),
            'num_positions': len(positions)
        }
    
    def stress_test(self, current_price: float, historical_returns: List[float],
                   stress_scenarios: List[Dict] = None) -> Dict:
        """
        Test de stress avec scénarios extrêmes
        
        Args:
            current_price: Prix actuel
            historical_returns: Rendements historiques
            stress_scenarios: Liste de scénarios {name, mean_shock, std_shock}
            
        Returns:
            Dict avec résultats des stress tests
        """
        if stress_scenarios is None:
            # Scénarios par défaut
            stress_scenarios = [
                {'name': 'Market Crash', 'mean_shock': -0.30, 'std_shock': 0.15},  # -30% ±15%
                {'name': 'Flash Crash', 'mean_shock': -0.50, 'std_shock': 0.10},   # -50% ±10%
                {'name': 'Bull Rally', 'mean_shock': 0.50, 'std_shock': 0.20},     # +50% ±20%
                {'name': 'Black Swan', 'mean_shock': -0.70, 'std_shock': 0.15}     # -70% ±15%
            ]
        
        results = {}
        
        for scenario in stress_scenarios:
            name = scenario['name']
            mean_shock = scenario['mean_shock']
            std_shock = scenario['std_shock']
            
            # Simuler le scénario
            final_prices = []
            for _ in range(self.num_simulations):
                shock = self.random_state.normal(mean_shock, std_shock)
                final_price = current_price * (1 + shock)
                final_prices.append(final_price)
            
            final_prices_array = np.array(final_prices)
            
            # Statistiques
            mean_price = np.mean(final_prices_array)
            median_price = np.median(final_prices_array)
            worst_case = np.min(final_prices_array)
            best_case = np.max(final_prices_array)
            
            loss = ((mean_price - current_price) / current_price) * 100
            
            results[name] = {
                'mean_price': round(mean_price, 8),
                'median_price': round(median_price, 8),
                'worst_case': round(worst_case, 8),
                'best_case': round(best_case, 8),
                'expected_loss': round(loss, 2)
            }
        
        return {
            'current_price': current_price,
            'scenarios': results,
            'max_potential_loss': round(min(s['expected_loss'] for s in results.values()), 2)
        }


# Instance globale
_monte_carlo_simulator = None

def get_monte_carlo_simulator(num_simulations: int = 1000) -> MonteCarloSimulator:
    """Retourne l'instance globale du Monte Carlo simulator"""
    global _monte_carlo_simulator
    if _monte_carlo_simulator is None:
        _monte_carlo_simulator = MonteCarloSimulator(num_simulations)
    return _monte_carlo_simulator
