from data_loader.types import ForwardReturnSeries, XDataFrame, ySeries
from utils.evaluate import discretize_threeway_threshold, evaluate_predictions
from utils.helpers import equal_except_nan
from .train_model import train_model
import pandas as pd
from models.base import Model
from models.model_map import default_feature_selector_classification
from typing import Optional
from config.types import Config
from .types import BetSizingWithMetaOutcome, ModelOverTime, TransformationsOverTime
from training.walk_forward import walk_forward_process_transformations
from transformations.scaler import get_scaler
from transformations.rfe import RFETransformation
from transformations.pca import PCATransformation

def bet_sizing_with_meta_model(
                            X: XDataFrame,
                            input_predictions: pd.Series,
                            y: ySeries,
                            forward_returns: ForwardReturnSeries,
                            model: Model,
                            config: Config,
                            model_suffix: str,
                            from_index: Optional[pd.Timestamp],
                            transformations_over_time: Optional[TransformationsOverTime] = None,
                            preloaded_models: Optional[ModelOverTime] = None
                        ) -> BetSizingWithMetaOutcome:

    input_predictions.name = "model_predictions"
    discretized_predictions = input_predictions.apply(discretize_threeway_threshold(0.33))
    discretized_predictions.name = "model_discretized_predictions"

    meta_y: pd.Series = pd.concat([discretized_predictions, y], axis=1).apply(equal_except_nan, axis = 1)
    meta_X = pd.concat([X, input_predictions, discretized_predictions], axis = 1)

    if transformations_over_time is None:
        print("Preprocess transformations")
        transformations_over_time = walk_forward_process_transformations(
            X = meta_X,
            y = meta_y,
            forward_returns = forward_returns,
            expanding_window = config.expanding_window_meta,
            window_size = config.sliding_window_size_meta,
            retrain_every = config.retrain_every,
            from_index = from_index,
            transformations= [
                get_scaler(config.scaler),
                PCATransformation(ratio_components_to_keep=0.5, sliding_window_size=config.sliding_window_size_meta),
                RFETransformation(n_feature_to_select=40, model=default_feature_selector_classification)
            ],
        )

    meta_outcome = train_model(
        ticker_to_predict = "prediction_correct",
        X = meta_X,
        y = meta_y,
        forward_returns = forward_returns,
        model = model,
        expanding_window = config.expanding_window_meta,
        sliding_window_size = config.sliding_window_size_meta,
        retrain_every = config.retrain_every,
        from_index = from_index,
        no_of_classes = 'two',
        level = 'meta',
        output_stats = config.mode == 'training',
        transformations_over_time = transformations_over_time,
        model_over_time = preloaded_models,
    )

    meta_predictions = meta_outcome.predictions
    bet_size = meta_outcome.probabilities.iloc[:,1]
    avg_predictions_with_sizing = input_predictions * meta_predictions * bet_size

    if config.mode == 'training':
        stats = evaluate_predictions(
            forward_returns = forward_returns,
            y_pred = avg_predictions_with_sizing,
            y_true = y,
            no_of_classes = 'three-balanced',
            discretize=False
        )
        print(stats)
    else:
        stats = None
    model_id = "model_" + config.target_asset[1] + "_" + model_suffix

    return BetSizingWithMetaOutcome(model_id, meta_outcome, transformations_over_time, avg_predictions_with_sizing, stats)
