#!/usr/bin/env python

import argparse
import configparser
import getpass
import logging
import os
import shutil
import sys
from datetime import datetime
from datetime import timedelta

import appdirs
import pandas as pd

from irida_sistr_results import irida_api
from irida_sistr_results import version
from irida_sistr_results.CommandParseException import CommandParseException
from irida_sistr_results.irida_api import IridaAPI
from irida_sistr_results.irida_connector import IridaConnector
from irida_sistr_results.irida_sistr_results import IridaSistrResults
from irida_sistr_results.irida_sistr_workflow import IridaSistrWorkflow
from irida_sistr_results.sistr_writer import SistrCsvWriter, SistrExcelWriter

REPORTABLE_SEROVARS_FILE = os.path.join(os.path.dirname(irida_api.__file__), 'data', 'reportable_serovars.tsv')

conf_dir = os.path.join(os.path.dirname(os.path.realpath(version.__file__)), 'etc')
appname = os.path.basename(__file__)

example_conf_file = os.path.join(conf_dir, 'config.ini.example')
conf_file = os.path.join(conf_dir, 'config.ini')

user_conf_dir = appdirs.user_data_dir(os.path.splitext(appname)[0])
user_conf_file = os.path.join(user_conf_dir, 'config.ini')

logger = logging.getLogger("irida-sistr-results")


def main(irida_url, client_id, client_secret, username, password, verbose, projects, tabular_file, excel_file,
         include_user_results, exclude_user_existing_results, reportable_serovars_file, exclude_reportable_status,
         all_projects, timeout, config, workflow_versions_or_ids, samples_created_since):
    """Main method connecting to IRIDA/writing SISTR results file."""

    logging.info("Running " + appname + " version " + version.__version__)

    samples_created_min_date = get_samples_created_min_date(samples_created_since)

    if password is None:
        password = getpass.getpass(
            'Enter password for user="' + arg_dict['username'] + '" on IRIDA="' + arg_dict['irida_url'] + '": ')

    if samples_created_min_date is not None:
        logger.debug('--samples-created-since set to [%s], only including samples created more recent than %s',
                     samples_created_since, samples_created_min_date)

    try:
        connector = IridaConnector(client_id, client_secret, username, password, irida_url, timeout)
    except KeyError as e:
        raise CommandParseException(
            "Error when connecting to IRIDA URL=[{}], Username=[{}], ClientID=[{}]. Perhaps the username/password or client_id/client_secret are invalid?".format(
                irida_url, username, client_id)) from e

    serovar_list = read_serovar_list(reportable_serovars_file)

    irida_api = IridaAPI(connector, serovar_list)
    irida_results = IridaSistrResults(irida_api, include_user_results, not exclude_user_existing_results,
                                      workflow_versions_or_ids, samples_created_min_date)

    if all_projects:
        logger.info("Getting results for all projects in IRIDA. This may take a while.")
        sistr_list = irida_results.get_sistr_results_all_projects()
    else:
        logger.info("Getting results for projects: " + str(projects) + ". This may take a while.")
        sistr_list = irida_results.get_sistr_results_from_projects(projects)

    total_sample_results = 0
    missing_sample_results = 0
    pass_sample_results = 0
    warn_sample_results = 0
    fail_sample_results = 0
    total_projects = 0
    total_reportable = 0
    for proj in sistr_list.keys():
        total_projects += 1
        for result in sistr_list[proj].values():
            total_sample_results += 1
            if (not result.has_sistr_results()):
                missing_sample_results += 1
            elif (result.get_qc_status() == 'PASS'):
                pass_sample_results += 1
            elif (result.get_qc_status() == 'WARNING'):
                warn_sample_results += 1
            elif (result.get_qc_status() == 'FAIL'):
                fail_sample_results += 1
            else:
                logger.error("Somehow got wrong qc info '" + result.get_qc_status() + "'")

            if result.is_reportable_serovar():
                total_reportable += 1

    proj_string = 'projects' if (total_projects > 1) else 'project'
    sample_created_string = ' created more recently than ' + samples_created_min_date.isoformat(
        sep=' ') if samples_created_min_date else ''
    logger.info("Done getting SISTR results\n")
    logger.info("Examined %s samples in %s %s%s with status", total_sample_results, total_projects, proj_string,
                sample_created_string)
    logger.info("PASS:    " + str(pass_sample_results))
    logger.info("WARN:    " + str(warn_sample_results))
    logger.info("FAIL:    " + str(fail_sample_results))
    logger.info("MISSING: " + str(missing_sample_results))

    if not exclude_reportable_status:
        logger.info("Of these a total of %s are considered as reportable", total_reportable)

    command_line = get_command_line_string()

    if tabular_file is not None:
        writer = SistrCsvWriter(irida_url, appname, command_line, username, tabular_file, not exclude_reportable_status,
                                samples_created_min_date)
        writer.write(sistr_list)
        writer.close()
        logger.info("Wrote results to file " + tabular_file)

    if excel_file is not None:
        writer = SistrExcelWriter(irida_url, appname, command_line, username, excel_file, not exclude_reportable_status,
                                  samples_created_min_date)
        writer.write(sistr_list)
        writer.close()
        logger.info("Wrote results to file " + excel_file)

def get_command_line_string():
    """
    Gets the command line string.

    :return: The command-line as a string.
    """
    PASSWORD_NAME = '--password'

    command_line_list=sys.argv[:]

    try:
        password_index = command_line_list.index(PASSWORD_NAME)
        command_line_list[password_index+1]='*****'
    except ValueError:
        pass

    return ' '.join(command_line_list)

def read_serovar_list(file):
    """
    Reads in the serovar list table from the given file.
    :param file: The file containing the serovar list.
    :return: A list containing the reportable serovars.
    """
    reportable_serovars = pd.read_table(file)
    serovar_list = reportable_serovars['serovar'].tolist()
    if not serovar_list:
        raise Exception("Error, serovar_table_file=[{}] is empty".format(REPORTABLE_SEROVARS_FILE))

    return serovar_list


def get_samples_created_min_date(samples_created_since):
    """
    Parses out the samples_created_since string into a particular minimum date.
    :param samples_created_since: The string to parse.
    :return: The minimum samples created date.
    """
    if samples_created_since is None:
        return None

    try:
        samples_created_min_date = datetime.strptime(samples_created_since, '%Y-%m-%d')
    except ValueError:
        try:
            samples_created_min_date = datetime.now() - timedelta(days=float(samples_created_since))
        except ValueError as e:
            raise CommandParseException(
                "Could not parse -d|--samples-created-since [{}], please enter either a number representing so many days ago (e.g., 7), or a minimum date in the format YYYY-MM-DD".format(
                    samples_created_since)) from e

    return samples_created_min_date


def setup_user_data_dir():
    if (not os.path.isdir(user_conf_dir)):
        logger.debug("Directory " + user_conf_dir + " does not exist, creating.")
        os.makedirs(user_conf_dir)

    if (not os.path.exists(user_conf_file)):
        logger.debug("File " + user_conf_file + " does not exist, copying default.")
        shutil.copy(example_conf_file, user_conf_file)
    else:
        logger.debug("File " + user_conf_file + " exists, will not copy default.")


def get_conf_files():
    """Gets a list of configuration file paths to attempt to read configuration values from."""
    setup_user_data_dir()
    return [conf_file,
            user_conf_file
            ]


def get_conf_files_plus(config_file):
    """
    Gets a list of configuration file paths plus the passed config file.

    :param config_file:  The additional configuration file to include in this list.

    :return:  The default configuration files plus the user-defined configuration file.
    """
    l = get_conf_files()
    l.append(config_file)
    return l


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Compile SISTR results from an IRIDA instance into a table.',
                                     formatter_class=argparse.RawTextHelpFormatter,
                                     epilog="\nExample:" +
                                            "\n\t" + appname + " -a -u irida-user -o out.xlsx" +
                                            "\n\t\tExports all SISTR results from all projects to a file 'out.xlsx'" +
                                            "\n\n\t" + appname + " -p 1 -p 2 -u irida-user -o out.xlsx" +
                                            "\n\t\tExports SISTR results from projects [1,2] to a file 'out.xlsx'" +
                                            "\n\n\t" + appname + " -p 1 -w 0.3 -w 0.2 -u irida-user -o out.xlsx" +
                                            "\n\t\tExports only SISTR results from workflow versions 0.3 and 0.2 from project [1]")

    parser.add_argument('--irida-url', action='store', dest='irida_url', help='The URL to the IRIDA instance.')
    parser.add_argument('--client-id', action='store', dest='client_id', help='The client id for an IRIDA instance.')
    parser.add_argument('--client-secret', action='store', dest='client_secret',
                        help='The client secret for the IRIDA instance.')
    parser.add_argument('-u', '--username', action='store', dest='username',
                        help='The username for the IRIDA instance.')
    parser.add_argument('--password', action='store', dest='password',
                        help='The password for the IRIDA instance. Prompts for password if left blank.')
    parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help='Turn on verbose logging.')
    parser.add_argument('-p', '--project', action='append', dest='projects',
                        help='Projects to scan for SISTR results. If left blank will scan all projects the user has access to.')
    parser.add_argument('-a', '--all-projects', action='store_true', dest='all_projects',
                        help='Explicitly load results from all projects the user has access to.  Will ignore the values given in --project.')
    parser.add_argument('--output-tab', '--to-tab-file', action='store', dest='tabular_file',
                        help='Print results to tab-deliminited file.')
    parser.add_argument('-o', '--output-excel', '--to-excel-file', action='store', dest='excel_file',
                        help='Print results to the given excel file.')
    parser.add_argument('--include-user-results', action='store_true', dest='include_user_results',
                        help='Include SISTR analysis results run directly by the user.')
    parser.add_argument('--exclude-user-existing-results', action='store_true', dest='exclude_user_existing_results',
                        help='If including user results, do not replace existing SISTR analysis that were automatically generated with user-run SISTR results.')
    parser.add_argument('--reportable-serovars-file', action='store', dest='reportable_serovars_file',
                        default=REPORTABLE_SEROVARS_FILE,
                        help='The reportable serovars file [{}].'.format(REPORTABLE_SEROVARS_FILE))
    parser.add_argument('--exclude-reportable-status', action='store_true', dest='exclude_reportable_status',
                        help='Excludes printing of reportable serovar status in final output.')
    parser.add_argument('-T', '--connection-timeout', action='store', dest='timeout',
                        help='Connection timeout when getting results from IRIDA.')
    parser.add_argument('-c', '--config', action='store', dest='config',
                        help='Configuration file for IRIDA (overrides values in ' + str(get_conf_files()) + ')')
    parser.add_argument('-V', '--version', action='version', version='%(prog)s {}'.format(version.__version__))
    parser.add_argument('-w', '--workflow', action='append', dest='workflow_versions_or_ids', default=None,
                        help="Only include results of these workflow versions (or uuids) {} [all versions]".format(
                            IridaSistrWorkflow.all_workflow_versions()))
    parser.add_argument('-d', '--samples-created-since', action='store', dest='samples_created_since',
                        default=None,
                        help='Only include samples created more recently than this date (in format YYYY-MM-DD) or this many days ago (as a number) [Include all samples]')

    if len(sys.argv) == 1:
        parser.print_help()
        sys.exit(1)

    args = parser.parse_args()
    arg_dict = vars(args)

    if (arg_dict['verbose']):
        logging.basicConfig(level=logging.DEBUG,
                            format='%(asctime)s %(levelname)s %(module)s.%(funcName)s,%(lineno)s: %(message)s')
    else:
        logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')

    config = configparser.ConfigParser()

    if (arg_dict['config'] is None):
        config.read(get_conf_files())
    else:
        config.read(get_conf_files_plus(arg_dict['config']))

    if ('irida' in config):
        conf_i = config['irida']
    else:
        logger.warning("No 'irida' information loaded in configuration files.")
        conf_i = {}

    try:
        if (arg_dict['irida_url'] is None):
            if 'url' in conf_i:
                arg_dict['irida_url'] = conf_i['url']
            else:
                raise Exception("Must set --irida-url or irida.url in config file (" + str(get_conf_files()) + ')')
        if (arg_dict['client_id'] is None):
            if 'client_id' in conf_i:
                arg_dict['client_id'] = conf_i['client_id']
            else:
                raise Exception(
                    "Must set --client-id or irida.client_id in config file (" + str(get_conf_files()) + ')')

        if (arg_dict['client_secret'] is None):
            if 'client_secret' in conf_i:
                arg_dict['client_secret'] = conf_i['client_secret']
            else:
                raise Exception(
                    "Must set --client-secret or irida.client_secret in config file (" + str(get_conf_files()) + ')')

        if (arg_dict['username'] is None):
            if 'username' in conf_i:
                arg_dict['username'] = conf_i['username']
            else:
                raise Exception("Must set --username or irida.username in config file (" + str(get_conf_files()) + ')')

        if (arg_dict['timeout'] is None):
            if ('timeout' in conf_i and conf_i['timeout'] is not None):
                arg_dict['timeout'] = int(conf_i['timeout'])
            else:
                arg_dict['timeout'] = 600
        elif (type(arg_dict['timeout']) is not int):
            arg_dict['timeout'] = int(arg_dict['timeout'])

        if (arg_dict['excel_file'] is None and arg_dict['tabular_file'] is None):
            raise Exception("Must use one of --to-tab-file or --to-excel-file [excel-file]")

        if (arg_dict['projects'] is None and arg_dict['all_projects'] is None):
            raise Exception("No --project or --all-projects parameter found.")
    except Exception as e:
        logging.error(e)
        sys.exit(1)

    try:
        main(**arg_dict)
    except CommandParseException as e:
        logger.debug(e, exc_info=True)
        logger.error(e)
        sys.exit(1)
