# coding: utf-8

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

import os
import logging
from pathlib2 import Path
from getpass import getuser

from .token_store import TokenStore
from .errors import TokenLoadError, TokenSaveError, TokenConflictError, TokenGetFail


class PersistentTokenStore(TokenStore):
    LOGGER = logging.getLogger(__name__)
    WELL_KNOWN_TOKEN_FILES = dict(
        sandbox=Path('.sandbox/{}.oauth'.format(getuser())),
        yt=Path(os.environ['YT_TOKEN_PATH']) if 'YT_TOKEN_PATH' in os.environ else Path('.yt/token'),
        yp=Path('.yp/token ')
    )
    WELL_KNOWN_TOKEN_URLS = dict(
        nanny='https://nanny.yandex-team.ru/ui/#/oauth/',
        sandbox='https://sandbox.yandex-team.ru/oauth',
        yav='https://oauth.yandex-team.ru/authorize?response_type=token&client_id=ce68fbebc76c4ffda974049083729982',
        yt='https://oauth.yt.yandex.net',
        puncher='https://oauth.yandex-team.ru/authorize?response_type=token&client_id=a4b9cc023f7244e8b2c7b4fa47c444a6',
        racktables='https://oauth.yandex-team.ru/authorize?response_type=token&client_id=4ae71bc12f4044b1ac9055efb0801c54'
    )

    @classmethod
    def ensure_tokens_for(cls, service_list):
        missing_tokens = []
        cls.add_tokens_from_env()
        for service in service_list:
            normal_service = cls._normalize_service(service)
            if normal_service not in cls._tokens:
                try:
                    cls.load_token_from_file(normal_service)
                except TokenLoadError:
                    missing_tokens.append(normal_service)
        if missing_tokens:
            error_message = 'Missing tokens for services: {}'.format(', '.join(missing_tokens))
            service_lines = [error_message, ]
            for service in missing_tokens:
                if service in cls.WELL_KNOWN_TOKEN_URLS:
                    error_msg = 'Get token for {} at {} and save it to {} or place in {} environment variable'.format(
                        service, cls.WELL_KNOWN_TOKEN_URLS[service],
                        cls._get_token_file(service), '{}_OAUTH'.format(service.upper())
                    )
                else:
                    error_msg = 'Please provide token for {} by {} file or {} environment variable'.format(
                        service, cls._get_token_file(service), '{}_OAUTH'.format(service.upper())
                    )
                service_lines.append(error_msg)
            error_text = '\n'.join(service_lines)
            logging.fatal(error_text)
            raise TokenGetFail(error_text)
        return all([(s in cls._tokens) for s in service_list])

    @classmethod
    def get_token_from_store_env_or_file(cls, service):
        try:
            return cls.add_tokens_from_env_and_get_token(service)
        except TokenGetFail:
            cls.load_token_from_file(service)  # may raise TokenLoadError
            return cls.get_token(service)

    @classmethod
    def add_persistent_token(cls, service, token):
        super(PersistentTokenStore, cls).add_token(service, token)
        try:
            cls.save_token_to_file(service)
            return True
        except TokenConflictError:
            cls.LOGGER.error('Token for %s not written to disc: token in file differ from current token)', service)
            return False
        except TokenSaveError:
            cls.LOGGER.error('Token for %s not written to disc: token file not writable', service)
            return False

    @classmethod
    def save_token_to_file(cls, service, overwrite_existing=False):
        token = cls.get_token(service)
        token_file = cls._get_token_file(service)
        token_dir = token_file.parent
        if not token_file.exists():
            token_dir.mkdir(parents=True, exist_ok=True)
            try:
                token_file.write_text(token)
                token_file.chmod(0o600)
            except IOError as e:
                raise TokenSaveError(e)
        else:
            try:
                with token_file.open('+') as f:
                    token_from_file = f.read().strip()
                    if token_from_file != token:
                        if overwrite_existing:
                            f.seek(0)
                            f.write(token)
                        else:
                            raise TokenConflictError('Token file {} for {} present and contains different token'.format(token_file, service))
                token_file.chmod(0o600)
            except IOError as e:
                raise TokenSaveError(e)

    @classmethod
    def load_token_from_file(cls, service):
        token_file = cls._get_token_file(service)
        cls.LOGGER.debug('Trying to get token from %s', token_file)
        try:
            with token_file.open() as f:
                cls.LOGGER.info('Adding %s token from %s', service, token_file)
                cls.add_token(service, f.read().strip())
        except IOError as e:
            cls.LOGGER.error('%s token file not readable or missing', service)
            raise TokenLoadError(e)

    @classmethod
    def _get_token_file(cls, service):
        if service not in cls.WELL_KNOWN_TOKEN_FILES:
            return Path.home().joinpath('.{}/token'.format(service))
        else:
            return Path.home().joinpath(cls.WELL_KNOWN_TOKEN_FILES[service]).resolve()
