import logging
from predict_helper import *


def prophet_initialize():
    print("--Initializing Prophet module--")
    logger = logging.getLogger("cmdstanpy")
    logger.addHandler(logging.NullHandler())
    logger.propagate = False
    logger.setLevel(logging.WARNING)

    logging.getLogger("prophet").setLevel(logging.WARNING)
    logging.getLogger("cmdstanpy").setLevel(logging.WARNING)



def prophet_checkConfig(config, input_data, future_data):

    # growth: type of trend growth used in the model
    if 'growth' in config:
        if not config['growth'] in ['linear', 'logistic', 'flat'] :
            exitWithError(6, "Error: growth parameter should be either 'linear', 'logistic' or 'flat.")
    else:
        config['growth'] = 'flat' # default: flat


    # make sure that we have a 'cap' column if the growth is logistic
    # in this case, optionally also add a 'floor' column. Then, TODO: floor must be smaller than cap
    if config['growth'] == 'logistic':
        if 'logistic_growth_cap' not in config:
            exitWithError(7, "Error: logistic growth requires 'logistic_growth_cap' parameter.")
        else:
            if config["logistic_growth_cap"] == None:
                exitWithError(7, "Error: logistic growth requires non-null 'logistic_growth_cap' parameter.")
            else:
                input_data['cap'] = config["logistic_growth_cap"]
                future_data['cap'] = config["logistic_growth_cap"]
        if 'logistic_growth_floor' in config:
            if config["logistic_growth_floor"] != None:
                input_data['floor'] = config["logistic_growth_floor"]
                future_data['floor'] = config["logistic_growth_floor"]
    config.pop("logistic_growth_cap", None)
    config.pop("logistic_growth_floor", None)

    # mcmc_samples: To control the number of MCMC samples for the Bayesian estimation of the model parameters
    # critical setting for controlling how Prophet estimates model parameters, balancing between computational efficiency and the thoroughness of uncertainty estimation.
    # ToDo: check that it's an integer >= 0
    if not 'mcmc_samples' in config:
        config['mcmc_samples'] = 0 # default

    # stan_backend: backend to use for fitting the model with Stan, the probabilistic programming language used for Bayesian inference. This parameter allows users to choose the computational engine that runs the Stan code for model fitting
    if not 'stan_backend' in config:
        config['stan_backend'] = None # default
    else:
        if not config['stan_backend'] in ['cmdstanpy', 'pystan', None]:
            exitWithError(8, "Error: stan_backend parameter should be either 'cmdstanpy' or 'pystan'.")

    # interval_width: the width of the uncertainty intervals for the forecasts. This parameter determines the range within which the true values are expected to lie with a certain level of confidence.
    # The interval_width corresponds to the confidence level for the uncertainty intervals. For instance, an interval_width of 0.90 implies that there is a 90% chance that the true value will fall within the predicted interval
    if not 'interval_width' in config:
        config['interval_width'] = 0.8 # default
    # ToDo: check that it's a float between 0 and 1

    # n_changepoints: controls the number of potential changepoints in the trend of the time series. Changepoints are locations in the data where the trend can change direction or rate.
    if not 'n_changepoints' in config:
        config['n_changepoints'] = 25 # default
    # ToDo: check integer >= 0

    # seasonality_mode: specifies how seasonality components are modeled. This parameter determines whether the seasonal effects (such as daily, weekly, and yearly patterns) are treated as additive or multiplicative.
    if not 'seasonality_mode' in config:
        config['seasonality_mode'] = 'additive' # default
    else:
        if not config['seasonality_mode'] in ['additive', 'multiplicative']:
            exitWithError(9, "Error: seasonality_mode parameter should be either 'additive' or 'multiplicative'.")

    # changepoint_range: specifies the proportion of the historical data within which the model will look for potential changepoints. Changepoints are points in time where the trend changes its direction or rate of growth/decline.
    if not "changepoint_range" in config:
        config["changepoint_range"] = 0.8 # default
    # ToDo: check float between 0 and 1

    # daily_seasonality: specifies whether to include daily seasonality in the model and, if so, how to configure it. Daily seasonality captures patterns that repeat every 24 hours, which can be crucial for data that exhibits intra-day variations, such as website traffic, energy consumption, or sales data.
    if not 'daily_seasonality' in config:
        config['daily_seasonality'] = True # default
    else:
        if not config['daily_seasonality'] in ['auto', True, False]:
            # ToDo: there is one correct use case: an integer: Specifies the Fourier order of the seasonal components for the daily seasonality, which determines the flexibility and complexity of the seasonal pattern.
            exitWithError(10, "Error: daily_seasonality parameter should be either 'auto', True or False.")

    # weekly_seasonality: specifies whether to include weekly seasonality in the model and, if so, how to configure it. Weekly seasonality captures patterns that repeat every week, which can be essential for data that shows variations depending on the day of the week, such as retail sales, website traffic, or call center volumes.
    if not 'weekly_seasonality' in config:
        config['weekly_seasonality'] = True # default
    else:
        if not config['weekly_seasonality'] in ['auto', True, False]:
            # ToDo: there is one correct use case: an integer: Specifies the Fourier order of the seasonal components for the weekly seasonality, which determines the flexibility and complexity of the seasonal pattern.
            exitWithError(11, "Error: weekly_seasonality parameter should be either 'auto', True or False.")

    # yearly_seasonality: specifies whether to include yearly seasonality in the model and, if so, how to configure it. Yearly seasonality captures patterns that repeat every year, which can be crucial for data that exhibits annual variations, such as sales influenced by holidays, weather patterns, or agricultural cycles.
    if not 'yearly_seasonality' in config:
        config['yearly_seasonality'] = True # default
    else:
        if not config['yearly_seasonality'] in ['auto', True, False]:
            # ToDo: there is one correct use case: an integer: Specifies the Fourier order of the seasonal components for the yearly seasonality, which determines the flexibility and complexity of the seasonal pattern.
            exitWithError(12, "Error: yearly_seasonality parameter should be either 'auto', True or False.")

    # uncertainty_samples: To specify the number of Monte Carlo samples to draw for estimating the uncertainty intervals of the forecast
    if not "uncertainty_samples" in config:
        config["uncertainty_samples"] = 1000 # default
    # ToDo: check for integer >= 0

    # changepoint_prior_scale: controls the flexibility of the automatic changepoint detection. This parameter influences how strongly the model allows the trend to change at the identified changepoints
    if not 'changepoint_prior_scale' in config:
        config['changepoint_prior_scale'] = 0.05 # default
    # ToDo: check for positive float > 0

    # seasonality_prior_scale: controls the flexibility of the seasonal components. This parameter influences how much the seasonality can vary, thereby impacting the overall seasonality fit in the model.
    if not 'seasonality_prior_scale' in config:
        config['seasonality_prior_scale'] = 10.0 # default
    # ToDo: check for positive float > 0


def prophet_getSettings(config) -> tuple[str, list[dict]]:
    # country_holidays_code: specifies the country code for the country-specific holidays to include in the model. This parameter allows users to add holidays that are specific to a particular country, such as national holidays, public observances, or cultural events.
    if not 'holidays_country_code' in config:
        country_holidays_code = None # default
    else:
        country_holidays_code = config["holidays_country_code"]
    # ToDo: check if string, i. e. "US" or "DE"
    config.pop("holidays_country_code", None)

    seasonalities = config["seasonalities"] if config.get("seasonalities", False) else []
    config.pop("seasonalities", None)

    return country_holidays_code, seasonalities
