import pandas as pd
import numpy as np
from sklearn.decomposition import PCA
from Promethee import promethee
from typing import Union


def run_promethee_analysis(
    data: pd.DataFrame,
    weights: pd.DataFrame,
    criteria_min_max: pd.DataFrame,
    pref_type: Union[int, pd.DataFrame],
    q_param: Union[int, pd.DataFrame],
    p_param: Union[int, pd.DataFrame],
) -> pd.DataFrame:
    """
    Run Promethee analysis on a given dataset.
    :param data: DataFrame containing the criteria data.
    :param weights: Weights DataFrame for the criteria.
    :param criteria_min_max: Boolean DataFrame indicating if each criterion is
     to be maximized (True) or minimized (False).
    :param pref_type:
    :param q_param:
    :param p_param:
    :return: Promethee results concatenated with the original data as a
     dataframe.
    """
    # Preconditions checks
    if data.shape[1] != weights.shape[0]:
        raise ValueError(
            "Le nombre de critères dans les données doit correspondre au"
            " nombre de poids."
        )
    if data.shape[1] != criteria_min_max.shape[0]:
        raise ValueError(
            "Le nombre de critères dans les données doit correspondre au"
            " nombre de critères dans criteria_min_max."
        )
    if any(data.columns != weights.index):
        raise ValueError("Columns of data must match the index of weights.")
    # Promethee parameters
    criteria_min_max = criteria_min_max.values.flatten().tolist()
    pref_type = (
        int(pref_type.values[0][0])
        if isinstance(pref_type, pd.DataFrame)
        else int(pref_type)
    )
    q_param = (
        int(q_param.values[0][0])
        if isinstance(q_param, pd.DataFrame)
        else int(q_param)
    )
    p_param = (
        int(p_param.values[0][0])
        if isinstance(p_param, pd.DataFrame)
        else int(p_param)
    )

    # PCA
    pca = PCA()
    principal_components = pca.fit_transform(data)
    contrib_ind_df = pd.DataFrame(
        np.square(principal_components), index=data.index
    )
    loadings = pca.components_.T
    contrib_var_df = pd.DataFrame(np.square(loadings), index=data.columns)
    contrib_var_df = contrib_var_df.div(contrib_var_df.sum(axis=0), axis=1)

    # Variables weighing
    weighted_contrib = (contrib_var_df * weights.values).sum(axis=0)
    weighted_contrib = pd.DataFrame(
        np.tile(weighted_contrib.values, (contrib_ind_df.shape[0], 1)),
        index=contrib_ind_df.index,
        columns=contrib_ind_df.columns,
    )

    # Promethee call
    promethee_result_df = promethee(
        contrib_ind_df,
        weighted_contrib,
        criteria_min_max,
        pref_type,
        q_param,
        p_param,
    )
    promethee_result_df.index = data.index
    promethee_result_df.name = "PrometheeScore"

    # Final result
    res = pd.concat([promethee_result_df, data.reset_index(drop=True)], axis=1)
    return res
