import os
import sys
import logging
import shutil
import re
from string import ascii_lowercase
import contextlib
import inspect
from pathlib import Path
sys.path.append(str(Path(inspect.getfile(lambda: None))))
from pipeline.utils.snakefile import  get_setting, set_setting, get_config
from pipeline.utils.snakefile import load_config_file, locate_str_in_list
from pipeline.utils.snakefile import read_stage_output, update_configfile
log = logging.getLogger()
logging.basicConfig(level=logging.INFO)


def create_new_configfile(profile, stage=None, stage_number=None, parent=None):
    if stage is None:
        if stage_number is None:
            raise KeyError("You must either specify `stage` or `stage_number`!")
        else:

            stage = get_setting('stages')[stage_number]

    config_path = Path(get_setting('config_path'))

    parent = f'_{parent}' if parent else ''
    config_dir = config_path / stage / 'configs'
    template_config_path = config_dir / f'config{parent}.yaml'
    new_config_path = config_dir / f'config_{profile}.yaml'

    if new_config_path.exists():
        log.debug(f"config file `{new_config_path}` already exists. Skip.")
        return None
    
    if not template_config_path.exists():
        log.debug(f"parent config file `{template_config_path}` doesn't exist.")
        potential_parents = [f.name for f in config_dir.iterdir() \
                                    if f.name.startswith(f'config{parent}')]
        if potential_parents:
            log.debug(f"Using `{potential_parents[0]}` instead.")
            template_config_path = config_dir / potential_parents[0]
        else:
            raise FileNotFoundError("No parent config file fitting the name "
                                   f"`{parent.strip('_')}` found!")

    shutil.copy(template_config_path, new_config_path)
    update_configfile(new_config_path, {'PROFILE':profile})
    return None


def input_profile(profile=None):
    if not is_profile_name_valid(profile) and profile is not None:
        log.info(f"profile name {profile} is not valid!")
        profile = None
    if profile is None:
        while not is_profile_name_valid(profile):
            profile = input("profile name: ")
            if not is_profile_name_valid(profile):
                log.info(f"{profile} is not a valid profile name. "\
                        "Please use only letters, "\
                        "'_' (for sub-profile structuring), "\
                        "or '|' (for variant separation).")
    return profile
    

def is_profile_name_valid(profile: str) -> bool:
    if type(profile) == str:
        profile = profile.strip("'\"")
        pattern = re.compile("[\w\d\|]+")
        return bool(pattern.fullmatch(profile))
    else:
        return False


def get_profile(profile=None, parent_profile=None):
    # set initial profile name
    profile = input_profile(profile=profile)
    
    # set full profile name
    if parent_profile is None:
        parent_profile = input("Specify the parent profile name [optional]:")

    if is_profile_name_valid(parent_profile) and '|' not in parent_profile \
    and not profile.startswith(parent_profile):
        profile = f"{parent_profile}_{profile}"
    elif parent_profile:
        log.info(f"{parent_profile} is not a valid profile parent name. "\
                "Ignore.")
        parent_profile = None
    return profile, parent_profile


def setup_entry_stage(profile, parent_profile=None, 
                  data_path=None, loading_script_name=None):
    """
    Populate an existing config file for stage 01 with a data path and create
    a corresponding loading script from a template.
    """
    config_path = Path(get_setting('config_path'))
    
    stage01_update_dict = {}

    # set data path
    if data_path is None:
        data_path = (input("Path to dataset [optional, "\
                           "can be set later in the stage01 config file]:")
                    or None)
    if data_path is not None:
        stage01_update_dict.update({'DATA_SETS': {profile: data_path}})
        
    # set loading script path
    if loading_script_name is None:
        base_profile = parent_profile if parent_profile else profile

        loading_script_name = (input("Loading script name (to be created) "\
                                "in <config_path>/stage01_data_entry/scripts/ "\
                                "[default: enter_<profile>.py]:")
                            or f"enter_{base_profile}.py")
        if not Path(loading_script_name).suffix == '.py':
            loading_script_name = f"{str(Path(loading_script_name).stem)}.py"
        stage01_update_dict.update({'CURATION_SCRIPT': loading_script_name})
    
    stages = get_setting('stages')
    loading_script_path = config_path / stages['1'] / 'scripts' \
                                      / f'{loading_script_name}'
    if loading_script_path.exists():
        log.info(f"`{loading_script_name}` already exists. Skip.")
    else:
        shutil.copy(config_path / stages['1'] / 'scripts' \
                                              / 'enter_data_template.py',
                    loading_script_path)

    # update stage 01 config
    stage01_config_path = config_path / stages['1'] / 'configs' \
                                        / f'config_{profile}.yaml'    
    update_configfile(stage01_config_path, stage01_update_dict)
    return None


def get_initial_available_stages():
    pipeline_path = Path(get_setting('pipeline_path'))
    stages = [x.name for x in pipeline_path.iterdir() 
                        if x.is_dir() 
                        and str(x.name).startswith('stage') 
                        and 'template' not in x.name]
    stages.sort()
    stage_numbers = [int(re.findall(r"\d+", stage)[0]) for stage in stages]
    stage_keys = []
    for i, v in enumerate(stage_numbers):
        totalcount = stage_numbers.count(v)
        count = stage_numbers[:i].count(v)
        stage_keys.append(str(v) + ascii_lowercase[count] 
                            if totalcount > 1 and count > 0 else str(v))
    return {k:v for k,v in zip(stage_keys, stages)}

@contextlib.contextmanager
def working_directory(path):
    """Changes working directory and returns to previous on exit."""
    prev_cwd = Path.cwd()
    os.chdir(path)
    try:
        yield
    finally:
        os.chdir(prev_cwd)
    return None
