import os
import sys
from pathlib import Path


project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))

# Set working directory to project root
os.chdir(project_root)

from bounds import stochastic_cvar_bound_const_eps
import numpy as np
from scipy import stats
from bounds import cvar_estimator

# Global parameters - edit these to change simulation settings
Y_SCALE = 1.2  # Standard deviation for distribution Y
DELTA = 0.2   # Confidence parameter for stochastic bounds
SEED = 42     # Random seed for reproducibility
N_Y_SAMPLES = 100  # Number of Y samples per Monte Carlo iteration
N_MONTE_CARLO = 10000  # Number of Monte Carlo iterations


def simulate_truncated_normal_cvar():
    """
    Simulation that creates samples from truncated normal distributions and estimates CVaR.
    
    Creates:
    1. 10000 samples from truncated normal between -1 and 1 with mean 0 and std 1 (X distribution)
    2. 10000 samples from truncated normal between -1 and 1 with mean 0 and std Y_SCALE (Y distribution)
    
    Estimates CVaR for both distributions, computes KS distance, and estimates bounds.
    """
    # Set random seed for reproducibility
    np.random.seed(SEED)
    
    # Parameters
    n_samples = 10000
    alpha = 0.05  # 95% confidence level for CVaR
    
    # Distribution X: truncated normal between -1 and 1, mean=0, std=1
    print("Generating samples for distribution X (truncated normal, mean=0, std=1)...")
    x_samples = stats.truncnorm.rvs(
        a=(-1 - 0) / 1,  # (lower_bound - mean) / std
        b=(1 - 0) / 1,   # (upper_bound - mean) / std
        loc=0,           # mean
        scale=1,         # standard deviation
        size=n_samples
    )
    
    # Distribution Y: truncated normal between -1 and 1, mean=0, std=Y_SCALE
    print(f"Generating samples for distribution Y (truncated normal, mean=0, std={Y_SCALE})...")
    y_samples = stats.truncnorm.rvs(
        a=(-1 - 0) / Y_SCALE,  # (lower_bound - mean) / std
        b=(1 - 0) / Y_SCALE,   # (upper_bound - mean) / std
        loc=0,                 # mean
        scale=Y_SCALE,         # standard deviation
        size=n_samples
    )
    
    # Estimate CVaR for distribution X
    print(f"\nEstimating CVaR for distribution X (alpha={alpha})...")
    cvar_x = cvar_estimator(x_samples, alpha)
    print(f"CVaR(X) = {cvar_x:.6f}")
    
    # Estimate CVaR for distribution Y
    print(f"\nEstimating CVaR for distribution Y (alpha={alpha})...")
    cvar_y = cvar_estimator(y_samples, alpha)
    print(f"CVaR(Y) = {cvar_y:.6f}")
    
    # Compute Kolmogorov-Smirnov distance between the distributions
    print(f"\nComputing Kolmogorov-Smirnov distance between X and Y...")
    ks_statistic, ks_pvalue = stats.ks_2samp(x_samples, y_samples)
    print(f"KS statistic: {ks_statistic:.6f}")
    print(f"KS p-value: {ks_pvalue:.6f}")
    
    # Determine bounds for the distributions
    x_inf, x_sup = -1.0, 1.0  # bounds for X distribution
    y_inf, y_sup = -1.0, 1.0  # bounds for Y distribution
    
    # Use KS distance as epsilon parameter for the bounds
    eps = ks_statistic
    print(f"\nUsing KS distance ({eps:.6f}) as epsilon parameter for bounds...")
    
    # Estimate stochastic CVaR bounds for X using Y samples
    print(f"\nEstimating stochastic CVaR bounds for X using Y samples...")
    print(f"Parameters: alpha={alpha}, delta={DELTA}, eps={eps:.6f}")
    
    lower_bound, upper_bound = stochastic_cvar_bound_const_eps(
        y_samp=y_samples,
        y_sup=y_sup,
        y_inf=y_inf,
        x_sup=x_sup,
        x_inf=x_inf,
        eps=eps,
        delta=DELTA,
        alpha=alpha
    )
    
    print(f"Lower bound for CVaR(X): {lower_bound:.6f}")
    print(f"Upper bound for CVaR(X): {upper_bound:.6f}")
    print(f"Actual CVaR(X): {cvar_x:.6f}")
    print(f"Bounds width: {upper_bound - lower_bound:.6f}")
    
    # Check if actual CVaR falls within bounds
    if lower_bound <= cvar_x <= upper_bound:
        print(f"✓ Actual CVaR(X) falls within the estimated bounds")
    else:
        print(f"✗ Actual CVaR(X) falls outside the estimated bounds")
    
    # Print summary statistics
    print(f"\nSummary Statistics:")
    print(f"Distribution X:")
    print(f"  Mean: {np.mean(x_samples):.6f}")
    print(f"  Std: {np.std(x_samples):.6f}")
    print(f"  Min: {np.min(x_samples):.6f}")
    print(f"  Max: {np.max(x_samples):.6f}")
    print(f"  CVaR({alpha}): {cvar_x:.6f}")
    
    print(f"\nDistribution Y:")
    print(f"  Mean: {np.mean(y_samples):.6f}")
    print(f"  Std: {np.std(y_samples):.6f}")
    print(f"  Min: {np.min(y_samples):.6f}")
    print(f"  Max: {np.max(y_samples):.6f}")
    print(f"  CVaR({alpha}): {cvar_y:.6f}")
    
    print(f"\nDistributional Discrepancy Analysis:")
    print(f"  KS distance: {ks_statistic:.6f}")
    print(f"  KS p-value: {ks_pvalue:.6f}")
    print(f"  CVaR bounds for X (using Y): [{lower_bound:.6f}, {upper_bound:.6f}]")
    print(f"  Actual CVaR(X): {cvar_x:.6f}")
    
    return x_samples, y_samples, cvar_x, cvar_y, ks_statistic, lower_bound, upper_bound


def monte_carlo_bounds_analysis(x_samples, cvar_x, original_ks_statistic, alpha=0.05):
    """
    Perform Monte Carlo analysis to estimate the probability that CVaR(X) falls outside estimated bounds.
    
    Args:
        x_samples: Samples from distribution X
        cvar_x: Actual CVaR of distribution X
        original_ks_statistic: KS statistic from the original simulation
        alpha: Confidence level for CVaR
    
    Returns:
        float: Probability that CVaR(X) falls outside estimated bounds
    """
    print(f"\n{'='*60}")
    print(f"MONTE CARLO BOUNDS ANALYSIS")
    print(f"{'='*60}")
    print(f"Parameters:")
    print(f"  Monte Carlo iterations: {N_MONTE_CARLO}")
    print(f"  Y samples per iteration: {N_Y_SAMPLES}")
    print(f"  Alpha: {alpha}")
    print(f"  Delta: {DELTA}")
    print(f"  Y scale (std): {Y_SCALE}")
    print(f"  Random seed: {SEED}")
    print(f"  Original KS statistic: {original_ks_statistic:.6f}")
    
    # Determine bounds for the distributions
    x_inf, x_sup = -1.0, 1.0  # bounds for X distribution
    y_inf, y_sup = -1.0, 1.0  # bounds for Y distribution
    
    # Store results
    bounds_results = []
    outside_bounds_count = 0
    
    print(f"\nRunning Monte Carlo simulation...")
    for i in range(N_MONTE_CARLO):
        if (i + 1) % 10 == 0:
            print(f"  Progress: {i + 1}/{N_MONTE_CARLO}")
        
        # Generate new samples from distribution Y
        y_samples_iter = stats.truncnorm.rvs(
            a=(-1 - 0) / Y_SCALE,  # (lower_bound - mean) / std
            b=(1 - 0) / Y_SCALE,   # (upper_bound - mean) / std
            loc=0,                 # mean
            scale=Y_SCALE,         # standard deviation
            size=N_Y_SAMPLES
        )
        
        # Use the original KS statistic as epsilon parameter (don't recompute)
        eps = original_ks_statistic
        
        # Estimate bounds using this Y sample
        try:
            lower_bound, upper_bound = stochastic_cvar_bound_const_eps(
                y_samp=y_samples_iter,
                y_sup=y_sup,
                y_inf=y_inf,
                x_sup=x_sup,
                x_inf=x_inf,
                eps=eps,
                delta=DELTA,
                alpha=alpha
            )
            
            # Check if actual CVaR falls outside bounds
            outside_bounds = not (lower_bound <= cvar_x <= upper_bound)
            if outside_bounds:
                outside_bounds_count += 1
            
            bounds_results.append({
                'iteration': i + 1,
                'ks_distance': original_ks_statistic,
                'lower_bound': lower_bound,
                'upper_bound': upper_bound,
                'outside_bounds': outside_bounds
            })
            
        except Exception as e:
            print(f"  Warning: Error in iteration {i + 1}: {e}")
            continue
    
    # Calculate probability
    successful_iterations = len(bounds_results)
    if successful_iterations == 0:
        print("Error: No successful iterations completed")
        return 0.0
    
    probability_outside = outside_bounds_count / successful_iterations
    probability_inside = 1 - probability_outside
    
    # Print results
    print(f"\nMonte Carlo Results:")
    print(f"  Successful iterations: {successful_iterations}/{N_MONTE_CARLO}")
    print(f"  Times CVaR(X) outside bounds: {outside_bounds_count}")
    print(f"  Times CVaR(X) inside bounds: {successful_iterations - outside_bounds_count}")
    print(f"  Probability CVaR(X) outside bounds: {probability_outside:.4f} ({probability_outside*100:.2f}%)")
    print(f"  Probability CVaR(X) inside bounds: {probability_inside:.4f} ({probability_inside*100:.2f}%)")
    
    # Summary statistics of bounds
    if bounds_results:
        lower_bounds = [r['lower_bound'] for r in bounds_results]
        upper_bounds = [r['upper_bound'] for r in bounds_results]
        ks_distances = [r['ks_distance'] for r in bounds_results]
        
        print(f"\nBounds Statistics:")
        print(f"  Lower bound - Mean: {np.mean(lower_bounds):.6f}, Std: {np.std(lower_bounds):.6f}")
        print(f"  Upper bound - Mean: {np.mean(upper_bounds):.6f}, Std: {np.std(upper_bounds):.6f}")
        print(f"  Bounds width - Mean: {np.mean([u-l for l, u in zip(lower_bounds, upper_bounds)]):.6f}")
        print(f"  KS distance - Mean: {np.mean(ks_distances):.6f}, Std: {np.std(ks_distances):.6f}")
        
        # Show some examples of bounds
        print(f"\nExample bounds (first 5 iterations):")
        for i in range(min(5, len(bounds_results))):
            r = bounds_results[i]
            status = "OUTSIDE" if r['outside_bounds'] else "INSIDE"
            print(f"  Iter {r['iteration']}: [{r['lower_bound']:.6f}, {r['upper_bound']:.6f}] - {status}")
    
    return probability_outside


def simulate_binomial_distributions_cvar():
    """
    Simulation that creates samples from Binomial distributions and estimates CVaR.
    
    Creates:
    1. 10000 samples from Binomial distribution with n=20, p=0.3 (X distribution)
    2. 10000 samples from Binomial distribution with n=20, p=0.35 (Y distribution)
    
    Estimates CVaR for both distributions, computes KS distance, and estimates bounds.
    """
    # Set random seed for reproducibility
    np.random.seed(SEED)
    
    # Parameters
    n_samples = 10000
    alpha = 0.05  # 95% confidence level for CVaR
    
    # Distribution X: Binomial with n=20, p=0.3
    print("Generating samples for distribution X (Binomial, n=20, p=0.3)...")
    x_samples = stats.binom.rvs(
        n=20,  # number of trials
        p=0.3, # probability of success
        size=n_samples
    )
    
    # Distribution Y: Binomial with n=20, p=0.35 (slightly different)
    print("Generating samples for distribution Y (Binomial, n=20, p=0.35)...")
    y_samples = stats.binom.rvs(
        n=20,  # number of trials
        p=0.35, # probability of success (slightly higher)
        size=n_samples
    )
    
    # Estimate CVaR for distribution X
    print(f"\nEstimating CVaR for distribution X (alpha={alpha})...")
    cvar_x = cvar_estimator(x_samples, alpha)
    print(f"CVaR(X) = {cvar_x:.6f}")
    
    # Estimate CVaR for distribution Y
    print(f"\nEstimating CVaR for distribution Y (alpha={alpha})...")
    cvar_y = cvar_estimator(y_samples, alpha)
    print(f"CVaR(Y) = {cvar_y:.6f}")
    
    # Compute Kolmogorov-Smirnov distance between the distributions
    print(f"\nComputing Kolmogorov-Smirnov distance between X and Y...")
    ks_statistic, ks_pvalue = stats.ks_2samp(x_samples, y_samples)
    print(f"KS statistic: {ks_statistic:.6f}")
    print(f"KS p-value: {ks_pvalue:.6f}")
    
    # Determine bounds for the distributions (Binomial distributions are bounded by 0 and n)
    x_inf, x_sup = 0.0, 20.0  # bounds for X distribution
    y_inf, y_sup = 0.0, 20.0  # bounds for Y distribution
    
    # Use KS distance as epsilon parameter for the bounds
    eps = ks_statistic
    print(f"\nUsing KS distance ({eps:.6f}) as epsilon parameter for bounds...")
    
    # Estimate stochastic CVaR bounds for X using Y samples
    print(f"\nEstimating stochastic CVaR bounds for X using Y samples...")
    print(f"Parameters: alpha={alpha}, delta={DELTA}, eps={eps:.6f}")
    
    lower_bound, upper_bound = stochastic_cvar_bound_const_eps(
        y_samp=y_samples,
        y_sup=y_sup,
        y_inf=y_inf,
        x_sup=x_sup,
        x_inf=x_inf,
        eps=eps,
        delta=DELTA,
        alpha=alpha
    )
    
    print(f"Lower bound for CVaR(X): {lower_bound:.6f}")
    print(f"Upper bound for CVaR(X): {upper_bound:.6f}")
    print(f"Actual CVaR(X): {cvar_x:.6f}")
    print(f"Bounds width: {upper_bound - lower_bound:.6f}")
    
    # Check if actual CVaR falls within bounds
    if lower_bound <= cvar_x <= upper_bound:
        print(f"✓ Actual CVaR(X) falls within the estimated bounds")
    else:
        print(f"✗ Actual CVaR(X) falls outside the estimated bounds")
    
    # Print summary statistics
    print(f"\nSummary Statistics:")
    print(f"Distribution X (Binomial, n=20, p=0.3):")
    print(f"  Mean: {np.mean(x_samples):.6f}")
    print(f"  Std: {np.std(x_samples):.6f}")
    print(f"  Min: {np.min(x_samples):.6f}")
    print(f"  Max: {np.max(x_samples):.6f}")
    print(f"  CVaR({alpha}): {cvar_x:.6f}")
    
    print(f"\nDistribution Y (Binomial, n=20, p=0.35):")
    print(f"  Mean: {np.mean(y_samples):.6f}")
    print(f"  Std: {np.std(y_samples):.6f}")
    print(f"  Min: {np.min(y_samples):.6f}")
    print(f"  Max: {np.max(y_samples):.6f}")
    print(f"  CVaR({alpha}): {cvar_y:.6f}")
    
    print(f"\nDistributional Discrepancy Analysis:")
    print(f"  KS distance: {ks_statistic:.6f}")
    print(f"  KS p-value: {ks_pvalue:.6f}")
    print(f"  CVaR bounds for X (using Y): [{lower_bound:.6f}, {upper_bound:.6f}]")
    print(f"  Actual CVaR(X): {cvar_x:.6f}")
    
    return x_samples, y_samples, cvar_x, cvar_y, ks_statistic, lower_bound, upper_bound


def simulate_beta_distributions_cvar():
    """
    Simulation that creates samples from Beta distributions and estimates CVaR.
    
    Creates:
    1. 10000 samples from Beta distribution with a=2, b=5 (X distribution)
    2. 10000 samples from Beta distribution with a=2.5, b=5 (Y distribution)
    
    Estimates CVaR for both distributions, computes KS distance, and estimates bounds.
    """
    # Set random seed for reproducibility
    np.random.seed(SEED)
    
    # Parameters
    n_samples = 10000
    alpha = 0.05  # 95% confidence level for CVaR
    
    # Distribution X: Beta with a=2, b=5
    print("Generating samples for distribution X (Beta, a=2, b=5)...")
    x_samples = stats.beta.rvs(
        a=2,  # alpha parameter
        b=5,  # beta parameter
        size=n_samples
    )
    
    # Distribution Y: Beta with a=2.5, b=5 (slightly different)
    print("Generating samples for distribution Y (Beta, a=2.5, b=5)...")
    y_samples = stats.beta.rvs(
        a=2.5,  # alpha parameter (slightly larger)
        b=5,    # beta parameter
        size=n_samples
    )
    
    # Estimate CVaR for distribution X
    print(f"\nEstimating CVaR for distribution X (alpha={alpha})...")
    cvar_x = cvar_estimator(x_samples, alpha)
    print(f"CVaR(X) = {cvar_x:.6f}")
    
    # Estimate CVaR for distribution Y
    print(f"\nEstimating CVaR for distribution Y (alpha={alpha})...")
    cvar_y = cvar_estimator(y_samples, alpha)
    print(f"CVaR(Y) = {cvar_y:.6f}")
    
    # Compute Kolmogorov-Smirnov distance between the distributions
    print(f"\nComputing Kolmogorov-Smirnov distance between X and Y...")
    ks_statistic, ks_pvalue = stats.ks_2samp(x_samples, y_samples)
    print(f"KS statistic: {ks_statistic:.6f}")
    print(f"KS p-value: {ks_pvalue:.6f}")
    
    # Determine bounds for the distributions (Beta distributions are bounded by 0 and 1)
    x_inf, x_sup = 0.0, 1.0  # bounds for X distribution
    y_inf, y_sup = 0.0, 1.0  # bounds for Y distribution
    
    # Use KS distance as epsilon parameter for the bounds
    eps = ks_statistic
    print(f"\nUsing KS distance ({eps:.6f}) as epsilon parameter for bounds...")
    
    # Estimate stochastic CVaR bounds for X using Y samples
    print(f"\nEstimating stochastic CVaR bounds for X using Y samples...")
    print(f"Parameters: alpha={alpha}, delta={DELTA}, eps={eps:.6f}")
    
    lower_bound, upper_bound = stochastic_cvar_bound_const_eps(
        y_samp=y_samples,
        y_sup=y_sup,
        y_inf=y_inf,
        x_sup=x_sup,
        x_inf=x_inf,
        eps=eps,
        delta=DELTA,
        alpha=alpha
    )
    
    print(f"Lower bound for CVaR(X): {lower_bound:.6f}")
    print(f"Upper bound for CVaR(X): {upper_bound:.6f}")
    print(f"Actual CVaR(X): {cvar_x:.6f}")
    print(f"Bounds width: {upper_bound - lower_bound:.6f}")
    
    # Check if actual CVaR falls within bounds
    if lower_bound <= cvar_x <= upper_bound:
        print(f"✓ Actual CVaR(X) falls within the estimated bounds")
    else:
        print(f"✗ Actual CVaR(X) falls outside the estimated bounds")
    
    # Print summary statistics
    print(f"\nSummary Statistics:")
    print(f"Distribution X (Beta, a=2, b=5):")
    print(f"  Mean: {np.mean(x_samples):.6f}")
    print(f"  Std: {np.std(x_samples):.6f}")
    print(f"  Min: {np.min(x_samples):.6f}")
    print(f"  Max: {np.max(x_samples):.6f}")
    print(f"  CVaR({alpha}): {cvar_x:.6f}")
    
    print(f"\nDistribution Y (Beta, a=2.5, b=5):")
    print(f"  Mean: {np.mean(y_samples):.6f}")
    print(f"  Std: {np.std(y_samples):.6f}")
    print(f"  Min: {np.min(y_samples):.6f}")
    print(f"  Max: {np.max(y_samples):.6f}")
    print(f"  CVaR({alpha}): {cvar_y:.6f}")
    
    print(f"\nDistributional Discrepancy Analysis:")
    print(f"  KS distance: {ks_statistic:.6f}")
    print(f"  KS p-value: {ks_pvalue:.6f}")
    print(f"  CVaR bounds for X (using Y): [{lower_bound:.6f}, {upper_bound:.6f}]")
    print(f"  Actual CVaR(X): {cvar_x:.6f}")
    
    return x_samples, y_samples, cvar_x, cvar_y, ks_statistic, lower_bound, upper_bound


def monte_carlo_binomial_bounds_analysis(x_samples, cvar_x, original_ks_statistic, alpha=0.05):
    """
    Perform Monte Carlo analysis for Binomial distributions to estimate the probability that CVaR(X) falls outside estimated bounds.
    
    Args:
        x_samples: Samples from distribution X
        cvar_x: Actual CVaR of distribution X
        original_ks_statistic: KS statistic from the original simulation
        alpha: Confidence level for CVaR
    
    Returns:
        float: Probability that CVaR(X) falls outside estimated bounds
    """
    print(f"\n{'='*60}")
    print(f"MONTE CARLO BINOMIAL BOUNDS ANALYSIS")
    print(f"{'='*60}")
    print(f"Parameters:")
    print(f"  Monte Carlo iterations: {N_MONTE_CARLO}")
    print(f"  Y samples per iteration: {N_Y_SAMPLES}")
    print(f"  Alpha: {alpha}")
    print(f"  Delta: {DELTA}")
    print(f"  Random seed: {SEED}")
    print(f"  Original KS statistic: {original_ks_statistic:.6f}")
    
    # Determine bounds for the distributions
    x_inf, x_sup = 0.0, 20.0  # bounds for X distribution
    y_inf, y_sup = 0.0, 20.0  # bounds for Y distribution
    
    # Store results
    bounds_results = []
    outside_bounds_count = 0
    
    print(f"\nRunning Monte Carlo simulation...")
    for i in range(N_MONTE_CARLO):
        if (i + 1) % 10 == 0:
            print(f"  Progress: {i + 1}/{N_MONTE_CARLO}")
        
        # Generate new samples from distribution Y (Binomial with n=20, p=0.35)
        y_samples_iter = stats.binom.rvs(
            n=20,  # number of trials
            p=0.35, # probability of success
            size=N_Y_SAMPLES
        )
        
        # Use the original KS statistic as epsilon parameter (don't recompute)
        eps = original_ks_statistic
        
        # Estimate bounds using this Y sample
        try:
            lower_bound, upper_bound = stochastic_cvar_bound_const_eps(
                y_samp=y_samples_iter,
                y_sup=y_sup,
                y_inf=y_inf,
                x_sup=x_sup,
                x_inf=x_inf,
                eps=eps,
                delta=DELTA,
                alpha=alpha
            )
            
            # Check if actual CVaR falls outside bounds
            outside_bounds = not (lower_bound <= cvar_x <= upper_bound)
            if outside_bounds:
                outside_bounds_count += 1
            
            bounds_results.append({
                'iteration': i + 1,
                'ks_distance': original_ks_statistic,
                'lower_bound': lower_bound,
                'upper_bound': upper_bound,
                'outside_bounds': outside_bounds
            })
            
        except Exception as e:
            print(f"  Warning: Error in iteration {i + 1}: {e}")
            continue
    
    # Calculate probability
    successful_iterations = len(bounds_results)
    if successful_iterations == 0:
        print("Error: No successful iterations completed")
        return 0.0
    
    probability_outside = outside_bounds_count / successful_iterations
    probability_inside = 1 - probability_outside
    
    # Print results
    print(f"\nMonte Carlo Results:")
    print(f"  Successful iterations: {successful_iterations}/{N_MONTE_CARLO}")
    print(f"  Times CVaR(X) outside bounds: {outside_bounds_count}")
    print(f"  Times CVaR(X) inside bounds: {successful_iterations - outside_bounds_count}")
    print(f"  Probability CVaR(X) outside bounds: {probability_outside:.4f} ({probability_outside*100:.2f}%)")
    print(f"  Probability CVaR(X) inside bounds: {probability_inside:.4f} ({probability_inside*100:.2f}%)")
    
    # Summary statistics of bounds
    if bounds_results:
        lower_bounds = [r['lower_bound'] for r in bounds_results]
        upper_bounds = [r['upper_bound'] for r in bounds_results]
        ks_distances = [r['ks_distance'] for r in bounds_results]
        
        print(f"\nBounds Statistics:")
        print(f"  Lower bound - Mean: {np.mean(lower_bounds):.6f}, Std: {np.std(lower_bounds):.6f}")
        print(f"  Upper bound - Mean: {np.mean(upper_bounds):.6f}, Std: {np.std(upper_bounds):.6f}")
        print(f"  Bounds width - Mean: {np.mean([u-l for l, u in zip(lower_bounds, upper_bounds)]):.6f}")
        print(f"  KS distance - Mean: {np.mean(ks_distances):.6f}, Std: {np.std(ks_distances):.6f}")
        
        # Show some examples of bounds
        print(f"\nExample bounds (first 5 iterations):")
        for i in range(min(5, len(bounds_results))):
            r = bounds_results[i]
            status = "OUTSIDE" if r['outside_bounds'] else "INSIDE"
            print(f"  Iter {r['iteration']}: [{r['lower_bound']:.6f}, {r['upper_bound']:.6f}] - {status}")
    
    return probability_outside


def monte_carlo_beta_bounds_analysis(x_samples, cvar_x, original_ks_statistic, alpha=0.05):
    """
    Perform Monte Carlo analysis for Beta distributions to estimate the probability that CVaR(X) falls outside estimated bounds.
    
    Args:
        x_samples: Samples from distribution X
        cvar_x: Actual CVaR of distribution X
        original_ks_statistic: KS statistic from the original simulation
        alpha: Confidence level for CVaR
    
    Returns:
        float: Probability that CVaR(X) falls outside estimated bounds
    """
    print(f"\n{'='*60}")
    print(f"MONTE CARLO BETA BOUNDS ANALYSIS")
    print(f"{'='*60}")
    print(f"Parameters:")
    print(f"  Monte Carlo iterations: {N_MONTE_CARLO}")
    print(f"  Y samples per iteration: {N_Y_SAMPLES}")
    print(f"  Alpha: {alpha}")
    print(f"  Delta: {DELTA}")
    print(f"  Random seed: {SEED}")
    print(f"  Original KS statistic: {original_ks_statistic:.6f}")
    
    # Determine bounds for the distributions
    x_inf, x_sup = 0.0, 1.0  # bounds for X distribution
    y_inf, y_sup = 0.0, 1.0  # bounds for Y distribution
    
    # Store results
    bounds_results = []
    outside_bounds_count = 0
    
    print(f"\nRunning Monte Carlo simulation...")
    for i in range(N_MONTE_CARLO):
        if (i + 1) % 10 == 0:
            print(f"  Progress: {i + 1}/{N_MONTE_CARLO}")
        
        # Generate new samples from distribution Y (Beta with a=2.5, b=5)
        y_samples_iter = stats.beta.rvs(
            a=2.5,  # alpha parameter
            b=5,    # beta parameter
            size=N_Y_SAMPLES
        )
        
        # Use the original KS statistic as epsilon parameter (don't recompute)
        eps = original_ks_statistic
        
        # Estimate bounds using this Y sample
        try:
            lower_bound, upper_bound = stochastic_cvar_bound_const_eps(
                y_samp=y_samples_iter,
                y_sup=y_sup,
                y_inf=y_inf,
                x_sup=x_sup,
                x_inf=x_inf,
                eps=eps,
                delta=DELTA,
                alpha=alpha
            )
            
            # Check if actual CVaR falls outside bounds
            outside_bounds = not (lower_bound <= cvar_x <= upper_bound)
            if outside_bounds:
                outside_bounds_count += 1
            
            bounds_results.append({
                'iteration': i + 1,
                'ks_distance': original_ks_statistic,
                'lower_bound': lower_bound,
                'upper_bound': upper_bound,
                'outside_bounds': outside_bounds
            })
            
        except Exception as e:
            print(f"  Warning: Error in iteration {i + 1}: {e}")
            continue
    
    # Calculate probability
    successful_iterations = len(bounds_results)
    if successful_iterations == 0:
        print("Error: No successful iterations completed")
        return 0.0
    
    probability_outside = outside_bounds_count / successful_iterations
    probability_inside = 1 - probability_outside
    
    # Print results
    print(f"\nMonte Carlo Results:")
    print(f"  Successful iterations: {successful_iterations}/{N_MONTE_CARLO}")
    print(f"  Times CVaR(X) outside bounds: {outside_bounds_count}")
    print(f"  Times CVaR(X) inside bounds: {successful_iterations - outside_bounds_count}")
    print(f"  Probability CVaR(X) outside bounds: {probability_outside:.4f} ({probability_outside*100:.2f}%)")
    print(f"  Probability CVaR(X) inside bounds: {probability_inside:.4f} ({probability_inside*100:.2f}%)")
    
    # Summary statistics of bounds
    if bounds_results:
        lower_bounds = [r['lower_bound'] for r in bounds_results]
        upper_bounds = [r['upper_bound'] for r in bounds_results]
        ks_distances = [r['ks_distance'] for r in bounds_results]
        
        print(f"\nBounds Statistics:")
        print(f"  Lower bound - Mean: {np.mean(lower_bounds):.6f}, Std: {np.std(lower_bounds):.6f}")
        print(f"  Upper bound - Mean: {np.mean(upper_bounds):.6f}, Std: {np.std(upper_bounds):.6f}")
        print(f"  Bounds width - Mean: {np.mean([u-l for l, u in zip(lower_bounds, upper_bounds)]):.6f}")
        print(f"  KS distance - Mean: {np.mean(ks_distances):.6f}, Std: {np.std(ks_distances):.6f}")
        
        # Show some examples of bounds
        print(f"\nExample bounds (first 5 iterations):")
        for i in range(min(5, len(bounds_results))):
            r = bounds_results[i]
            status = "OUTSIDE" if r['outside_bounds'] else "INSIDE"
            print(f"  Iter {r['iteration']}: [{r['lower_bound']:.6f}, {r['upper_bound']:.6f}] - {status}")
    
    return probability_outside


if __name__ == "__main__":
    print("="*80)
    print("NORMAL DISTRIBUTIONS SIMULATION")
    print("="*80)
    
    # Run the normal distributions simulation
    x_samples, y_samples, cvar_x, cvar_y, ks_statistic, lower_bound, upper_bound = simulate_truncated_normal_cvar()
    
    # Run Monte Carlo analysis for normal distributions
    probability_outside_normal = monte_carlo_bounds_analysis(
        x_samples=x_samples,
        cvar_x=cvar_x,
        original_ks_statistic=ks_statistic,
        alpha=0.05
    )
    
    print(f"\nFINAL RESULT - Normal Distributions:")
    print(f"Probability that CVaR(X) falls outside bounds: {probability_outside_normal:.4f} ({probability_outside_normal*100:.2f}%)")
    
    print("\n" + "="*80)
    print("BINOMIAL DISTRIBUTIONS SIMULATION")
    print("="*80)
    
    # Run the binomial distributions simulation
    x_samples_binom, y_samples_binom, cvar_x_binom, cvar_y_binom, ks_statistic_binom, lower_bound_binom, upper_bound_binom = simulate_binomial_distributions_cvar()
    
    # Run Monte Carlo analysis for binomial distributions
    probability_outside_binom = monte_carlo_binomial_bounds_analysis(
        x_samples=x_samples_binom,
        cvar_x=cvar_x_binom,
        original_ks_statistic=ks_statistic_binom,
        alpha=0.05
    )
    
    print(f"\nFINAL RESULT - Binomial Distributions:")
    print(f"Probability that CVaR(X) falls outside bounds: {probability_outside_binom:.4f} ({probability_outside_binom*100:.2f}%)")
    
    print("\n" + "="*80)
    print("BETA DISTRIBUTIONS SIMULATION")
    print("="*80)
    
    # Run the beta distributions simulation
    x_samples_beta, y_samples_beta, cvar_x_beta, cvar_y_beta, ks_statistic_beta, lower_bound_beta, upper_bound_beta = simulate_beta_distributions_cvar()
    
    # Run Monte Carlo analysis for beta distributions
    probability_outside_beta = monte_carlo_beta_bounds_analysis(
        x_samples=x_samples_beta,
        cvar_x=cvar_x_beta,
        original_ks_statistic=ks_statistic_beta,
        alpha=0.05
    )
    
    print(f"\nFINAL RESULT - Beta Distributions:")
    print(f"Probability that CVaR(X) falls outside bounds: {probability_outside_beta:.4f} ({probability_outside_beta*100:.2f}%)")
    
    print("\n" + "="*80)
    print("COMPARISON SUMMARY")
    print("="*80)
    print(f"Normal Distributions: {probability_outside_normal:.4f} ({probability_outside_normal*100:.2f}%)")
    print(f"Binomial Distributions: {probability_outside_binom:.4f} ({probability_outside_binom*100:.2f}%)")
    print(f"Beta Distributions: {probability_outside_beta:.4f} ({probability_outside_beta*100:.2f}%)")


