import pandas as pd
from prophet import Prophet
from predict_prophet_helper import *
from predict_helper import *

# Adjust Pandas display settings
pd.set_option('display.max_columns', None)  # Show all columns
pd.set_option('display.width', None)        # Auto-detect the display width
pd.set_option('display.max_colwidth', None) # Show full content of each column


print("--start prophet prediction module--")
config, resultId, output_data_file, regressors, histData, futureData, cutoff_minimum, log_transform, anomaly_threshold_n_stddev, onlyModelAnalysis = predict_initialize()
prophet_initialize()
prophet_checkConfig(config, histData, futureData)
country_holidays_code, seasonalities = prophet_getSettings(config)


print("--start processing--")

# ToDo: implement handing over a constant 'changepoints' list of dates
# ToDo: implement handing over a constant 'holidays' list of dates with 'holiday, 'ds', 'lower_window', 'upper_window' columns
# ToDo: implement holidays_prior_scale


# create an instance of the Prophet model
print("--creating instance of the Prophet model--")
try:
    model = Prophet(** config)
except Exception as e:
    exitWithError(13, f"Error: An unexpected error occurred: {str(e)} when creating an instance of the Prophet model.")


# add country holidays if the 'holidays_country_code' is set
print("--adding country holidays--")
if country_holidays_code:
    try:
        model.add_country_holidays(country_name = country_holidays_code)
        # ToDo: implement 'lower_window', 'upper_window' and 'holidays' parameters
    except ValueError as e:
        exitWithError(14, f"Error: the country code {country_holidays_code} for adding holidays is not supported.")
    except Exception as e:
        exitWithError(15, f"Error: An unexpected error occurred: {str(e)} when adding country holidays to the Prophet model.")


# Add regressors
print("--adding regressors--")
try:
    for regressor in regressors:
        # standardize: True, False or 'auto' (default)
        # mode: 'additive' (default) or 'multiplicative'
        # prior_scale: positive float > 0, default 10.0
        if not 'standardize' in regressor:
            regressor["standardize"] = 'auto'
        if not 'mode' in regressor:
            regressor["mode"] = 'additive'
        if not 'prior_scale' in regressor:
            regressor["prior_scale"] = 10.0
        model.add_regressor(regressor["name"], prior_scale=regressor["prior_scale"], standardize=regressor["standardize"], mode=regressor["mode"])
except ValueError as e:
    exitWithError(16, f"Error: {e} when adding a regressor to the Prophet model.")
except Exception as e:
    exitWithError(17, f"Error: An unexpected error occurred: {str(e)} when adding a regressor to the Prophet model.")


# Add seasonalities
print("--adding seasonalities--")
for s in seasonalities:
    try:
        if s["condition_name"] == "null":
            print("adding seasonality without condition: ", s["name"])
            model.add_seasonality(s["name"], s["period"], s["fourier_order"], s["prior_scale"], s["mode"])
        else:
            print("adding seasonality with condition: ", s["name"])
            model.add_seasonality(s["name"], s["period"], s["fourier_order"], s["prior_scale"], s["mode"], s["condition_name"])
    except ValueError as e:
        exitWithError(18, f"Error: {e} when adding a seasonality to the Prophet model.")



# fit the model
try:
    print("--fitting the model--")
    model.fit(histData) # iter_warmup=500
except ValueError as e:
    exitWithError(19, f"Error: {e} while fitting model.")
except Exception as e:
    exitWithError(19, f"Error: {e} while fitting model.")


# Make predictions for the future time periods

if not onlyModelAnalysis:
    try:
        print("--predicting future data--")
        forecast = model.predict(futureData)
    except Exception as e:
        exitWithError(20, f"Error: {e} while predicting future data.")

    forecast.to_csv(
        "/var/www/prophettmp/forecastFuture.csv",
        index=True,
        sep=";",
        header=True,
        na_rep="NA",
        encoding="utf-8"
    )


# Make predictions for the past
try:
    print("--predicting past data--")
    forecast_past = model.predict(histData)
except Exception as e:
    exitWithError(21, f"Error: {e} while predicting past data.")

forecast_past.to_csv(
    "/var/www/prophettmp/forecastPast.csv",
    index=True,
    sep=";",
    header=True,
    na_rep="NA",
    encoding="utf-8"
)

if not onlyModelAnalysis:
    wMAPE = predict_processAfterForecast(log_transform, cutoff_minimum, anomaly_threshold_n_stddev, forecast, ["yhat", "yhat_lower", "yhat_upper"], forecast_past, ["yhat", "yhat_lower", "yhat_upper"], histData, ["y"])
else:
    wMAPE = predict_processAfterForecast(log_transform, cutoff_minimum, anomaly_threshold_n_stddev, None, None, forecast_past, ["yhat", "yhat_lower", "yhat_upper"], histData, ["y"])


model.history['ds'] = model.history['ds'].dt.strftime('%Y-%m-%d %H:%M:%S')
model.changepoints = model.changepoints.dt.strftime('%Y-%m-%d %H:%M:%S')

if config.get("holidays", False) and not config["holidays"].empty:
    model.holidays['ds'] = model.holidays['ds'].dt.strftime('%Y-%m-%d %H:%M:%S')


# print top 10 forecast records
if not onlyModelAnalysis:
    print("forecast:")
    print(forecast.head(10))


# ds: The timestamp (or date) for the forecasted point.
# trend: The underlying long‐term trend component of the forecast
# yhat_lower: The lower bound of the overall forecast’s prediction interval. Provides a boundary for the minimum expected value of yhat (the point forecast) based on the model’s uncertainty settings. Notes: The width of the interval is determined by the confidence level (commonly 80% by default in Prophet).
# yhat_upper: The upper bound of the forecast’s prediction interval. Usage: Indicates the maximum likely value for yhat given the uncertainty in the model’s components. Notes: When plotting, the band between yhat_lower and yhat_upper visualizes the forecast uncertainty.
# trend_lower: The lower bound of the uncertainty interval for the trend component. Usage: Helps to understand the minimum expected level of the trend given the model’s variability in the trend estimation. Notes: In your sample these values are identical to the trend, which can happen if trend uncertainty isn’t separately estimated.
# trend_upper: The upper bound for the trend component’s uncertainty interval. Usage: Indicates the highest expected value for the trend component under model uncertainty. Notes: As with trend_lower, identical values mean that the uncertainty on the trend was either negligible or not computed separately.
# additive_terms: The sum of all additive components (seasonal effects and extra regressors that are modeled additively) included in the forecast. Usage: Separates the contribution of seasonal and other additive effects from the trend. Notes: When combined with the trend (and possibly adjusted by multiplicative terms), they yield the final forecast yhat.
# additive_terms_lower: The lower uncertainty bound for the combined additive components. Usage: Provides insight into the minimum contribution expected from the additive effects. Notes: The uncertainty here propagates from each additive component’s variability.
# additive_terms_upper: The upper uncertainty bound for the additive terms. Usage: Indicates the maximum contribution expected from the additive components given model uncertainty.
# daily: The contribution from the daily seasonality component (if enabled). Usage: Captures recurring patterns that occur within a 24‑hour cycle (for instance, hourly or within-day fluctuations). Notes: Its magnitude and shape depend on how you configured daily seasonality in Prophet.
# daily_lower: The lower bound of the daily seasonality’s uncertainty interval. Usage: Reflects the minimum expected daily effect at each time point.
# daily_upper: The upper bound for the daily seasonality’s contribution. Usage: Shows the maximum expected contribution from daily cycles given the model’s uncertainty.
# weekly, yearly - see above
# multiplicative_terms: The sum of all multiplicative components (if any are specified in the model). Usage: In Prophet you can model seasonality as multiplicative; here the value adjusts the trend multiplicatively rather than additively. Notes: In your sample, these values are 0.0—indicating that either no multiplicative seasonality was applied or its effect was negligible.
# multiplicative_terms_lower: The lower bound for the multiplicative seasonal effects. Usage: Provides a minimum expected adjustment factor when multiplicative seasonality is in use.
# multiplicative_terms_upper: The upper bound for the multiplicative terms’ uncertainty. Usage: Indicates the maximum multiplicative adjustment to be applied.
# yhat: The final forecasted value (the point estimate). Usage: Represents the sum (or combination) of the trend, additive, and (if applicable) multiplicative components. Notes: This is the primary forecast value you would compare against actual outcomes.

# Reports:
# Forecast with Uncertainty Bounds (yhat, yhat_lower, yhat_upper)
# Trend Component Analysis (trend, trend_lower, trend_upper)
# Seasonal Decomposition: additive_terms, additive_terms_lower, additive_terms_upper, daily, daily_lower, daily_upper, weekly, weekly_lower, weekly_upper, yearly, yearly_lower, yearly_upper
# Multiplicative Components: multiplicative_terms, multiplicative_terms_lower, multiplicative_terms_upper

# yhat = yhat = trend + seasonal effects + regressor contributions + multiplicative effects
# additive_terms = seasonal effects + regressor contributions
# additive_terms = daily + weekly + yearly + extra_regressors


print("--end processing--")

if not onlyModelAnalysis:
    futureColumns = [
        ['yhat', 'FC'], 
        ['yhat_lower', 'COL'], 
        ['yhat_upper', 'COU'],
        ['anomaly', 'FAN']
        #'trend', 
        #'trend_lower', 
        #'trend_upper', 
        #'additive_terms', 
        #'additive_terms_lower', 
        #'additive_terms_upper'
        ]

    # Add the required elements for each regressor
    #for reg in regressors:
    #    name = reg['name']
    #    futureColumns.extend([name, f"{name}_lower", f"{name}_upper"])

    print("--saving future results--")
    predict_saveResults(resultId, 'FUTURE', forecast, futureColumns, 0.0)


print("forecast_past:")
print(forecast_past.head(10))

histColumns = [
    ['yhat', 'MOD'], 
    ['anomaly', 'ANO'],
    ['interval_scaled_daily_mape', 'SDM']
    ]

# Add the required elements for each regressor
#for reg in regressors:
#    name = reg['name']
#    histColumns.extend([name, f"{name}_lower", f"{name}_upper"])

print("--saving historical results--")
predict_saveResults(resultId, 'HIST', forecast_past, histColumns, wMAPE)


exitWithError(0, "Success: No errors occurred.", additional_data={"wMAPE": wMAPE})