# coding: utf-8

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import re
import os
import six
import logging

from .errors import TokenStoreError, TokenGetFail, TokenAlreadyExists


class TokenStore(object):
    LOGGER = logging.getLogger(__name__)
    INTERNAL_OAUTH_REGEXP = re.compile(r'[A-Z]{4}-[a-zA-Z0-9_\-]{34}')
    COMMON_ENV_VAR_PATTERNS = (
        re.compile(r'\AOAUTH_(?P<srv>\w+)\Z'),
        re.compile(r'\A(?P<srv>\w+?)(_OAUTH|_TOKEN)+\Z'),
    )
    _evn_loaded = False
    _tokens = {}

    @classmethod
    def mask_token(cls, token):
        if cls.INTERNAL_OAUTH_REGEXP.match(token):
            return '{}-******************************{}'.format(token[:4], token[-4:])
        elif len(token) >= 16:
            return '{}{}'.format('*' * (len(token) - 4), token[-4:])
        elif len(token) > 4:
            cls.LOGGER.warning('Masking token with length < 16, 2 symbols from {} unmasked'.format(len(token)))
            return '{}{}'.format('*' * (len(token) - 2), token[-2:])
        else:
            cls.LOGGER.warning('Disclose no symbols from token with length < 4')
            return '*' * len(token)

    @classmethod
    def get_token(cls, service):
        service = cls._normalize_service(service)
        if service in cls._tokens:
            return cls._tokens[service]
        else:
            raise TokenGetFail('Token for {} not found in store'.format(service))

    @classmethod
    def add_tokens_from_env_and_get_token(cls, service):
        if not cls._evn_loaded:
            cls.add_tokens_from_env()
        return cls.get_token(service)

    @classmethod
    def get_token_from_store_or_env(cls, service):
        return cls.add_tokens_from_env_and_get_token(service)

    @classmethod
    def add_token(cls, service, token):
        service = cls._normalize_service(service)
        token = token.strip()
        if service in cls._tokens and cls._tokens[service] != token:
            raise TokenAlreadyExists('Token for {} already present in store'.format(service))
        else:
            cls.LOGGER.info('Adding token %s for %s to TokenStore', cls.mask_token(token), service)
            cls._tokens[service] = token

    @classmethod
    def add_or_update_token(cls, service, token):
        service = cls._normalize_service(service)
        if service in cls._tokens:
            cls.LOGGER.info('Updating %s token in TokenStore. New token: {}', service, cls.mask_token(token))
        else:
            cls.LOGGER.info('Adding token %s for %s to TokenStore', cls.mask_token(token), service)
        cls._tokens[service] = token

    @classmethod
    def add_tokens_from_env(cls):
        """Add missing tokens from env. Existing tokens won\'t be overwritten"""
        for service, token in cls._tokens_from_env():
            if service not in cls._tokens:
                cls.add_token(service, token)
        return cls

    @classmethod
    def load_tokens_from_env(cls):
        """Load tokens form env. Existing tokens will be replaced with tokens from env"""
        for service, token in cls._tokens_from_env():
            cls.add_or_update_token(service, token)
        return cls

    @classmethod
    def has_tokens_for(cls, service_list):
        """
        return true if all services in list have token in store.
        """
        return all([(s in cls._tokens) for s in service_list])

    @classmethod
    def _service_from_env(cls, env_var):
        service = None
        for pattern in cls.COMMON_ENV_VAR_PATTERNS:
            match = pattern.match(env_var)
            if match:
                match_service = match.group('srv')
                if service is None:
                    service = match_service
                elif match_service == service:
                    cls.LOGGER.warning('Multiple patterns match regexp %s', env_var)
                else:
                    raise TokenStoreError('Env {} matched multiple patterns with different results. Results:{}, {}'.format(env_var, service, match_service))
            else:
                continue
        return cls._normalize_service(service) if service else None

    @classmethod
    def _tokens_from_env(cls):
        for k, v in six.iteritems(os.environ):
            service_candidate = cls._service_from_env(k)
            if service_candidate:
                cls.LOGGER.debug('Assume evn var %s have token for %s', k, service_candidate)
                yield (service_candidate, v)

    @classmethod
    def _normalize_service(cls, service):
        return service.lower().strip()
