# Copyright (c) 2020 ING Bank N.V.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


import pandas as pd
import numpy as np
from probatus.utils import assure_numpy_array
from probatus.metric_volatility.utils import sample_data
from sklearn.model_selection import train_test_split


def get_metric(
    X,
    y,
    clf,
    test_size,
    split_seed,
    scorers,
    train_sampling_type=None,
    test_sampling_type=None,
    train_sampling_fraction=1,
    test_sampling_fraction=1,
):
    """
    Draws random train/test sample from the data using random seed and calculates metric of interest.

    Args:
        X (np.array or pd.DataFrame):
            Dataset with features.

        y (np.array or pd.Series):
            Target of the prediction.

        clf (model object):
            Binary classification model or pipeline.

        test_size (float):
            Fraction of data used for testing the model.

        split_seed (int):
            Randomized seed used for splitting data.

        scorers (list of Scorers):
            List of Scorer objects used to score the trained model.

        train_sampling_type (str, optional):
            String indicating what type of sampling should be applied on train set:

                - `None`: indicates that no additional sampling is done after splitting data,
                - `'bootstrap'`: indicates that sampling with replacement will be performed on train data,
                - `'subsample'`: indicates that sampling without repetition will be performed  on train data.

        test_sampling_type (str, optional):
            string indicating what type of sampling should be applied on test set:

                - `None`: indicates that no additional sampling is done after splitting data
                - `'bootstrap'`: indicates that sampling with replacement will be performed on test data
                - `'subsample'`: indicates that sampling without repetition will be performed  on test data

        train_sampling_fraction (float, optional):
            Fraction of train data sampled, if sample_train_type is not None. Default value is 1.

        test_sampling_fraction (float, optional):
            Fraction of test data sampled, if sample_test_type is not None. Default value is 1.

    Returns:
        (pd.Dataframe):
            Dataframe with results for a given model trained. Rows indicate the metric measured and columns ther results
    """

    if not (isinstance(X, np.ndarray) or isinstance(X, pd.DataFrame)):
        X = assure_numpy_array(X)
    if not (isinstance(X, np.ndarray) or isinstance(X, pd.Series)):
        y = assure_numpy_array(y)

    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=test_size, random_state=split_seed, stratify=y
    )

    # Sample data based on the input arguments
    X_train, y_train = sample_data(
        X=X_train,
        y=y_train,
        sampling_type=train_sampling_type,
        sampling_fraction=train_sampling_fraction,
        dataset_name="train",
    )
    X_test, y_test = sample_data(
        X=X_test,
        y=y_test,
        sampling_type=test_sampling_type,
        sampling_fraction=test_sampling_fraction,
        dataset_name="test",
    )

    clf = clf.fit(X_train, y_train)

    results_columns = ["metric_name", "train_score", "test_score", "delta_score"]
    results = pd.DataFrame([], columns=results_columns)

    for scorer in scorers:
        score_train = scorer.score(clf, X_train, y_train)
        score_test = scorer.score(clf, X_test, y_test)
        score_delta = score_train - score_test

        results = results.append(
            pd.DataFrame(
                [[scorer.metric_name, score_train, score_test, score_delta]],
                columns=results_columns,
            )
        )
    return results
