"""Validate system module

Used by the command validate-system
"""

import inspect
import importlib
import logging
import os
import sys
import glob
import filecmp

root_path = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), '../../../../')


def handle_error(msg, strict):
    """Error handler
        msg (str): An error message to display as a warning or as an Exception
        strict (bool): Whether to throw an exception with the provided message
    """
    if strict:
        raise Exception('System validation failed: {}'.format(msg))
    logging.warning('{}'.format(msg))


def validate_system(strict):
    """Main validation method
        strict (bool): Whether to cause a failure in case of an error or not

    """
    validate_structure(strict)
    validate_cli(strict)
    validate_container_output(strict)
    if strict:
        logging.info("System is valid")
    else:
        logging.warning(
            "System validation not performed yet. Fix any WARNING and validate"
            " again using the --strict flag")


# ############################## structure ####################################


def validate_structure(strict):
    """Validate structure of the CLI: should contain required directories.
        strict (bool): Whether to cause a failure in case of an error or not

    """
    directories = os.listdir(root_path)
    content = import_expected_result('expected_structure.txt')
    logging.info("Structure validation")

    for directory in content:
        if directory not in directories:
            handle_error("missing {} directory".format(directory), strict)
        if not os.path.isdir(root_path + '/' + directory):
            handle_error("{} not a directory".format(directory), strict)
        logging.info("    .. {} is valid".format(directory))


# ################################# cli #######################################

def validate_cli(strict):
    """Import the entry points method and try to compare them with the
    expected result

        strict (bool): Whether to cause a failure in case of an error or not
    """

    from diva_evaluation_cli.bin.cli import public_subcommands

    # Open expected result
    content = import_expected_result('expected_validation_result.txt')
    i = 0
    logging.info("CLI validation")

    for subcommand in public_subcommands:
        actev_command = "actev_" + subcommand.command.replace('-', '_')

        # Is entry point exist
        try:
            entry_point_module = importlib.import_module(
                'diva_evaluation_cli.src.entry_points.{}'.format(
                    actev_command))
        except ModuleNotFoundError:
            handle_error("{} entry_point method removed".format(
                actev_command), strict)

        entry_point_function = getattr(entry_point_module, 'entry_point')
        result = "{} - {}".format(
            actev_command, inspect.signature(entry_point_function))

        # Is entry point contain correct arguments
        if content[i] != result:
            handle_error(("{} entry_point method changed: {} != {}".format(
                actev_command, content[i], result)), strict)
        i += 1
        logging.info("    .. {} is valid".format(actev_command))


def import_expected_result(file_name):
    """ Import expected validation result

    Args:
        file_name (str): Path to file to open in order to extract lines inside
            a list
    """

    content = []
    path = os.path.dirname(__file__)
    expected_result = os.path.join(path, file_name)
    with open(expected_result, 'r') as f:
        content = f.readlines()
    content = [line.strip() for line in content]
    return content

# ############################ container output ###############################


def dataset_name_from_dirname(dirname, dataset_names):
    """Get the dataset name from the dirname
    """
    for dataset_name in dataset_names:
        if dirname.startswith(dataset_name):
            return dataset_name
    return None


def get_dataset_name_matching_dirname(dirname, dataset_names, strict):
    """Get the dataset name and check its format, and whether it exists
    """
    if not dirname:
        handle_error('system output format should be in the following format:'
                     '<dataset-name>_*', strict)

    matching_dataset_names = [dataset_name for dataset_name in dataset_names
                              if dirname.startswith(dataset_name + '_')]

    if not matching_dataset_names:
        return False

    # Return the longest dataset name matched,
    # in case some names are composed of others
    matching_dataset_names.sort(key=len, reverse=True)

    return matching_dataset_names[0]


def validate_container_output(strict):
    """ Check that container output directory is present.
    For each subdirectory, an *output.json file should be present.
        strict (bool): Whether to cause a failure in case of an error or not
    """

    container_output_dir = os.path.normpath(os.path.join(
        root_path, 'container_output'))
    logging.info("Container output validation")

    # Gather all system outputs paths
    system_outputs_paths = [dirname for dirname in
                            os.listdir(container_output_dir) if os.path.isdir(
                                os.path.join(container_output_dir, dirname))]

    # Get the datasets names
    baseline_results_path = os.path.join(
        os.path.dirname(__file__), 'baseline_outputs')
    dataset_names = [path for path in os.listdir(baseline_results_path)
                     if os.path.isdir(os.path.join(
                         baseline_results_path, path)) and path]

    # Check whether the container-outputs dir is empty
    if not system_outputs_paths:
        handle_error('no system output in {}'.format(
            container_output_dir), strict)

    for system_output_dir_path in system_outputs_paths:

        system_output_name = os.path.basename(system_output_dir_path)
        logging.info('Checking system output {}'.format(
            system_output_dir_path))

        # 1st check: check that the system output name matches one of the
        # partitions
        dataset_name = get_dataset_name_matching_dirname(
            system_output_name, dataset_names, strict)
        if dataset_name:

            # 2nd check: check that all expected files are present
            expected_files = import_expected_result(
                'expected_container_output.txt')
            for expected_file in expected_files:
                dataset_filename = dataset_name + expected_file
                dataset_filepath = os.path.join(
                    container_output_dir, system_output_name, dataset_filename)
                if not os.path.exists(dataset_filepath):
                    handle_error(
                        "Cannot find file {}".format(dataset_filepath), strict)

            # 3rd check: check whether the output is identical to the baseline
            # output
            dataset_name = dataset_name_from_dirname(
                system_output_name, dataset_names)
            baseline_result_path = os.path.join(
                baseline_results_path, dataset_name,
                dataset_name + '_output.json')
            system_output_path_glob = os.path.join(
                container_output_dir, system_output_name, '*output.json')
            system_output_path = glob.glob(system_output_path_glob)
            if system_output_path:
                if filecmp.cmp(baseline_result_path, system_output_path[0]):
                    handle_error('container output file {} is identical to '
                                 'the baseline output on the same '
                                 'dataset.'.format(
                                     system_output_name, dataset_names),
                                 strict)
        else:
            handle_error('bad directory name "{}", it has to be <dataset_name>'
                         '_*  with dataset_name one of {}'.format(
                             system_output_name, dataset_names), strict)
