import configparser
import logging
import sys
import os

from datetime import datetime
from uncloud.common.etcd_wrapper import Etcd3Wrapper
from os.path import join as join_path

logger = logging.getLogger(__name__)
settings = None


class CustomConfigParser(configparser.RawConfigParser):
    def __getitem__(self, key):
        try:
            result = super().__getitem__(key)
        except KeyError as err:
            raise KeyError(
                'Key \'{}\' not found in configuration. Make sure you configure uncloud.'.format(
                    key
                )
            ) from err
        else:
            return result


class Settings(object):
    def __init__(self, conf_dir, seed_value=None):
        conf_name = 'uncloud.conf'
        self.config_file = join_path(conf_dir, conf_name)

        # this is used to cache config from etcd for 1 minutes. Without this we
        # would make a lot of requests to etcd which slows down everything.
        self.last_config_update = datetime.fromtimestamp(0)

        self.config_parser = CustomConfigParser(allow_no_value=True)
        self.config_parser.add_section('etcd')
        self.config_parser.set('etcd', 'base_prefix', '/')

        if os.access(self.config_file, os.R_OK):
            self.config_parser.read(self.config_file)
        else:
            raise FileNotFoundError('Config file %s not found!', self.config_file)
        self.config_key = join_path(self['etcd']['base_prefix'] + 'uncloud/config/')

        self.read_internal_values()

        if seed_value is None:
            seed_value = dict()

        self.config_parser.read_dict(seed_value)

    def get_etcd_client(self):
        args = tuple()
        try:
            kwargs = {
                'host': self.config_parser.get('etcd', 'url'),
                'port': self.config_parser.get('etcd', 'port'),
                'ca_cert': self.config_parser.get('etcd', 'ca_cert'),
                'cert_cert': self.config_parser.get('etcd', 'cert_cert'),
                'cert_key': self.config_parser.get('etcd', 'cert_key'),
            }
        except configparser.Error as err:
            raise configparser.Error(
                '{} in config file {}'.format(
                    err.message, self.config_file
                )
            ) from err
        else:
            try:
                wrapper = Etcd3Wrapper(*args, **kwargs)
            except Exception as err:
                logger.error(
                    'etcd connection not successfull. Please check your config file.'
                    '\nDetails: %s\netcd connection parameters: %s',
                    err,
                    kwargs,
                )
                sys.exit(1)
            else:
                return wrapper

    def read_internal_values(self):
        base_prefix = self['etcd']['base_prefix']
        self.config_parser.read_dict(
            {
                'etcd': {
                    'file_prefix': join_path(base_prefix, 'files/'),
                    'host_prefix': join_path(base_prefix, 'hosts/'),
                    'image_prefix': join_path(base_prefix, 'images/'),
                    'image_store_prefix': join_path(base_prefix, 'imagestore/'),
                    'network_prefix': join_path(base_prefix, 'networks/'),
                    'request_prefix': join_path(base_prefix, 'requests/'),
                    'user_prefix': join_path(base_prefix, 'users/'),
                    'vm_prefix': join_path(base_prefix, 'vms/'),
                    'vxlan_counter': join_path(base_prefix, 'counters/vxlan'),
                    'tap_counter': join_path(base_prefix, 'counters/tap')
                }
            }
        )

    def read_config_file_values(self, config_file):
        try:
            # Trying to read configuration file
            with open(config_file) as config_file_handle:
                self.config_parser.read_file(config_file_handle)
        except FileNotFoundError:
            sys.exit('Configuration file {} not found!'.format(config_file))
        except Exception as err:
            logger.exception(err)
            sys.exit('Error occurred while reading configuration file')

    def read_values_from_etcd(self):
        etcd_client = self.get_etcd_client()
        if (datetime.utcnow() - self.last_config_update).total_seconds() > 60:
            config_from_etcd = etcd_client.get(self.config_key, value_in_json=True)
            if config_from_etcd:
                self.config_parser.read_dict(config_from_etcd.value)
                self.last_config_update = datetime.utcnow()
            else:
                raise KeyError('Key \'{}\' not found in etcd. Please configure uncloud.'.format(self.config_key))

    def __getitem__(self, key):
        # Allow failing to read from etcd if we have
        # it locally
        if key not in self.config_parser.sections():
            try:
                self.read_values_from_etcd()
            except KeyError:
                pass
        return self.config_parser[key]


def get_settings():
    return settings