import hashlib
import yaml
import os

from utils.db_handling import DbAccessor
from utils.db_handling.db_access import get_db_object_for_dbtype_and_version, DbRequirementsAccessor


def get_taxblast_result(config):
    if not config['taxblast']:
        return []
    return config['output']['task_result'].replace("{n}", str(config['taxblast']))


def get_md5_for_task(task_dict, db_base_dir):
    md5hash = hashlib.md5()
    md5hash.update(task_dict["prog"].encode())
    md5hash.update(task_dict["type"].encode())
    md5hash.update(task_dict["db_type"].encode())
    if task_dict["prog"] == "acc2annot_mapper":
        pass
    else:
        db_obj = get_db_obj_for_task_and_dbdir(task_dict, db_base_dir)
        md5hash.update(db_obj.get_version().encode())
        if "params" in task_dict:
            md5hash.update(yaml.dump(task_dict["params"], sort_keys=True).encode())
    return md5hash.hexdigest()


def get_task_best_hits_file(taskid, task_dict, db_base_dir):
    task_file_base_string = get_task_file_base_string(db_base_dir, task_dict, taskid)
    return f"tasks/{task_file_base_string}.best_hits"


def get_task_lca(taskid, task_dict, db_base_dir):
    task_file_base_string = get_task_file_base_string(db_base_dir, task_dict, taskid)
    return f'tasks/{task_file_base_string}.lca'


def get_task_lca_support(taskid, task_dict, db_base_dir):
    task_file_base_string = get_task_file_base_string(db_base_dir, task_dict, taskid)
    return f'tasks/{task_file_base_string}.lcasupport'


def get_task_map(taskid, task_dict, db_base_dir):
    task_file_base_string = get_task_file_base_string(db_base_dir, task_dict, taskid)
    return f'tasks/{task_file_base_string}.map'


def get_task_plot_file(taskid, task_dict, db_base_dir):
    task_file_base_string = get_task_file_base_string(db_base_dir, task_dict, taskid)
    return f"plots/plot_of_{task_file_base_string}.html"


def get_db_version_for_task(task_dict, db_base_dir):
    # Todo if the required DB is not yet downloaded, the user requested DB version should be returned.
    # ToDo Based on the constructed task string, snakemake can determine which DB to download.
    db_obj = get_db_obj_for_task_and_dbdir(task_dict, db_base_dir)
    return db_obj.get_version()


def get_db_obj(task_dict, db_base_dir):
    # Todo if the required DB is not yet downloaded, the user requested DB version should be returned.
    # ToDo Based on the constructed task string, snakemake can determine which DB to download.
    db_obj = get_db_obj_for_task_and_dbdir(task_dict, db_base_dir)
    return db_obj


def get_hmmer_version_for_db_type(db_type):
    # ToDo: this should really be read from the database object
    if db_type == "foam":
        version = "3-0"
    else:
        version = "latest"
    return version


def get_task_file_base_string(db_base_dir, task_dict, taskid):
    """
    :param db_base_dir: path/to/dbs_folder
    :param task_dict: OrderedDict([prog, type, shortname, db_type, db_version, params]
    :param taskid: int task ID
    # reads from task config file, change db_type and prog for eggnog and emapper depending on database version
    """
    annot_type = task_dict["type"][:3]
    db_type = task_dict["db_type"]
    prog = task_dict["prog"]
    if prog == "acc2annot_mapper":
        tool = prog
        db_type = "custom_map"
        db_version = "NA"
    else:
        db_obj = get_db_obj(task_dict, db_base_dir)
        db_version = db_obj.get_version()
        if prog in ['hmmscan', 'hmmsearch']:
            tool = f"hmmer-{get_hmmer_version_for_db_type(db_type)}"
        elif prog == "diamond blastp":
            tool = "diamond"
        elif prog == 'emapper':
            db_type = db_obj.get_db_type()
            tool = 'emapper_v2' if db_type == 'eggnog_v5' else 'emapper_v1'
        else:
            raise ValueError(f"Unexpected prog string in task {taskid}: {prog}")
    return f"{annot_type}_annot_by_{tool}_on_{db_type}.v{db_version}.task{taskid}"


def get_mafft_report(n):
    return f'algn/mafft.{n}.txt'


def get_db_obj_for_task_and_dbdir(task_config_dict, dbdir, db_acc=None):
    if not db_acc:
        db_acc = DbAccessor(dbdir)
    db_type = task_config_dict['db_type']
    db_version = 'newest'
    if 'db_version' in task_config_dict:
        db_version = task_config_dict['db_version']
    db_obj = get_db_object_for_dbtype_and_version(
        db_type,
        db_version,
        dbdir,
        db_acc
    )
    return db_obj


def construct_db_yaml_path_from_name_and_version(db_name, db_version, db_base_dir):
    if db_base_dir is None:
        db_base_dir = ""
    return os.path.join(db_base_dir, db_name, db_version, "db_config.yaml")


def convert_dbs_names_to_yaml_paths(lst_desired_dbs, versions=None, db_base_dir=None):
    if versions:
        if len(versions) != len(lst_desired_dbs):
            raise ValueError("One version number for each database is required."
                             + f"\n\tgot '{len(lst_desired_dbs)}' databases"
                             + f"\n\tgot '{len(versions)}' versions")
    else:
        versions = [DbRequirementsAccessor(db).get_most_recent_downloadable_version_string() for db in lst_desired_dbs]
    lst_yamls = []
    for db_name, ver in zip(lst_desired_dbs, versions):
        db_yaml = construct_db_yaml_path_from_name_and_version(db_name, ver, db_base_dir)
        lst_yamls.append(db_yaml)
    return lst_yamls
