import numpy as np
import pandas as pd
from typing import Optional, List, Dict


def generate_volatility_scenarios(
    original_data: pd.Series,
    sigmas: Optional[List[float]] = None,
    match_endpoints: bool = False,
    random_seed: Optional[int] = None,
) -> pd.DataFrame:
    """
    Generate synthetic price series under various volatility scenarios.

    Args:
        original_data (pd.Series): The original price time series (must be >2 values).
        sigmas (Optional[List[float]]): List of volatilities (std dev of log-returns).
            If None, uses the original volatility and ±5%, 10%, 20%, 30% changes.
        match_endpoints (bool): If True, adjusts synthetic series to match original start/end prices.
        random_seed (Optional[int]): Random seed for reproducibility.

    Returns:
        pd.DataFrame: DataFrame with columns: original and one per volatility scenario.

    Raises:
        ValueError: If original_data is too short.
    """
    if len(original_data) < 2:
        raise ValueError("original_data must have at least 2 data points.")

    if random_seed is not None:
        np.random.seed(random_seed)

    index = original_data.index
    prices = original_data.values
    log_returns = np.log(prices[1:] / prices[:-1])
    orig_sigma = np.std(log_returns)
    orig_mu = np.mean(log_returns)

    if sigmas is None:
        percent_changes = np.array([-0.3, -0.2, -0.1, -0.05, 0, 0.05, 0.1, 0.2, 0.3])
        sigmas = [(1 + p) * orig_sigma for p in percent_changes]
        sigmas = sorted(
            set([round(s, 10) for s in sigmas])
        )  # remove duplicates, keep order

    results: Dict[str, pd.Series] = {"original": original_data}

    total_log_return = np.log(prices[-1] / prices[0])

    for sigma in sigmas:
        # Generate synthetic log returns
        synthetic_log_returns = np.random.normal(
            loc=orig_mu, scale=sigma, size=len(log_returns)
        )

        if match_endpoints:
            # Scale log returns to match total log return
            synthetic_cum_return = synthetic_log_returns.sum()
            scaling = (
                total_log_return / synthetic_cum_return
                if synthetic_cum_return != 0
                else 1.0
            )
            synthetic_log_returns = synthetic_log_returns * scaling

        # Reconstruct price series
        synthetic_prices = [prices[0]]
        for ret in synthetic_log_returns:
            synthetic_prices.append(synthetic_prices[-1] * np.exp(ret))
        scenario_name = f"vol_{sigma:.4f}"
        results[scenario_name] = pd.Series(synthetic_prices, index=index)

    df = pd.DataFrame(results)
    return df
