# Here is the place to but general helper functions

import ast
import json
from pathlib import Path
import toml
import pandas as pd

from ._backend_calls import _backend_POST, _backend_PUT, _backend_GET


def load_file(filename: Path) -> dict | pd.DataFrame:
    """
    Load data from a file. The function currently supports JSON files.

    Parameters:
    filename (Path): The path to the file.

    Returns:
    dict: The data loaded from the file if it's a JSON.
    """
    if filename.suffix == '.csv':
        df = pd.read_csv(filename, sep=',', header=0,
                         index_col=None, na_values=['NA', '?'])
        return df

    elif filename.suffix == '.json':
        with open(filename, 'r') as file:
            return json.load(file)

    elif filename.suffix == '.toml':
        with open(filename, 'r') as file:
            return toml.load(file)

    else:
        raise ValueError(
            "You need to add the file format to the load_file function.")



def create_new_project(project_info: dict):
    """Create a new project in the backend.
    Params:
        project_info(dict):
    Returns:
        dict:
    """
    status_code, message = _backend_POST(
        endpoint=f"/v2/projects/", data=project_info)
    if status_code == 200:
        return message
    else:
        print(f"Error creating project: {message}")
        return {"error": message}


def get_list_of_all_project_infos() -> list[tuple[int, str, str]]:
    """A function to connect to the backend and get a list of all projects
    in the current project data base.

    Params:
        None

    Retruns:
        all_project (list): List of tuples of id and name for all projects in the projectDB
        project_id (int): The project Identification Number
        project_name (str): The name of the project/mission
    """

    status_code, projects = _backend_GET(endpoint=f"/v2/projects/")
    project_ids: list[tuple[int, str, str]] = []
    if status_code == 200:
        for project in projects:
            project_ids.append((project["id"], project["project_name"], project["modified"]))
        return project_ids
    else:
        print(f"Error fetching projects: {projects}")
        return []

def get_project_info(project_id: int) -> dict:
    status_code, project_info = _backend_GET(endpoint=f"/v2/projects/{project_id}/")
    return project_info


def set_project_name(project_id: int, project_name: str) -> dict:
    """A function to set the project name of a specific project, identified by its ID.

    Params:
        project_id (int): The project Identification Number
        project_name(str): Name of the project/mission, eg OPS-Sat
    Returns:
        dict: html response from the backend to indicate success (200) or problems
    """

    data = {
        "project_id": project_id,
        "project_name": project_name,
    }

    # status, msg = _backend_put(endpoint=f"/v2/projects/{project_id}", data=data)
    # return (status, msg)
    status_code, msg = _backend_PUT(endpoint=f"/v2/projects/{project_id}/", data=data)
    if status_code == 200:
        return msg
    else:
        print(f"Error setting project name: {msg}")
        return {"error": msg}


def get_recent_project() -> tuple[int, str, str]:
    status_code, msg = _backend_GET(endpoint=f"/v2/projects/recent/")
    if status_code != 200:
        print(f"Error fetching recent project: {msg}")
        return (None, "No recent project found.")
    return (msg["id"], msg["project_name"], msg["modified"])


def get_mission_info(project_id: int) -> dict:
    """A function to get the mission/orbit info for a specific project based on its ID.
    Params:
        project_id (int): The project Identification Number

    Returns:
        dict: Keplerian elements to define the orbit
    """

    response = _backend_GET(
        endpoint=f"/v2/projects/{project_id}/mission/")
    return response["orbit"]


def set_mission_orbit(project_id: int, orbit_info: dict) -> dict:
    """A function to set the orbit in the project database.
    Params:
        project_id (int): The project Identification Number
        orbit_info(dict): Information about the satelllite's orbit
    Returns:
        dict: html response from the backend to indicate success (200) or problems
     """

    data = {
        "mission_name": "string",  # TODO remove?
        "description": "string",  # TODO remove?
        "launch_date": "string",
        "purpose": "string",  # TODO remove?
        "website": "string",  # TODO remove?
        "picture_web": "string",  # TODO remove?
        "project_id": 0,
        "orbit": orbit_info,
        # "orbit": {
        #     "celestial_object": "string",
        #     "orbit_shell": "string",
        #     "altitude_km": 0,
        #     "epoch_utc": "string",
        #     "semi_major_axis_km": 0,
        #     "eccentricity": 0,
        #     "inclination_degrees": 0,
        #     "perigee_height_km": 0,
        #     "apogee_height_km": 0,
        #     "raan_degrees": 0,
        #     "argument_of_perigee_degrees": 0,
        #     "mean_anomaly_at_epoch_degrees": 0,
        #     "revolutions_per_day": 0,
        #     "orbit_number_at_epoch": 0,
        #     "time_eclipse_min": 0,
        #     "mission_id": 0  # TODO remove?
        # }
    }

    # {
    #     "Celestical_Object": "Earth",  # Earth, Luna, Sun, Mars
    #     "Orbit_shell" : "LEO",  # LEO, MEO, GEO
    #     "Altitude_km" : 515,
    #     "Epoch_UTC" : datetime.strptime("2024-05-27T07:32:00", "%Y-%m-%dT%H:%M:%S"),
    #     "Semi_major_Axis_km" : 6_786,
    #     "Eccentricity" : 0.0005551,
    #     "Inclination_degrees" : 97.4540,
    #     "Perigee_Height_km" : 385,
    #     "Apogee_Height_km" : 392,
    #     "RAAN_degrees" : 267.4498,
    #     "Argument_of_Perigee_degrees" : 256.8657,
    #     "Mean_Anomaly_at_Epoch_degrees" : 103.1980,
    #     "Revolutions_per_Day" : 15.59560910,
    #     "Orbit_Number_at_Epoch" : 23_446,
    #     "Time_Eclipse_min" : 38.512,  # calculated by AI4CE software
    # }

    status, msg = _backend_POST(endpoint=f"/v2/projects/{project_id}/mission/", data=data)
    if status == 201:
        return msg
    else:
        print(f"Error setting mission/orbit info: {msg}")
        return {"error": msg}


def get_enabled_components(nested_dict, enabled_components=None) -> list:
    """
    Recursively traverses a nested dictionary to find and return a list of components that are enabled.

    Parameters:
    nested_dict (dict): The nested dictionary to traverse.
    enabled_components (list, optional): A list to store the names of the enabled components.
                                         Defaults to None, in which case a new list is created.

    Returns:
    list: A list of the names of the enabled components.
    """

    if enabled_components is None:
        enabled_components = []

    for key, value in nested_dict.items():
        if isinstance(value, dict):
            if value.get('Enabled') == True:
                enabled_components.append(key)
                pass
            get_enabled_components(value, enabled_components)

    return enabled_components


def traverse_and_modify(d: dict, sys_config_enabled: dict):
    for key, value in d.items():
        if isinstance(value, dict):
            component = key
            traverse_and_modify(d=value, sys_config_enabled=sys_config_enabled)
        else:
            # This block executes only if `value` is not a dict, i.e., at the deepest level
            # if d["Enabled"] is True or d["Enable"] is True or :
            try:
                if d["Enabled"] is True:
                    sys_config_enabled.update(enable_component(
                        component_name=key, data=load_file(Path("ai4ce/backend_sys_default.json"))))
            except KeyError as e:
                print("Error: ", e)


def enable_component(component_name: str, data: dict) -> dict:
    """Recursively search for a component in the nested dictionary from the backend
    and set 'enabled' to True if the feature name matches any key or value (case-insensitive).

    Parameters:
        data (dict): The nested dictionary to search within.
        feature_name (str): The feature name to search for, case-insensitively.

    Returns:
        dict: edited dict
    """

    for key, value in data.items():
        #  print(key, value)
        if isinstance(value, dict):
            enable_component(component_name, value)
        elif isinstance(value, list):
            for item in value:
                if isinstance(item, dict):
                    enable_component(component_name, item)
        if key.lower() == component_name.lower() or str(value).lower() == component_name.lower():
            data['enabled'] = True
    return data


def set_sys_arch(project_id: int, sys_arch: dict) -> dict:
    """A function to set the system architecture in the project database.
    Params:
        project_id (int): The project Identification Number
        sys_arch(dict): Information about the satelllite's modules, which form the system architecture
    Returns:
        dict: html response from the backend to indicate success (200) or problems
     """

    sys_arch_example = {
        "system": {
            "eps": {
                "enabled": True,
                "battery": {
                    "enabled": True,
                }
            },
            "payload": {
                "camera": {
                    "enabled": True,
                }
            }
        }
    }

    response: dict = _backend_POST(
        endpoint=f"/v2/projects/{project_id}/system/", data=sys_arch)
    return response


def get_sys_arch(project_id: int) -> dict:
    """A function to get the system configuration info for a specific project based on its ID.
    Params:
        project_id (int): The project Identification Number

    Returns:
        dict: System information
    """

    response = _backend_GET(
        endpoint=f"/v2/projects/{project_id}/system/")
    return response

#  set_comp_list(project: int, not_yet_defined: dict)
# For each selected system generator, we need a place to store the corresponding found comp lists
# Every generator can produce n sets of components


def set_sys_generator(project_id: int, sys_gen_info: dict) -> dict:
    response = _backend_POST(
        endpoint=f"/v2/projects/{project_id}/sysgen/", data=sys_gen_info)
    return response


def update_sys_generator(project_id: int, sys_gen_info: dict) -> dict:
    response = _backend_PUT(
        endpoint=f"/v2/projects/{project_id}/sysgen/", data=sys_gen_info)
    return response


def set_trained_model(project_id: int, model_info: dict) -> dict:
    """A function to upload a trained AI model.
    Params:
        project_id (int): The project Identification Number
        model (ASKYOUNES): info about model

    Returns:
        dict: Upload server response
    """

    sta, msg = _backend_POST(endpoint=f"/v2/projects/{project_id}/sysgen/",
                             data=model_info)

    return msg


def get_prepared_system_generator_info(project_id: int) -> list:
    """A function to get all prepared system generators.
    Params:
        project_id (int): The project Identification Number

    Returns:
        list: list of dictionaries with infos for the prepared system generators
    """

    response = _backend_GET(
        endpoint=f"/v2/projects/{project_id}/sysgen/")
    return response


# def get_trained_model(project_id: int) -> mode: ASKYOUNES
#     pass


# def set_train_log(project_id: int, logs: ASKYOUNES) -> DB_response: ASKALEX
#     pass


# def get_train_logs(project_id: int) -> logs: ASKYOUNE   pass


def get_tags_from_string(str_of_tags: str) -> list[str]:
    """
    example_tags_str = "['satellite', 'power', 'solar-panels']"
    """
    # The string representation of a list of tags

    # Convert the string to an actual list using ast.literal_eval
    tags: list = ast.literal_eval(str_of_tags)
    return tags


def translate_tomlsystem_to_backend(system: dict, project_id: int) -> dict:
    """Translate a system configuration from a TOML file to a format that can be uploaded to the backend.

    Parameters:
    system (dict): The system configuration in TOML format.

    Returns:
    dict: The system configuration in a format that can be uploaded to the backend.
    """

    sys_config_enabled = {}
    # Loading System Configuration
    for key, value in system.items():
        if isinstance(value, dict):
            traverse_and_modify(d=value, sys_config_enabled=sys_config_enabled)

    resp = set_sys_arch(project_id=project_id, sys_arch=sys_config_enabled)
    pass
    return resp


def comp_create(comp_info: dict) -> dict:
    """Create a new component in the backend.
    Params:
        comp_info(dict): Information about the component
    Returns:
        dict:
    """
    status_code, msg = _backend_POST(
        endpoint=f"/v2/components/", data=comp_info)
    return status_code, msg

def get_all_decisions() -> dict:
    """Fetches all decisions from the DecisionDB and returns them as a dictionary.

    Returns:
        dict: A dictionary containing all decisions fetched from the DecisionDB.
    """
    response = _backend_GET(endpoint="/v2/components/export/")
    return response

def get_comp_statistics() -> dict:

    response =_backend_GET(endpoint="/v2/streamlit/db/stats/components")
    return response

