# -*- coding: utf-8 -*-

import logging
import requests

from retrying import retry
from six.moves.urllib_parse import urljoin

from saas.library.python.common_functions.common_functions import wait_minute

from .tag import GencfgTag
from .errors import GencfgApiError, GencfgGroupNotFound


class GencfgAPI(object):
    """
    Main class for Gencfg API requests.
    """
    BASE_URL = 'https://api.gencfg.yandex-team.ru/'
    DEFAULT_HEADERS = {'Content-Type': 'application/json'}
    LOGGER = logging.getLogger(__name__)
    _RETRY_CONF = {
        'stop_max_attempt_number': 5,
        'stop_max_delay': 5*60*1000,  # stop after 5 minutes
        'wait_exponential_multiplier': 1000,  # Wait 2^x * 1000 milliseconds between each retry
        'wait_exponential_max': 10 * 1000  # set maximum time between attempts = 10 seconds
    }
    _CONNECTION = requests.session()
    _CONNECTION.headers = DEFAULT_HEADERS

    def __init__(self, debug=False, tag=None, wait_function=wait_minute):
        self._debug = debug
        self._tag = GencfgTag(tag)
        self._wait_function = wait_function

    @property
    def api_url(self):
        api_path = 'tags/{}/'.format(self._tag) if self._tag != 'trunk' else 'trunk/'
        api_url = urljoin(self.BASE_URL, api_path)
        return urljoin(self.BASE_URL, api_url)

    @property
    def tag(self):
        return self._tag

    @tag.setter
    def tag(self, tag):
        self._tag = GencfgTag(tag)

    @retry(**_RETRY_CONF)
    def _get(self, url, **kwargs):
        request_url = urljoin(self.api_url, url)
        kwargs['timeout'] = kwargs.get('timeout', 30)
        result = self._CONNECTION.get(request_url, **kwargs)
        if result.status_code == requests.codes.ok:
            return result.json()
        else:
            raise GencfgApiError(result)

    @retry(**_RETRY_CONF)
    def _post(self, url, **kwargs):
        request_url = urljoin(self.api_url, url)
        kwargs['timeout'] = kwargs.get('timeout', 60)
        result = self._CONNECTION.post(request_url, **kwargs)
        return result.json()

    @classmethod
    @retry(**_RETRY_CONF)
    def get_tags(cls):
        """
        Get list of gencfg stable tags.
        """
        request_url = urljoin(cls.BASE_URL, '/trunk/tags')
        tags = cls._CONNECTION.get(request_url).json()['displayed_tags']
        return [GencfgTag(tag) for tag in tags]

    @classmethod
    @retry(**_RETRY_CONF)
    def get_tag_by_commit(cls, commit):
        """
        Get gencfg tag with changes from commit
        :param commit: Svn revision with changes
        :return: Oldest Gencfg tag with changes from revision
        """
        request_url = urljoin(cls.BASE_URL, '/tags/tag_by_commit/{}'.format(commit))
        return GencfgTag(cls._CONNECTION.get(request_url).json()['tag'])

    @classmethod
    def wait_for_tag_with_commit(cls, commit):
        """
        Wait gor gencfg tag with changes present in commit
        :param commit: Svn revision with changes
        :return: Oldest Gencfg tag with changes from revision
        """
        tag = cls.get_tag_by_commit(commit)
        while not tag:
            wait_minute()
            tag = cls.get_tag_by_commit(commit)
        return tag

    @classmethod
    def _wait_for_groups_in_trunk(cls, groups):
        """
        Wait gor gencfg tag with changes present in commit
        :param groups: List of groups to wait for
        """
        while not all([cls.group_present_in_trunk(group) for group in groups]):
            wait_minute()
        return None

    @classmethod
    def wait_for_tag_with_groups(cls, groups):
        """
        Get last tag with all specified groups.
        """
        unknown_groups = set([group for group in groups if not cls.group_present_in_trunk(group)])
        if unknown_groups:
            raise GencfgGroupNotFound(group=','.join(unknown_groups), tag=GencfgTag(None))

        current_tag = cls.get_latest_tag()
        groups_present = [cls.group_present_in_tag(group, current_tag) for group in groups]
        while not all(groups_present):
            latest_tag = cls.get_latest_tag()
            if latest_tag > current_tag:
                current_tag = latest_tag
                for i, group in enumerate(groups):
                    if not groups_present[i]:
                        groups_present[i] = cls.group_present_in_tag(group, current_tag)
            else:
                logging.info('Groups %s was not found in latest tag %s, waiting some time', str(groups), current_tag)
                wait_minute()
        return current_tag

    @classmethod
    def get_latest_tag(cls):
        """
        Short method for getting latest gencfg tag.
        """
        latest_tag = cls.get_tags()[0]
        return latest_tag

    @classmethod
    def group_present_in_trunk(cls, group):
        return cls().group_exists(group)

    @classmethod
    def group_present_in_tag(cls, group, tag):
        return cls(tag=tag).group_exists(group)

    def get_groups_by_hostlist(self, host_list):
        """
        Get list of groups that are involved from the list of hosts.
        :param host_list: list of hostnames
        """
        return self._post('hosts/hosts_to_groups', json={'hosts': host_list})

    def group_exists(self, group):
        try:
            self.get_group_info(group)
            return True
        except GencfgGroupNotFound:
            return False

    def get_group_info(self, group, sub_path=''):
        """
        Get information about the specified group.
        :param group: Group name
        :param sub_path: api group sub path
        """
        try:
            return self._get('groups/{}{}'.format(group, sub_path))
        except GencfgApiError as e:
            if e.code == 404 and e.gencfg_response.text == 'group does not exist':
                raise GencfgGroupNotFound(group, self.tag, e.gencfg_response)
            else:
                raise e

    def get_group_info_card(self, group):
        """
        Get information about the specified group.
        :param group: Group name
        """
        return self.get_group_info(group, '/card')

    def get_group_hostlist(self, group):
        """
        Get list of group hosts
        :param group: Group name
        """
        return self.get_group_info(group)['hosts']

    def get_slave_groups(self, group):
        """
        Get list of slave groups of specified group.
        :param group: Master group name
        """
        return self.get_group_info_card(group)['slaves']

    def get_hosts_data(self, hosts):
        """
        Get information about specified list of hosts from Gencfg.
        :param hosts: List of hosts
        """
        return self._post('hosts_data', json={'hosts': hosts})

    def get_instances(self, group):
        """
        Get list of instances that are running on a given group.
        :param group: Gencfg group name
        """
        return self._get('searcherlookup/groups/{}/instances'.format(group))['instances']

    def get_memory_guarantee(self, group):
        """
        :param group: Gencfg group name
        :return: Memory guarantee in bytes
        """
        return self.get_group_info_card(group)['reqs']['instances']['memory_guarantee']

    def get_memory_limit(self, group):
        """
        :param group: Gencfg group name
        :return: Memory limit in bytes
        """
        reqs = self.get_group_info_card(group)['reqs']['instances']
        return reqs['memory_guarantee'] + reqs['memory_overcommit']
