import os
import glob
import json
import tqdm
import pkgutil

import astrohack
import functools

import numpy as np
import skriba.messenger
import skriba.logger

from inspect import getfullargspec
from auror.protego import Protego


def validate(config_dir=None, custom_checker=None, add_data_type=None, logger=None):
    def function_wrapper(function):
        arguments = {}

        @functools.wraps(function)
        def wrapper(*args, **kwargs):
            arg_list = getfullargspec(function).args
            print("arg-list: ", arg_list)
            print("args: ", args)

            if kwargs:
                for key, value in kwargs.items():
                    arguments[key] = value
                    index = arg_list.index(key)
                    arg_list.pop(index)

            for arg in arg_list:
                print("arg: ", arg)
                print("arg-list.index: ", arg_list.index(arg))
                index = arg_list.index(arg)
                arguments[arg] = args[index]

            assert verify(
                function=function,
                args=arguments,
                config_dir=config_dir,
                custom_checker=custom_checker,
                add_data_type=add_data_type,
                logger=None
            )

            return function(*args, **kwargs)

        return wrapper

    return function_wrapper


def config_search(root="/"):
    logger = skriba.logger.get_logger()

    paths = []
    if root == "/":
        logger.info("[{warning}] File search from root could take some time ...".format(
            warning=skriba.messenger.SystemMessage.yellow("Warning!")))

    logger.info("Searching fileSystemMessage for configuration files, please wait ...")
    for file in glob.iglob("{root}/**".format(root=root), recursive=True):
        logger.info(file)
        if "param.json" in file:
            basename = os.path.dirname(file)
            if basename not in paths:
                paths.append(basename)

    logger.info(paths)

    return paths


def set_config_directory(path, create=False):
    logger = skriba.logger.get_logger()

    if os.path.exists(path):
        logger.info("Setting configuration directory to [{path}]".format(path=path))
        os.environ["CERBERUS_CONFIG_PATH"] = path
    else:
        logger.info("The configuration directory [{path}] does not currently exist.".format(path=path))
        if create:
            logger.info("Creating empty configuration directory: {path}".format(path=path))
            os.mkdir(path)


def verify_configuration(path, module):
    logger = skriba.logger.get_logger()

    modules = []
    for file in tqdm.tqdm(glob.glob("{path}/*.param.json".format(path=path), recursive=True)):
        if file.endswith(".param.json"):
            modules.append(os.path.basename(file).strip(".param.json"))

    pkgpath = os.path.dirname(module.__file__)
    package_modules = [name for _, name, _ in pkgutil.iter_modules([pkgpath])]

    not_found = []
    for module in package_modules:
        if module not in modules:
            not_found.append(module)

    coverage = (len(package_modules) - len(not_found)) / len(package_modules)

    logger.info("The following functions were not found: {not_found}".format(not_found=not_found))
    logger.info("The parameter checking coverage is: {coverage}%".format(coverage=100 * coverage))

    return package_modules


def verify(function, args, config_dir=None, add_data_type=None, custom_checker=None, logger=None):
    function_name = function.__name__
    module_name = function.__module__

    if logger is None:
        logger = skriba.logger.setup_logger(
            logger_name="{module}::{function}::logger".format(module=module_name, function=function_name),
            log_to_term=True
        )

    # The module call gives the full module chain, but we only want the last
    # module, ex. astrohack.extract_holog would be returned, but we only want
    # extract_holog. This should generally work.

    module_name = module_name.split(".")[-1]

    logger.debug(
        "{function}: Checking parameter values for {module}".format(function=function_name, module=module_name))

    # First we need to find the parameter configuration files
    if config_dir is not None:
        path = config_dir

    # If the parameter configuration directory is not passed as an argument this environment variable should be set.
    # In this case, the environment variable is set in the __init__ file of the astrohack module.
    elif os.getenv("AUROR_CONFIG_PATH") is not None:
        path = os.getenv("AUROR_CONFIG_PATH")
        logger.debug("AUROR_CONFIG_PATH: {dir}".format(dir=os.getenv("AUROR_CONFIG_PATH")))

    else:
        logger.error("{function}: Cannot find parameter configuration directory.".format(function=function_name))
        return False

    # Define parameter file name
    parameter_file = module_name + ".param.json"

    logger.debug(path + "/" + parameter_file)

    # This will check the configuration path and return the available modules
    module_config_list = verify_configuration(path, astrohack)

    # Make sure that required module is present
    if module_name not in module_config_list:
        logger.error("{function}: Parameter file not found in {path}".format(function=function_name,
                                                                             path=path + "/" + parameter_file))
        raise FileNotFoundError

    with open(path + "/" + parameter_file) as json_file:
        schema = json.load(json_file)

    if function_name in schema.keys():
        logger.info("Function [{}] found in parameter configuration files.".format(function_name))

    else:
        raise Exception("Function [{}] not in parameter configuration files.".format(function_name))

    # Here is where the parameter checking is done
    # First instantiate the validator.
    validator = Protego()

    if add_data_type is not None:
        validator.register_data_type(add_data_type)

    # Set up the schema to validate against.
    validator.schema = schema

    # If a custom unit custom checker is needed, instantiate the
    # void function in the validator class. In the schema this
    # is used with "check allowed with".
    if custom_checker is not None:
        validator.custom_allowed_function = custom_checker

    assert validator.validate(args), logger.error(validator.errors)
