import json
import logging
import os
from collections import namedtuple

from .errors.common import InvalidConfig

ENV_PREFIX = 'NETLAB_CONFIG_'
ENV_BOOLEANS = ['ssl']
CONFIG_FILENAME = os.path.join('.netlab', 'config.json')
DEFAULT_CONFIG_PATH = os.path.join(os.path.expanduser('~'), CONFIG_FILENAME)
log = logging.getLogger(__name__)

SystemConfig = namedtuple("SystemConfig", ['user', 'token', 'host', 'port', 'timeout', 'ssl'])


def get_system_config(system, config=None, config_path=None):
    defaults = {
        'host': 'localhost',
        'port': 9000,
        'timeout': 10,
        'ssl': True
    }

    if config:
        # config from Client init
        defaults.update(config)
    else:
        # file config
        defaults.update(load_config(system, config_path))

        # environment variables config
        env_vars = [var for var in os.environ.keys() if var.startswith(ENV_PREFIX)]
        if env_vars:
            env_config = {}
            for env in env_vars:
                key = env.replace(ENV_PREFIX, '').lower()
                if key in ENV_BOOLEANS:
                    # convert env value to python boolean
                    env_config[key] = os.environ.get(env).lower() in ("true", "yes", "t", "1")
                else:
                    env_config[key] = os.environ.get(env)
            defaults.update(env_config)

    if 'user' not in defaults:
        raise InvalidConfig('Config missing required "user".')
    if 'token' not in defaults:
        raise InvalidConfig('Config missing required "token".')
    return SystemConfig(**defaults)


def load_config(system=None, config_path=None):
    """
    Loads authentication data from a Netlab configuration file in the given
    root directory or if config_path is passed use given path.
    If no system is specified, return entire config dict.
    Lookup priority:
        explicit config_path parameter > NETLAB_CONFIG environment variable > ~/.netlab/config.json
    """
    config_file = find_config_file(config_path)

    if not config_file:
        return {}

    try:
        with open(config_file) as f:
            data = json.load(f)
            if system:
                return data.get(system, {})
            else:
                return data
    except (IOError, KeyError, ValueError) as e:
        log.debug(e)
        raise


def add_config(system, config, config_path=None):
    """
    Adds system and config data from a Netlab to the configuration file
    """
    current_config = load_config(config_path=config_path)
    current_config[system] = config
    config_file = find_config_file(config_path)
    if not config_file:
        config_file = DEFAULT_CONFIG_PATH

    try:
        os.makedirs(os.path.dirname(config_file), exist_ok=True)
        with open(config_file, 'w') as f:
            json.dump(current_config, f, sort_keys=True, indent=4)
    except (IOError, KeyError, ValueError) as e:
        log.debug(e)
        raise


def remove_config(system, config_path=None):
    """
    Removes system and config data from the configuration file
    """
    current_config = load_config(config_path=config_path)
    if not system:
        current_config = {}
    else:
        current_config.pop(system, None)

    config_file = find_config_file(config_path)

    try:
        with open(config_file, 'w') as f:
            json.dump(current_config, f, sort_keys=True, indent=4)
    except (IOError, KeyError, ValueError) as e:
        log.debug(e)
        raise


def find_config_file(config_path=None):
    environment_path = os.path.join(
        os.environ.get('NETLAB_CONFIG'),
        os.path.basename(CONFIG_FILENAME)
    ) if os.environ.get('NETLAB_CONFIG') else None

    paths = filter(None, [
        config_path,
        environment_path,
        DEFAULT_CONFIG_PATH
    ])

    for path in paths:
        if os.path.exists(path):
            return path

    return None
