#  Copyright 2015-2016 Palo Alto Networks, Inc
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import os

import gevent

import yaml
import filelock
import passlib.apache

from . import utils
from .logger import LOG


CONFIG = {}
API_CONFIG_PATH = None
API_CONFIG_LOCK = None

CONFIG_FILES_RE = '^(?:(?:[0-9]+.*\.yml)|(?:.*\.htpasswd))$'

# if you change things here change also backup/import API
_AUTH_DBS = {
    'USERS_DB': 'wsgi.htpasswd',
    'FEEDS_USERS_DB': 'feeds.htpasswd'
}


def get(key, default=None):
    try:
        result = CONFIG[key]
    except KeyError:
        pass
    else:
        return result

    try:
        result = os.environ[key]
    except KeyError:
        pass
    else:
        if result == 'False':
            result = False
        if result == 'True':
            result = True

        return result

    return default


def store(file, value):
    with API_CONFIG_LOCK.acquire():
        with open(os.path.join(API_CONFIG_PATH, file), 'w+') as f:
            yaml.safe_dump(value, stream=f)


def lock():
    return API_CONFIG_LOCK.acquire()


class APIConfigDict(object):
    def __init__(self, attribute, level=50):
        self.attribute = attribute
        self.filename = '%d-%s.yml' % (level, attribute.lower().replace('_', '-'))

    def set(self, key, value):
        curvalues = get(self.attribute, {})
        curvalues[key] = value
        store(self.filename, {self.attribute: curvalues})

    def delete(self, key):
        curvalues = get(self.attribute, {})
        curvalues.pop(key, None)
        store(self.filename, {self.attribute: curvalues})

    def value(self):
        return get(self.attribute, {})


def _load_config(config_path):
    global CONFIG

    new_config = {}

    # comptaibilty early releases where all the config
    # was store in a single file
    old_config_file = os.path.join(config_path, 'wsgi.yml')
    if os.path.exists(old_config_file):
        try:
            with open(old_config_file, 'r') as f:
                add_config = yaml.safe_load(f)

            if add_config is not None:
                new_config.update(add_config)
        except OSError:
            pass

    with API_CONFIG_LOCK.acquire():
        api_config_path = os.path.join(config_path, 'api')
        if os.path.exists(api_config_path):
            config_files = sorted(os.listdir(api_config_path))

            for cf in config_files:
                if not cf.endswith('.yml'):
                    continue

                try:
                    with open(os.path.join(api_config_path, cf), 'r') as f:
                        add_config = yaml.safe_load(f)

                    if add_config is not None:
                        new_config.update(add_config)

                except (OSError, IOError, ValueError):
                    LOG.exception('Error loading config file %s' % cf)

    CONFIG = new_config
    LOG.info('Config loaded: %r', new_config)


def _load_auth_dbs(config_path):
    with API_CONFIG_LOCK.acquire():
        api_config_path = os.path.join(config_path, 'api')
        for env, default in _AUTH_DBS.iteritems():
            dbname = get(env, default)
            new_db = False

            dbpath = os.path.join(
                api_config_path,
                dbname
            )

            # for compatibility with old releases
            if not os.path.exists(dbpath):
                old_dbpath = os.path.join(
                    config_path,
                    dbname
                )
                if os.path.exists(old_dbpath):
                    dbpath = old_dbpath
                else:
                    new_db = True

            CONFIG[env] = passlib.apache.HtpasswdFile(
                path=dbpath,
                new=new_db
            )

            LOG.info('%s loaded from %s', env, dbpath)


def _config_monitor(config_path):
    api_config_path = os.path.join(config_path, 'api')
    dirsnapshot = utils.DirSnapshot(api_config_path, CONFIG_FILES_RE)
    while True:
        try:
            with API_CONFIG_LOCK.acquire(timeout=600):
                new_snapshot = utils.DirSnapshot(api_config_path, CONFIG_FILES_RE)

                if new_snapshot != dirsnapshot:
                    try:
                        _load_config(config_path)
                        _load_auth_dbs(config_path)

                    except gevent.GreenletExit:
                        break

                    except:
                        LOG.exception('Error loading config')

                    dirsnapshot = new_snapshot

        except filelock.Timeout:
            LOG.error('Timeout locking config in config monitor')

        gevent.sleep(1)


# initialization
def init():
    global API_CONFIG_PATH
    global API_CONFIG_LOCK

    config_path = os.environ.get('MM_CONFIG', None)
    if config_path is None:
        LOG.critical('MM_CONFIG environment variable not set')
        raise RuntimeError('MM_CONFIG environment variable not set')

    if not os.path.isdir(config_path):
        config_path = os.path.dirname(config_path)

    # init global vars
    API_CONFIG_PATH = os.path.join(config_path, 'api')
    API_CONFIG_LOCK = filelock.FileLock(
        os.environ.get('API_CONFIG_LOCK', '/var/run/minemeld/api-config.lock')
    )

    _load_config(config_path)
    _load_auth_dbs(config_path)
    if config_path is not None:
        gevent.spawn(_config_monitor, config_path)
