''' The configuration specific Module. '''

import os
import yaml
import dotenv
import logging
from copy import deepcopy


from .constants import DEFAULT_CONFIG_FILE
from .core import fs, vault
from .core.output import halt, info
from .core.util.colors import cyan
from .core.util.object import merge
from .core.util.types import is_dict
from .core.constants.config import DEFAULT_CONFIG, PSD


_config = deepcopy(DEFAULT_CONFIG)


def get():
    '''
    Return the loaded configuration.
    Note: The returned config is a deep clone of the original config.
    '''
    return deepcopy(_config)


def resolve_dotenv_file(path, stage=None):
    '''
    Resolve dotenv file and load environment vars if it exists.
    If stage parameter is provided, then stage specific .env file is resolved,
    for instance .env.production if stage=production etc.
    If stage is None, just .env file is resolved.
    '''
    filename = '.env' + ('' if not stage else '.{}'.format(stage))
    dotenv_path = os.path.join(path, filename)
    fallback_path = os.path.join(path, '.env')

    if fs.exists(dotenv_path):
        info('Resolving env file: {}'.format(cyan(dotenv_path)))
        dotenv.load_dotenv(dotenv_path)

    elif fs.exists(fallback_path):
        info('Resolving env file: {}'.format(cyan(fallback_path)))
        dotenv.load_dotenv(fallback_path)


def get_deployment_preset(raw_config):
    ''' Get the deployment preset for a raw config. '''
    has_preset = (
        is_dict(raw_config.get('deployment')) and
        'preset' in raw_config.get('deployment')
    )

    # If preset is configured return it.
    if has_preset:
        return raw_config['deployment']['preset']

    # Else return the default deployment config preset.
    return DEFAULT_CONFIG['deployment']['preset']


def merge_config(raw_config):
    '''
    Merge the default and preset specific default configs,
    to the raw configuration, add stage default configuration
    to each stage too and return the merged result.
    '''
    preset = get_deployment_preset(raw_config)
    preset_defaults = PSD[preset]
    all_defaults = merge(DEFAULT_CONFIG, preset_defaults)
    result = merge(all_defaults, raw_config)
    base_config = get_base_config(result)

    # Add base config to each of the stage config
    for (stage_name, _) in result['stages'].items():
        stage_config = result['stages'][stage_name]
        merged_stage_config = merge(base_config, stage_config)
        result['stages'][stage_name].update(merged_stage_config)

    return result


def parse_config(raw_config):
    '''
    Parse a raw config yaml encoded string,
    and merge it with the defaults before it's used everywhere.
    '''
    parsed = yaml.load(raw_config, Loader=yaml.FullLoader) or {}

    return merge_config(parsed)


def load(filename=DEFAULT_CONFIG_FILE, stage=None):
    ''' Load the configuration and return it. '''
    try:
        # pass
        config_str = fs.read(filename)
        resolve_dotenv_file(os.path.dirname(filename), stage)

        # Check if vault is configured.
        use_vault_if_enabled(config_str, stage)

        # Expand the environment variables used in the yaml config.
        loaded_config = os.path.expandvars(config_str)

        # Parse the yaml configuration.
        merged_config = parse_config(loaded_config)

        _config.update(merged_config)

        return get()

    except KeyError:
        halt('Invalid configuration file "{}"'.format(filename))

    except IOError as err:
        # TODO: Handle from logger util.
        if os.environ.get('DEBUG') == 'true':
            logging.exception(err)

        halt(err)


def get_base_config(resolved_config=None):
    ''' Get the base configuration. '''
    config = resolved_config or _config

    return {
        'user': config.get('user'),
        'port': config.get('port'),
        'vault': config.get('vault'),
        'branch': config.get('branch'),
        'cwd': config.get('cwd'),
        'deployment': config.get('deployment'),
        'repository_url': config.get('repository_url'),
        'remote_env_path': config.get('remote_env_path')
    }


def get_stage_config(stage):
    ''' Retrieve the configuration for the given stage. '''
    try:
        return _config['stages'][stage]
    except KeyError:
        halt('Unknown stage %s. Stage should be any one of %s' % (
            stage, _config['stages'].keys()
        ))

def is_vault_enabled(raw_config):
    ''' Check if vault is configured using raw config. '''
    return raw_config['vault']['enabled']


def use_vault_if_enabled(config_str, stage=None):
    ''' Check if vault is configured using raw config. '''
    raw_config = parse_config(config_str)

    # Skip if vault is not enabled.
    if not is_vault_enabled(raw_config):
        return

    # Load secrets from vault and inject into env.
    if stage and stage in raw_config['stages']:
        path = raw_config['stages'][stage]['vault']['path']
    else:
        path = raw_config['vault']['path']

    vault.env_inject_secrets(path, silent=raw_config['vault']['silent'])

''' The configuration specific Module. '''

import os
import yaml
import dotenv
import logging
from copy import deepcopy


from .constants import DEFAULT_CONFIG_FILE
from .core import fs, vault
from .core.output import halt, info
from .core.util.colors import cyan
from .core.util.object import merge
from .core.util.types import is_dict
from .core.constants.config import DEFAULT_CONFIG, PSD


_config = deepcopy(DEFAULT_CONFIG)


def get():
    '''
    Return the loaded configuration.
    Note: The returned config is a deep clone of the original config.
    '''
    return deepcopy(_config)


def resolve_dotenv_file(path, stage=None):
    '''
    Resolve dotenv file and load environment vars if it exists.
    If stage parameter is provided, then stage specific .env file is resolved,
    for instance .env.production if stage=production etc.
    If stage is None, just .env file is resolved.
    '''
    filename = '.env' + ('' if not stage else '.{}'.format(stage))
    dotenv_path = os.path.join(path, filename)
    fallback_path = os.path.join(path, '.env')

    if fs.exists(dotenv_path):
        info('Resolving env file: {}'.format(cyan(dotenv_path)))
        dotenv.load_dotenv(dotenv_path)

    elif fs.exists(fallback_path):
        info('Resolving env file: {}'.format(cyan(fallback_path)))
        dotenv.load_dotenv(fallback_path)


def get_deployment_preset(raw_config):
    ''' Get the deployment preset for a raw config. '''
    has_preset = (
        is_dict(raw_config.get('deployment')) and
        'preset' in raw_config.get('deployment')
    )

    # If preset is configured return it.
    if has_preset:
        return raw_config['deployment']['preset']

    # Else return the default deployment config preset.
    return DEFAULT_CONFIG['deployment']['preset']


def merge_config(raw_config):
    '''
    Merge the default and preset specific default configs,
    to the raw configuration, add stage default configuration
    to each stage too and return the merged result.
    '''
    preset = get_deployment_preset(raw_config)
    preset_defaults = PSD[preset]
    all_defaults = merge(DEFAULT_CONFIG, preset_defaults)
    result = merge(all_defaults, raw_config)
    base_config = get_base_config(result)

    # Add base config to each of the stage config
    for (stage_name, _) in result['stages'].items():
        stage_config = result['stages'][stage_name]
        merged_stage_config = merge(base_config, stage_config)
        result['stages'][stage_name].update(merged_stage_config)

    return result


def parse_config(raw_config):
    '''
    Parse a raw config yaml encoded string,
    and merge it with the defaults before it's used everywhere.
    '''
    parsed = yaml.load(raw_config, Loader=yaml.FullLoader) or {}

    return merge_config(parsed)


def load(filename=DEFAULT_CONFIG_FILE, stage=None):
    ''' Load the configuration and return it. '''
    try:
        # pass
        config_str = fs.read(filename)
        resolve_dotenv_file(os.path.dirname(filename), stage)

        # Check if vault is configured.
        use_vault_if_enabled(config_str, stage)

        # Expand the environment variables used in the yaml config.
        loaded_config = os.path.expandvars(config_str)

        # Parse the yaml configuration.
        merged_config = parse_config(loaded_config)

        _config.update(merged_config)

        return get()

    except KeyError:
        halt('Invalid configuration file "{}"'.format(filename))

    except IOError as err:
        # TODO: Handle from logger util.
        if os.environ.get('DEBUG') == 'true':
            logging.exception(err)

        halt(err)


def get_base_config(resolved_config=None):
    ''' Get the base configuration. '''
    config = resolved_config or _config

    return {
        'user': config.get('user'),
        'port': config.get('port'),
        'vault': config.get('vault'),
        'branch': config.get('branch'),
        'cwd': config.get('cwd'),
        'deployment': config.get('deployment'),
        'repository_url': config.get('repository_url'),
        'remote_env_path': config.get('remote_env_path')
    }


def get_stage_config(stage):
    ''' Retrieve the configuration for the given stage. '''
    try:
        return _config['stages'][stage]
    except KeyError:
        halt('Unknown stage %s. Stage should be any one of %s' % (
            stage, _config['stages'].keys()
        ))

def is_vault_enabled(raw_config):
    ''' Check if vault is configured using raw config. '''
    return raw_config['vault']['enabled']


def use_vault_if_enabled(config_str, stage=None):
    ''' Check if vault is configured using raw config. '''
    raw_config = parse_config(config_str)

    # Skip if vault is not enabled.
    if not is_vault_enabled(raw_config):
        return

    # Load secrets from vault and inject into env.
    if stage and stage in raw_config['stages']:
        path = raw_config['stages'][stage]['vault']['path']
    else:
        path = raw_config['vault']['path']

    vault.env_inject_secrets(path, silent=raw_config['vault']['silent'])

The configuration specific Module. Return the loaded configuration.     Note: The returned config is a deep clone of the original config. Resolve dotenv file and load environment vars if it exists.     If stage parameter is provided, then stage specific .env file is resolved,     for instance .env.production if stage=production etc.     If stage is None, just .env file is resolved. Get the deployment preset for a raw config. # If preset is configured return it. # Else return the default deployment config preset. Merge the default and preset specific default configs,     to the raw configuration, add stage default configuration     to each stage too and return the merged result. # Add base config to each of the stage config Parse a raw config yaml encoded string,     and merge it with the defaults before it's used everywhere. Load the configuration and return it. # pass # Check if vault is configured. # Expand the environment variables used in the yaml config. # Parse the yaml configuration. # TODO: Handle from logger util. Get the base configuration. Retrieve the configuration for the given stage. Check if vault is configured using raw config. Check if vault is configured using raw config. # Skip if vault is not enabled. # Load secrets from vault and inject into env.