import numpy as np
import dataiku
import dataikuapi
import requests
import datetime
import re
import pickle
from typing import Dict

from commons.python.business_solutions_api.dataiku_api import dataiku_api


def parse_date(yyyy_mm_dd_date):
    date_components = yyyy_mm_dd_date.split("-")
    year = int(date_components[0])
    month = int(date_components[1])
    day = int(date_components[2])
    return datetime.date(year, month, day)

def get_user(headers):
    try:
        auth_info = dataiku.api_client().get_auth_info_from_browser_headers(headers)
        user = auth_info["authIdentifier"]
    except (dataikuapi.utils.DataikuException, requests.exceptions.HTTPError) as e:
        print(f"Exception occurred: {str(e)}")
        user = "user_not_found"
    return user

def get_managed_folder_metadata(project, managed_folder_name):
    """
    Retrieves the information associated with a project folder.

    :param project: dataikuapi.dss.project.DSSProject: A handle to interact with a project on the DSS instance.
    :param managed_folder_name: str: Name of the project managed folder.

    :returns: managed_folder_metadata: dict: The associated with 'flow_zone_name'.
    """
    all_managed_folders_information = project.list_managed_folders()
    managed_folder_metadata = None
    for managed_folder_information in all_managed_folders_information:
        if managed_folder_information["name"] == managed_folder_name:
            managed_folder_metadata = managed_folder_information
            pass
        pass
    if managed_folder_metadata == None:
        log_message = (
            "Folder '{}' does not exist! "
            "Please use the computed 'all_managed_folders_information' to use a valid folder name."
            "all_managed_folders_information = '{}'".format(
                managed_folder_name, all_managed_folders_information
            )
        )
        raise Exception(log_message)
    return managed_folder_metadata


def get_managed_folder_id(project, managed_folder_name):
    """
    Retrieves the ID of a project managed folder.

    :param project: dataikuapi.dss.project.DSSProject: A handle to interact with a project on the DSS instance.
    :param managed_folder_name: str: Name of the project managed folder.

    :returns: managed_folder_id: str: The ID associated with 'managed_folder_name'.
    """
    managed_folder_metadata = get_managed_folder_metadata(project, managed_folder_name)
    managed_folder_id = managed_folder_metadata["id"]
    return managed_folder_id


def remove_pickle_extension(pickle_name):
    """
    Removes the extension contained in a pickle name.

    :param pickle_name: str: String used for naming the pickle file.

    :returns: pickle_name_without_extension: str: The pickle name without extension.
    """
    pickle_name_without_extension = re.sub("\.p$", "", pickle_name)
    return pickle_name_without_extension


def read_pickle_from_managed_folder(project, managed_folder_name, pickle_name):
    """
    Reads a pickle file from a project managed folder.

    :param project: dataikuapi.dss.project.DSSProject: A handle to interact with a project on the DSS instance.
    :param managed_folder_name: str: Name of the project managed folder.
    :param pickle_name: str: String used for naming the pickle file.

    :returns: pickle_data: Any: Any python object serializabled with pickle.
    """
    managed_folder_id = get_managed_folder_id(project, managed_folder_name)
    managed_folder = dataiku.Folder(managed_folder_id, project_key=project.project_key)
    pickle_name_raw = remove_pickle_extension(pickle_name)
    pickle_name = "{}.p".format(pickle_name_raw)
    print("Reading '{}' ...".format(pickle_name))
    with managed_folder.get_download_stream(pickle_name) as f:
        data = f.read()
        pickle_data = pickle.loads(data)
    f.close()
    print("'{}' successfully read!".format(pickle_name))
    return pickle_data


def get_current_project_and_variables(project_key=None):
    """
    Retrieves current dataiku DSS project and its variables.

    :returns: project: dataikuapi.dss.project.DSSProject: A handle to interact with a project on the DSS instance.
    :returns: variables: dict: Variables of the project
    """
    if project_key is None:
       project_key = dataiku_api.get_project().project_key
    project = dataiku.api_client().get_project(project_key)
    variables = project.get_variables()
    return project, variables


def init_webapp():
    _, current_variables = get_current_project_and_variables()
    project, variables = get_current_project_and_variables(
        current_variables["standard"]["demand_forecast_project_key_app"]
    )
    app_variables: Dict = variables["standard"]
    webapp_engine = app_variables["webapp_engine_app"]

    if len(variables["standard"]["products_hierarchy_app"]) == 0:
        log_message = """It seems that the Product/SKUs hierarchy has not been well defined.
        Please update this parameter in the application :
        -1: Set the columns of the hierarchy if you want to use one
        -2: Disable the 'Use a Product/SKUs hierarchy' if you don't need it.
        """
        raise Exception(log_message)
    return app_variables, webapp_engine, project


def compute_all_hierarchy_dependencies(
    hierarchy_dataframe, columns_hierarchy, forecast_focus_column
):
    hierarchy_dependencies = {}
    n_columns_in_hierarchy = len(columns_hierarchy)
    column_analyzed = []

    for loop_index, column in enumerate(columns_hierarchy):
        column_analyzed.append(column)
        column_is_first_in_hierarchy = loop_index == 0
        column_is_not_last_in_hierarchy = loop_index != n_columns_in_hierarchy - 1
        hierarchy_dependencies[column] = {}

        if column_is_first_in_hierarchy:
            column_has_parent = False
            column_first_parent = None
        else:
            column_has_parent = True
            column_first_parent = columns_hierarchy[loop_index - 1]

        if column_is_not_last_in_hierarchy:
            next_column_in_hierarchy = columns_hierarchy[loop_index + 1]
            hierarchy_dependencies[column]["has_child"] = True
            hierarchy_dependencies[column]["first_child"] = next_column_in_hierarchy
            hierarchy_dependencies[column]["all_children"] = [
                column for column in columns_hierarchy if not column in column_analyzed
            ]

        else:
            next_column_in_hierarchy = None
            hierarchy_dependencies[column]["has_child"] = False
            hierarchy_dependencies[column]["first_child"] = None
            hierarchy_dependencies[column]["all_children"] = []

        hierarchy_dependencies[column]["has_parent"] = column_has_parent
        hierarchy_dependencies[column]["first_parent"] = column_first_parent

        hierarchy_dependencies[column]["child_constraints"] = {}
        column_possible_values = list(np.unique(hierarchy_dataframe[column]))
        hierarchy_dependencies[column]["possible_values"] = column_possible_values

        for possible_value in column_possible_values:
            filtered_hierarchy_dataframe = hierarchy_dataframe[
                hierarchy_dataframe[column] == possible_value
            ].copy()
            if column_is_not_last_in_hierarchy:
                column_child_constraints = list(
                    np.unique(filtered_hierarchy_dataframe[next_column_in_hierarchy])
                )
            else:
                column_child_constraints = []
            hierarchy_dependencies[column]["child_constraints"][
                possible_value
            ] = column_child_constraints

            forecast_focus_associated_values = list(
                filtered_hierarchy_dataframe[forecast_focus_column]
            )
            hierarchy_dependencies[column][
                "forecast_focus_associated_values"
            ] = forecast_focus_associated_values
            pass
    return hierarchy_dependencies


def compute_frontend_parameters(product_list):
    project, variables = get_current_project_and_variables()

    project_key = project.project_key

    granularity_x_hierarchy_constraints_df = dataiku.Dataset(
        "granularity_x_hierarchy_constraints", project_key=project_key
    ).get_dataframe()

    project_demand , variables_demand = get_project_and_variables(
    variables["standard"]["demand_forecast_project_key_app"]
)
    
    all_granularities = list(np.sort(np.unique(granularity_x_hierarchy_constraints_df["forecast_granularity"])))

    # Hierarchy data:
    product_hierarchy = variables_demand["standard"]["timeseries_identifiers_columns"]
    product_hierarchy_not_well_defined = len(product_hierarchy) == 0

    if product_hierarchy_not_well_defined:
        log_message = """It seems that the Product/SKUs hierarchy has not been well defined.
        Please update this parameter in the application :
        -1: Set the columns of the hierarchy if you want to use one
        -2: Disable the 'Use a Product/SKUs hierarchy' if you don't need it.
        """
        raise Exception(log_message)

    granularity_x_hierarchy_constraints = read_pickle_from_managed_folder(
        project, "webapp_folder", "granularity_x_hierarchy_constraints.p"
    )

    hierarchy_dependencies = read_pickle_from_managed_folder(
        project, "webapp_folder", "hierarchy_dependencies.p"
    )

    time_granularity = variables_demand["standard"]["time_step"]
    leverage_inventory = False

    hierarchy_data = {
        "use_a_product_hierarchy": True,
        "product_hierarchy": product_hierarchy,
        "hierarchy_dependencies": hierarchy_dependencies,
    }
    try:
        hierarchy_length = len(hierarchy_data["product_hierarchy"])
    except:
        hierarchy_length = 0

    if hierarchy_length >2:
        last_hierachy = hierarchy_data["product_hierarchy"][
                            hierarchy_length - 2
                        ]
        local_constraints = {}
        for contraints in hierarchy_data["hierarchy_dependencies"][last_hierachy]["child_constraints"]:
            temp_list = [product for product in hierarchy_data["hierarchy_dependencies"][last_hierachy]["child_constraints"][contraints] if product in product_list]
            local_constraints[contraints]= temp_list
        hierarchy_data["hierarchy_dependencies"][last_hierachy]["child_constraints"] = local_constraints

    date_constraints = read_pickle_from_managed_folder(
        project, "webapp_folder", "date_constraints.p"
    )

    backend_response = {
        "status": "ok",
        "all_granularities": all_granularities,
        "granularity_x_hierarchy_constraints": granularity_x_hierarchy_constraints,
        "hierarchy_data": hierarchy_data,
        "time_granularity": time_granularity,
        "date_constraints": date_constraints,
        "leverage_inventory": leverage_inventory,
    }
    return backend_response

def get_current_project_and_variables():
    """
    Retrieves current dataiku DSS project and its variables.

    :returns: project: DSSProject: A handle to interact with a project on the DSS instance.
    :returns: variables: dict: Variables of the project
    """
    project = dataiku.api_client().get_project(dataiku.get_custom_variables()["projectKey"])
    variables = project.get_variables()
    return project, variables


def get_project_and_variables(project_key: str):
    """
    Retrieves any dataiku DSS project handler and its variables.

    :param project_key: str: The project key of interest.

    :returns: project: DSSProject: A handle to interact with a project on the DSS instance.
    :returns: variables: dict: Variables of the project
    """
    project = dataiku.api_client().get_project(project_key)
    variables = project.get_variables()
    return project, variables