from typing import Any

import pandas as pd


class Preprocessing:
    """
    A class for preprocessing datasets loaded from a parquet file.

    This class provides methods to preprocess datasets according to a given configuration. It supports operations such as
    filtering data by order, section, popular sections, users with more comments, and excluding data based on specific values.

    Attributes
    ----------
    data : pd.DataFrame
        The dataset loaded from the specified parquet file.

    Methods
    -------
    preprocess_datasets(preprocessing_config):
        Applies a series of preprocessing steps to the dataset based on the provided configuration and returns the modified datasets.
    _apply_chain(data, chain):
        Applies a chain of preprocessing steps to the given data.
    full_data():
        Returns the full dataset without any preprocessing.
    data_order(data, order):
        Filters the dataset to include only the data with the specified order.
    data_section(data, section):
        Filters the dataset to include only the data from the specified section.
    popular_sections(data, threshold):
        Filters the dataset to include only the data from sections with more than a specified number of entries.
    users_with_more_comments(data, num_comments):
        Filters the dataset to include only the data from users with more than a specified number of comments.
    exclude_data_with_value(data, param):
        Excludes data from the dataset based on a specified column value.
    """

    def __init__(self, path_to_file: str, name_data: str):
        """
        Initializes the Preprocessing class with data loaded from a specified parquet file.

        Parameters
        ----------
        path_to_file : str
            The path to the directory containing the parquet file.
        name_data : str
            The name of the parquet file from which to load the data.
        """
        self.data = pd.read_parquet(path_to_file + name_data)

    def preprocess_datasets(self, preprocessing_config) -> dict[str, pd.DataFrame]:
        """
        Applies a series of preprocessing steps to the dataset based on the provided configuration.

        Parameters
        ----------
        preprocessing_config : dict
            A dictionary where keys are dataset names and values are lists of preprocessing steps (as dictionaries) to be applied.

        Returns
        -------
        dict[str, pd.DataFrame]
            A dictionary of preprocessed datasets.
        """
        datasets = {}
        for dataset_name, config in preprocessing_config.items():
            datasets[dataset_name]: pd.DataFrame = self._apply_chain(self.data.copy(), config)
        datasets["data"] = self.full_data()
        return datasets

    def _apply_chain(self, data: pd.DataFrame, chain: list) -> pd.DataFrame:
        """
        Applies a chain of preprocessing steps to the given data.

        Parameters
        ----------
        data : pd.DataFrame
            The dataset to preprocess.
        chain : list
            A list of dictionaries, each representing a preprocessing step with a method name and optional parameters.

        Returns
        -------
        pd.DataFrame
            The preprocessed dataset.
        """
        for step in chain:
            method_name = step["method"]
            param = step.get("param")
            method = getattr(self, method_name)
            data = method(data, param)
        return data

    def full_data(self):
        return self.data

    @staticmethod
    def data_order(data: pd.DataFrame, order: int) -> pd.DataFrame:
        return data.loc[data["order"] == order]

    @staticmethod
    def data_section(data: pd.DataFrame, section: str) -> pd.DataFrame:
        return data.loc[data["section"] == section]

    @staticmethod
    def popular_sections(data: pd.DataFrame, threshold: int = 1000) -> pd.DataFrame:
        counts = data["section"].value_counts()
        popular_sections = counts[counts > threshold].index.tolist()
        return data[data["section"].isin(popular_sections)]

    @staticmethod
    def users_with_more_comments(data: pd.DataFrame, num_comments: int = 10) -> pd.DataFrame:
        counts_user = data["user_id"].value_counts()
        users_with_10_comments = counts_user[counts_user > num_comments].index.tolist()
        return data[data["user_id"].isin(users_with_10_comments)]

    @staticmethod
    def exclude_data_with_value(data: pd.DataFrame, param: dict) -> pd.DataFrame:
        column: str = param['column']
        value: Any = param['value']
        return data[data[column] != value]
