import os
import logging
import json
import yaml
from typing import List
from joblib import load, dump


def check_and_create_folder(path: str) -> None:
    """
    Checks if the directory for the given file path exists, and creates it if it does not.

    This function ensures that the directory structure for a specified file path is available.
    If the directory does not exist, it will create all the necessary subdirectories.
    Logs information when a directory is successfully created.

    Arguments:
        path (str): The file path whose directory needs to be checked and
                    potentially created.

    Returns:
        None
    """
    directory = os.path.dirname(path)
    if directory and not os.path.exists(directory):
        os.makedirs(directory)
        logging.info(f"{directory} created")


def get_folder_name(filepath) -> str:
    """
    Returns the name of the folder from the given file path.

    This function extracts and returns the directory portion of the provided
    file path.

    Args:
        filepath (str): The file path from which to extract the folder name.

    Returns:
        str: The name of the folder containing the file.
    """
    return os.path.dirname(filepath)


def check_file_exists(filepath) -> None:
    """
    Checks if a file exists at the specified file path.

    This function verifies the existence of a file at the given file path. If the
    file does not exist, it raises a FileNotFoundError. It does not return any
    value and is used solely for validation purposes.

    Args:
        filepath (str): The path to the file to check for existence.

    Raises:
        FileNotFoundError: If the file does not exist at the specified file path.
    """
    if not os.path.isfile(filepath):
        raise FileNotFoundError(f"No such file: '{filepath}'")


def get_file_extension(path: str) -> str:
    """
    Retrieve the file extension from the given file path.

    This function extracts and returns the file extension from
    a given file path string. It uses the `os.path.splitext`
    method to split the path and obtain the extension.

    Parameters:
        path (str): The file path from which to extract the extension.

    Returns:
        str: The file extension, including the leading period.
    """
    return os.path.splitext(path)[1]


def del_file_extension(file_path: str) -> str:
    """
    Removes the extension from a file path.

    The function accepts a file path as input and removes its extension, returning
    the file path without the extension.

    Args:
        file_path (str): The path of the file whose extension is to be removed.

    Returns:
        str: The file path without its extension.
    """
    path, _ = os.path.splitext(file_path)
    return path


def load_file(file_path: str) -> any:
    """
    Load a file based on its extension and return its content. Supports `.joblib`, `.json`,
    and `.yaml/.yml` file types. Ensures relative file paths are resolved against the current
    working directory if no directory is specified in the given path.

    Args:
        file_path: Path to the input file.

    Returns:
        The content of the loaded file. The type of the returned content depends on
        the file type and its structure.

    Raises:
        ValueError: Raised if the file type is not supported.
    """
    # Ensure the file path is treated as relative to the current directory if no directory is specified
    if not os.path.isabs(file_path) and not os.path.dirname(file_path):
        file_path = os.path.join(os.getcwd(), file_path)

    file_type = get_file_extension(file_path)

    if file_type == ".joblib":
        return load(filename=file_path)
    elif file_type == ".json":
        with open(file_path, 'r') as file:
            return json.load(file)
    elif file_type == ".yaml" or file_type == ".yml":
        with open(file_path, 'r') as file:
            return yaml.safe_load(file)
    else:
        raise ValueError("datatype not supported yet")


def save_file(data: any, path: str, autocreate_folder: bool = True) -> None:
    """
    Saves data to a file at the specified path. Supports saving in JSON, YAML/YML, and
    Joblib formats. If enabled, automatically creates the necessary folder structure
    for the file if it does not already exist. Logs an informational message upon
    successful save.

    Parameters:
    data : any
        The data to be saved to the file.
    path : str
        The file path including the file name and extension where the data should
        be saved.
    autocreate_folder : bool
        Optional. If True, automatically creates the folder structure required by
        the specified path if it does not exist. Defaults to True.

    Raises:
    ValueError
        If the file extension is not supported.

    Returns:
        None
    """
    if autocreate_folder:
        check_and_create_folder(path)

    filetype = get_file_extension(path)

    if filetype == ".joblib":
        dump(data, path)
    elif filetype == ".json":
        with open(path, 'w') as outfile:
            json.dump(data, outfile, indent=4)
    elif filetype == ".yaml" or type == ".yml":
        with open(path, 'w') as outfile:
            yaml.dump(data, outfile)
    else:
        raise ValueError("datatype not supported yet")

    logging.info("File saved to %s", path)


def get_folder_files(directory: str, recursive: bool = True) -> List[str]:
    """
    Retrieves a list of file paths from a specified directory. The function can operate
    recursively to include files from subdirectories or non-recursively to include files
    from the top-level directory only.

    Parameters:
        directory (str): The directory path from which to retrieve the file paths.
        recursive (bool): Optional; If True, retrieves files from the directory and all
            subdirectories. Defaults to True.

    Returns:
        List[str]: A list containing file paths as strings.
    """
    files_list = []
    if recursive:
        for root, _, files in os.walk(directory):
            for file in files:
                files_list.append(os.path.join(root, file))
    else:
        for file in os.listdir(directory):
            full_path = os.path.join(directory, file)
            if os.path.isfile(full_path):
                files_list.append(full_path)
    return files_list
