# ----------------------------
# Helper Functions
# ----------------------------

@handle_exceptions
def calculate_metrics(
    returns: pd.Series, 
    cum_returns: pd.Series, 
    rf: float = 0.02, 
    benchmark_returns: pd.Series = None
) -> dict:
    """
    Calculate portfolio performance metrics with consistent data types.
    
    Args:
        returns (pd.Series): Portfolio returns
        cum_returns (pd.Series): Cumulative returns
        rf (float): Risk-free rate
        benchmark_returns (pd.Series, optional): Benchmark returns for comparison
    
    Returns:
        dict: Dictionary of metrics with consistent data types
    """
    metrics = {}
    
    # Initialize with default values
    initial_balance = 10000.0
    metrics['Start Balance'] = initial_balance
    
    def safe_calculation(calculation, default=np.nan):
        """Helper function to safely perform calculations"""
        try:
            result = calculation()
            return result if not np.isinf(result) and not np.isnan(result) else default
        except Exception:
            return default

    if not cum_returns.empty and not returns.empty:
        # Basic portfolio metrics
        end_balance = safe_calculation(
            lambda: initial_balance * cum_returns.iloc[-1]
        )
        years = (cum_returns.index[-1] - cum_returns.index[0]).days / 365.25

        metrics.update({
            'End Balance': end_balance,
            'Annualized Return (CAGR)': safe_calculation(
                lambda: (end_balance / initial_balance) ** (1 / years) - 1 if years > 0 else np.nan
            ),
            'Best Year': safe_calculation(
                lambda: returns.resample('Y').apply(lambda x: (1 + x).prod() - 1).max() * 100  # Converted to percentage
            ),
            'Worst Year': safe_calculation(
                lambda: returns.resample('Y').apply(lambda x: (1 + x).prod() - 1).min() * 100  # Converted to percentage
            ),
            'Standard Deviation (Annualized)': safe_calculation(
                lambda: returns.std() * np.sqrt(252) * 100  # Converted to percentage
            ),
            'Maximum Drawdown': safe_calculation(
                lambda: drawdown(cum_returns) * 100  # Converted to percentage
            ),
            'Sharpe Ratio': safe_calculation(
                lambda: calculate_sharpe_ratio(returns, rf)
            ),
            'Sortino Ratio': safe_calculation(
                lambda: calculate_sortino_ratio(returns, rf)
            ),
            'Beta': safe_calculation(
                lambda: calculate_beta(returns, benchmark_returns)
            ),
            'Alpha (annualized)': safe_calculation(
                lambda: calculate_alpha(returns, benchmark_returns, rf) * 100  # Converted to percentage
            ),
            'R2': safe_calculation(
                lambda: calculate_r_squared(returns, benchmark_returns)
            ),
            'Calmar Ratio': safe_calculation(
                lambda: calculate_calmar_ratio(returns, cum_returns)
            ),
            'Information Ratio': safe_calculation(
                lambda: calculate_information_ratio(returns, benchmark_returns)
            ),
            'Tracking Error': safe_calculation(
                lambda: calculate_tracking_error(returns, benchmark_returns) * 100  # Converted to percentage
            ),
            'Upside Capture Ratio': safe_calculation(
                lambda: calculate_capture_ratio(returns, benchmark_returns, upside=True) * 100  # Converted to percentage
            ),
            'Downside Capture Ratio': safe_calculation(
                lambda: calculate_capture_ratio(returns, benchmark_returns, upside=False) * 100  # Converted to percentage
            )
        })
    else:
        # Set default values for empty data
        default_metrics = {
            'End Balance': np.nan,
            'Annualized Return (CAGR)': np.nan,
            'Best Year': np.nan,
            'Worst Year': np.nan,
            'Standard Deviation (Annualized)': np.nan,
            'Maximum Drawdown': np.nan,
            'Sharpe Ratio': np.nan,
            'Sortino Ratio': np.nan,
            'Beta': np.nan,
            'Alpha (annualized)': np.nan,
            'R2': np.nan,
            'Calmar Ratio': np.nan,
            'Information Ratio': np.nan,
            'Tracking Error': np.nan,
            'Upside Capture Ratio': np.nan,
            'Downside Capture Ratio': np.nan
        }
        metrics.update(default_metrics)

    # Ensure all metrics are present even if benchmark_returns is None
    expected_metrics = [
        'Start Balance', 'End Balance', 'Annualized Return (CAGR)', 'Best Year',
        'Worst Year', 'Standard Deviation (Annualized)', 'Maximum Drawdown',
        'Sharpe Ratio', 'Sortino Ratio', 'Beta', 'Alpha (annualized)', 'R2',
        'Calmar Ratio', 'Information Ratio', 'Tracking Error',
        'Upside Capture Ratio', 'Downside Capture Ratio'
    ]

    for metric in expected_metrics:
        if metric not in metrics:
            if 'Percentage' in metric or 'Best Year' in metric or 'Worst Year' in metric:
                metrics[metric] = np.nan
            else:
                metrics[metric] = "N/A"

    return metrics

def safe_calculation(calculation, default=np.nan):
    """Helper function to safely perform calculations"""
    try:
        result = calculation()
        return result if not np.isinf(result) and not np.isnan(result) else default
    except Exception:
        return default

# ----------------------------
# Run Portfolio Comparison
# ----------------------------

@handle_exceptions
def run_portfolio_comparison(comparison_type, portfolio1, portfolio2=None):
    """
    Generalized function to run portfolio comparisons.

    Parameters:
    - comparison_type (str): "vs_benchmark" or "vs_portfolio"
    - portfolio1 (dict): Primary portfolio.
    - portfolio2 (dict, optional): Secondary portfolio for "vs_portfolio".

    Returns:
    - dict: Backtest results or error.
    """
    try:
        if comparison_type == "vs_benchmark":
            # Handle primary portfolio data
            available, missing, price_data = handle_portfolio_data(portfolio1)
            if not available:
                return {"error": "No valid tickers with available data."}
            
            # Adjust allocations based on available tickers
            allocations = adjust_allocations([portfolio1['allocations'][portfolio1['selected'].index(t)] for t in available])
            
            # Perform backtest for the portfolio
            returns, cum_returns = backtest(
                weights=np.array(allocations) / 100,
                prices=price_data[available],
                rebalance_freq=portfolio1['rebalance_freq'],
                broker_fee=portfolio1['broker_fee']
            )
            if cum_returns.empty:
                return {"error": "Cumulative returns are empty."}
            
            # Download benchmark data
            benchmark_data = download_data(
                [portfolio1['benchmark_symbol']],
                portfolio1['start_date'].strftime('%Y-%m-%d'),
                portfolio1['end_date'].strftime('%Y-%m-%d')
            )
            if benchmark_data.empty:
                return {"error": "Benchmark data could not be downloaded."}
            
            # Process benchmark data and handle potential errors
            try:
                benchmark_returns, benchmark_cum_returns, benchmark_metrics = process_benchmark(benchmark_data, returns, portfolio1)
            except ValueError as ve:
                return {"error": str(ve)}
            except Exception as e:
                return {"error": f"Benchmark processing failed: {e}"}
            
            # Calculate metrics for the portfolio without benchmark comparison
            portfolio_metrics = calculate_metrics(returns, cum_returns, portfolio1['rf_rate'])
            
            # Compile backtest results
            backtest_results = {
                'returns': returns,
                'cum_returns': cum_returns,
                'weights': allocations,
                'price_data': price_data[available],
                'available_selected': available,
                'metrics': portfolio_metrics,
                'benchmark_returns': benchmark_returns,
                'benchmark_cum_returns': benchmark_cum_returns,
                'benchmark_metrics': benchmark_metrics
            }
            
            return {"backtest_results": backtest_results}
    
        elif comparison_type == "vs_portfolio":
            # Determine overlapping date range between the two portfolios
            overlap_start = max(portfolio1['start_date'], portfolio2['start_date'])
            overlap_end = min(portfolio1['end_date'], portfolio2['end_date'])
            if overlap_start >= overlap_end:
                return {"error": "No overlapping date ranges."}
            
            # Handle data for both portfolios within the overlapping date range
            available_a, missing_a, price_data_a = handle_portfolio_data(portfolio1, (overlap_start, overlap_end))
            available_b, missing_b, price_data_b = handle_portfolio_data(portfolio2, (overlap_start, overlap_end))
            if not available_a or not available_b:
                return {"error": "One of the portfolios has no valid tickers with available data."}
            
            # Adjust allocations based on available tickers for both portfolios
            allocations_a = adjust_allocations([portfolio1['allocations'][portfolio1['selected'].index(t)] for t in available_a])
            allocations_b = adjust_allocations([portfolio2['allocations'][portfolio2['selected'].index(t)] for t in available_b])
            
            # Perform backtest for both portfolios
            returns_a, cum_returns_a = backtest(
                weights=np.array(allocations_a) / 100,
                prices=price_data_a[available_a],
                rebalance_freq=portfolio1['rebalance_freq'],
                broker_fee=portfolio1['broker_fee']
            )
            returns_b, cum_returns_b = backtest(
                weights=np.array(allocations_b) / 100,
                prices=price_data_b[available_b],
                rebalance_freq=portfolio2['rebalance_freq'],
                broker_fee=portfolio2['broker_fee']
            )
            if cum_returns_a.empty or cum_returns_b.empty:
                return {"error": "One of the cumulative returns is empty."}
            
            # Add error handling for metrics calculation
            if returns_a.empty or returns_b.empty:
                return {"error": "Unable to calculate returns for one or both portfolios"}
                
            # Calculate metrics with proper error handling
            try:
                metrics_a = calculate_metrics(returns_a, cum_returns_a, portfolio1['rf_rate'])
                metrics_b = calculate_metrics(returns_b, cum_returns_b, portfolio2['rf_rate'])
            except Exception as e:
                return {"error": f"Error calculating metrics: {str(e)}"}
                
            # Ensure no None values in metrics
            metrics_a = {k: 0.0 if v is None else v for k, v in metrics_a.items()}
            metrics_b = {k: 0.0 if v is None else v for k, v in metrics_b.items()}

            # Align two portfolios' returns
            common_index = cum_returns_a.index.intersection(cum_returns_b.index)
            if common_index.empty:
                return {"error": "No overlapping dates after backtesting."}
            returns_a = returns_a.loc[common_index]
            returns_b = returns_b.loc[common_index]
            cum_returns_a = cum_returns_a.loc[common_index]
            cum_returns_b = cum_returns_b.loc[common_index]
            
            # Compile backtest results
            backtest_results = {
                'returns_a': returns_a,
                'cum_returns_a': cum_returns_a,
                'weights_a': allocations_a,
                'price_data_a': price_data_a[available_a],
                'metrics_a': metrics_a,
                'returns_b': returns_b,
                'cum_returns_b': cum_returns_b,
                'weights_b': allocations_b,
                'price_data_b': price_data_b[available_b],
                'metrics_b': metrics_b
            }
            
            return {"backtest_results": backtest_results}
    except Exception as e:
        return {"error": f"An unexpected error occurred: {e}"}

# ----------------------------
# Display Backtest Results
# ----------------------------

def display_backtest_results(results, comparison_type, portfolio1_name, portfolio2_name=None):
    # Do not pre-format metrics here
    if comparison_type == "Portfolio vs Benchmark":
        display_portfolio_vs_benchmark(results, portfolio1_name)
    elif comparison_type == "Portfolio vs Portfolio":
        display_portfolio_vs_portfolio(results, portfolio1_name, portfolio2_name)

def display_portfolio_vs_benchmark(results, portfolio_name):
    # Extract data from results
    returns = results['returns']
    cum_returns = results['cum_returns']
    weights = results['weights']
    price_data = results['price_data']
    available_selected = results['available_selected']
    metrics = results['metrics']
    benchmark_metrics = results.get('benchmark_metrics', {})
    benchmark_returns = results.get('benchmark_returns', pd.Series(dtype=float))
    benchmark_cum_returns = results.get('benchmark_cum_returns', pd.Series(dtype=float))

    # ... [Other code for displaying charts and metrics]

    # Performance Metrics Tab
    with metrics_tab:
        st.header("📊 Key Performance Metrics")
        st.subheader("Performance Comparison Table")

        # Define metrics separately
        common_metrics = [
            'Annualized Return (CAGR)',
            'Maximum Drawdown',
            'Sharpe Ratio',
            'Sortino Ratio',
            'Standard Deviation (Annualized)',
            'Beta',
            'Alpha (annualized)',
            'R2',
            'Calmar Ratio',
            'Information Ratio',
            'Tracking Error',
            'Upside Capture Ratio',
            'Downside Capture Ratio',
            'Best Year',
            'Worst Year'
        ]

        # Filter metrics
        filtered_portfolio_metrics = {k: v for k, v in metrics.items() if k in common_metrics}
        filtered_benchmark_metrics = {k: v for k, v in benchmark_metrics.items() if k in common_metrics}

        # Create DataFrame for Portfolio vs Benchmark Metrics
        performance_comparison_df = create_performance_stats_df_portfolio_vs_portfolio(
            metrics_a=filtered_portfolio_metrics,
            metrics_b=filtered_benchmark_metrics,
            portfolio1=portfolio_name,
            portfolio2='Benchmark'
        )

        # Rename columns to ensure 'Portfolio' exists
        if portfolio_name != 'Portfolio':
            performance_comparison_df = performance_comparison_df.rename(
                columns={portfolio_name: 'Portfolio'}
            )

        # Define which metrics are percentage-based
        percentage_metrics = [
            'Annualized Return (CAGR)',
            'Maximum Drawdown',
            'Standard Deviation (Annualized)',
            'Alpha (annualized)',
            'Tracking Error',
            'Upside Capture Ratio',
            'Downside Capture Ratio',
            'Best Year',
            'Worst Year'
        ]

        # Style the metrics table
        def highlight_better(row):
            styles = [''] * len(row)
            portfolio_val = row["Portfolio"]
            benchmark_val = row["Benchmark"]

            # Skip if Portfolio is 'N/A'
            if portfolio_val == "N/A" or benchmark_val == "N/A":
                return styles

            try:
                # Convert string values to float if possible
                if isinstance(portfolio_val, str):
                    portfolio_num = float(portfolio_val.strip('%').strip('$').replace(',', ''))
                else:
                    portfolio_num = portfolio_val

                if isinstance(benchmark_val, str):
                    benchmark_num = float(benchmark_val.strip('%').strip('$').replace(',', ''))
                else:
                    benchmark_num = benchmark_val

                # Determine if higher is better or lower is better based on metric name
                metric_name = row.name
                lower_is_better = any(keyword in metric_name for keyword in [
                    'Drawdown', 'Deviation', 'Tracking Error', 'Downside Capture Ratio'
                ])
                if lower_is_better:
                    better = portfolio_num < benchmark_num
                else:
                    better = portfolio_num > benchmark_num

                # Apply styling
                if better:
                    styles[0] = 'background-color: #d4f7d4'  # Portfolio Green
                    styles[1] = 'background-color: #f7d4d4'  # Benchmark Red
                else:
                    styles[0] = 'background-color: #f7d4d4'  # Portfolio Red
                    styles[1] = 'background-color: #d4f7d4'  # Benchmark Green
            except (ValueError, TypeError):
                pass  # Skip if conversion fails

            return styles

        def custom_formatter(x, metric):
            try:
                # Attempt to convert to float after stripping possible symbols
                if x == "N/A":
                    return "N/A"
                value = float(str(x).strip('%').strip('$').replace(',', ''))
                if metric in percentage_metrics:
                    return f"{value:.2f}%"
                else:
                    return f"{value:.2f}"
            except (ValueError, TypeError):
                return "N/A"  # Return 'N/A' if conversion fails

        # Apply formatter row-wise to access metric name
        def row_formatter(row):
            metric = row.name
            formatted = {}
            for col in ['Portfolio', 'Benchmark']:
                formatted[col] = custom_formatter(row[col], metric)
            return pd.Series(formatted)

        # Ensure 'Portfolio' and 'Benchmark' columns exist
        required_columns = ['Portfolio', 'Benchmark']
        for col in required_columns:
            if col not in performance_comparison_df.columns:
                performance_comparison_df[col] = "N/A"

        # Assuming performance_comparison_df is already created and correctly named
        formatted_metrics = performance_comparison_df.apply(row_formatter, axis=1)
        performance_comparison_df[['Portfolio', 'Benchmark']] = formatted_metrics[['Portfolio', 'Benchmark']]

        # Apply the highlight function to style the rows
        styled_metrics = performance_comparison_df.style.apply(
            highlight_better,
            axis=1
        ).format({
            'Portfolio': lambda x: x,
            'Benchmark': lambda x: x
        })

        # Add metric explanations
        metric_explanations = {
            'Annualized Return (CAGR)': 'Compound Annual Growth Rate - The geometric mean of portfolio returns',
            'Maximum Drawdown': 'Largest peak-to-trough decline',
            'Sharpe Ratio': 'Risk-adjusted return relative to risk-free rate',
            'Sortino Ratio': 'Risk-adjusted return focusing on downside volatility',
            'Standard Deviation (Annualized)': 'Measure of portfolio volatility',
            'Beta': 'Portfolio sensitivity to market movements',
            'Alpha (annualized)': 'Excess return compared to benchmark',
            'R2': 'How well portfolio returns are explained by benchmark',
            'Calmar Ratio': 'Return relative to maximum drawdown',
            'Information Ratio': 'Risk-adjusted excess returns vs benchmark',
            'Tracking Error': 'Standard deviation of portfolio vs benchmark difference',
            'Upside Capture Ratio': 'Performance relative to benchmark in up markets',
            'Downside Capture Ratio': 'Performance relative to benchmark in down markets',
            'Best Year': 'Highest annual return',
            'Worst Year': 'Lowest annual return'
        }

        # Display the metrics table
        col1, col2 = st.columns(2)
        with col1:
            st.dataframe(styled_metrics)
    
        with col2:
            with st.expander("📖 Metric Definitions", expanded=True):
                for metric, explanation in metric_explanations.items():
                    st.markdown(f"**{metric}**")
                    st.markdown(f"_ {explanation}_")
                    st.markdown("---")

# ----------------------------
# Create Performance Stats DataFrame
# ----------------------------

def create_performance_stats_df_portfolio_vs_portfolio(metrics_a, metrics_b, portfolio1, portfolio2):
    """Creates a refined DataFrame for Portfolio vs Portfolio performance statistics."""
    metrics = [
        'Annualized Return (CAGR)',
        'Sharpe Ratio',
        'Sortino Ratio',
        'Maximum Drawdown',
        'Beta',
        'Alpha (annualized)',
        'Information Ratio',
        'R2',
        'Calmar Ratio',
        'Tracking Error',
        'Upside Capture Ratio',
        'Downside Capture Ratio',
        'Best Year',
        'Worst Year'
    ]

    data = {
        'Metric': metrics,
        portfolio1: [metrics_a.get(metric, "N/A") for metric in metrics],
        portfolio2: [metrics_b.get(metric, "N/A") for metric in metrics]
    }

    performance_df = pd.DataFrame(data)
    performance_df.set_index('Metric', inplace=True)
    return performance_df

problem: enhancing the Performance Comparison data as there are too many nan
if the segments code need update, please return the full updated segments code, no need import or other stuff, only the full version of the updated segment