from infra.awacs.proto import api_pb2, api_stub
from saas.library.python.nanny_proto.rpc_client_base import NannyRpcClientBase
from saas.library.python.deploy_manager_api.client import DeployManagerApiClient
from google.protobuf import json_format
import nanny_rpc_client
from saas.library.python.awacs import helpers
import logging
from saas.library.python.awacs.configs import ConfigManager


class UpstreamManager(NannyRpcClientBase):
    LOGGER = logging.getLogger(__name__)
    _RPC_URL = "https://awacs.yandex-team.ru/api/"
    _OAUTH_SLUG = 'awacs'
    _API_STUB = api_stub.UpstreamServiceStub
    _API_DM = None

    @classmethod
    def _init_dm_client(cls):
        if cls._API_DM is None:
            cls._API_DM = DeployManagerApiClient()

    def __init__(self):
        super(NannyRpcClientBase, self).__init__()
        self._init_client()
        self._init_dm_client()

    def create_upstream(self, ctype, *args, **kwargs):
        kwargs['namespace_id'] = helpers.namespace_id_from_ctype(ctype)
        required_pb = api_pb2.CreateUpstreamRequest()
        config_manager = ConfigManager()
        request_data = config_manager.render_upstream_config(*args, **kwargs)
        json_format.ParseDict(request_data, required_pb)
        self._CLIENT.create_upstream(required_pb)

    def select_label(self, ctype):
        upstreams = self.list_upstreams(helpers.namespace_id_from_ctype(ctype))
        numbers = set([el.spec.labels['order'] for el in upstreams])
        for num in range(1, len(numbers) + 2):
            if "%08d" % num not in numbers:
                return num

    @staticmethod
    def get_backends_by_dc(backends):
        dcs = set([backend.split('_')[-1] for backend in backends])
        backends_by_dc = {dc: [] for dc in dcs}
        for backend in backends:
            backends_by_dc[backend.split('_')[-1]].append(backend)
        return backends_by_dc

    def get_default_upstream(self, ctype):
        return self.get_upstream('default', helpers.namespace_id_from_ctype(ctype))

    def get_upstream(self, ctype, upstream_id):
        req_pb = api_pb2.GetUpstreamRequest()
        req_pb.id = upstream_id
        req_pb.namespace_id = helpers.namespace_id_from_ctype(ctype)
        try:
            return self._CLIENT.get_upstream(req_pb).upstream
        except nanny_rpc_client.exceptions.NotFoundError:
            return None

    def list_upstreams(self, ctype):
        req_pb = api_pb2.ListUpstreamsRequest()
        req_pb.namespace_id = helpers.namespace_id_from_ctype(ctype)
        return self._CLIENT.list_upstreams(req_pb).upstreams

    def remove_upstream(self, ctype, upstream_id):
        namespace_id = helpers.namespace_id_from_ctype(ctype)
        if helpers.mean_rps_last_30m(report=upstream_id, prj=namespace_id, ctype='prod', itype='balancer') == 0:
            return self._remove_upstream(ctype, upstream_id)
        self.LOGGER.error("Can't remove upstream with none zero traffic")
        return None

    def _remove_upstream(self, ctype, upstream_id):
        req_pb = api_pb2.RemoveUpstreamRequest()
        req_pb.id = upstream_id
        req_pb.namespace_id = helpers.namespace_id_from_ctype(ctype)
        req_pb.version = self.get_upstream(ctype, upstream_id).meta.version
        return self._CLIENT.remove_upstream(req_pb)

    def label_for_new_upstream(self, ctype):
        arr = [int(el.spec.labels['order'][-5:]) for el in self.list_upstreams(ctype) if not int(el.spec.labels['order'])//10000]
        if arr:
            return str(max(arr)+1).zfill(8)
        else:
            return str(1).zfill(8)

    def set_common_upstream_for_service(self, service, ctype, timeout_from_sla=False):
        common_label = self.label_for_new_upstream("common")
        common_matcher = {"and_": [{"host_re": "{}(:[0-9]+)?".format(helpers.namespace_id_from_ctype(ctype))},
                                   {"or_": [{"path_re": "/{}(/.*)?".format(service)},
                                            {"cgi_re": '(.*&)?service={}(&.*)?'.format(service)}]}]}
        sla_q999_timeout = self._API_DM.get_sla(ctype, service).get('search_q_999_ms')
        upstream_id = "{}-{}".format(ctype, service)
        if self.get_upstream("common", upstream_id):
            self.LOGGER.warning("Can't create upstream due to upstream exists yet")
            return True
        monitoring_name = "requests_{}_to_{}_{{}}".format(ctype, service)

        common_namespace_ctype_upstream = json_format.MessageToDict(self.get_upstream("common", ctype))
        meta, spec = common_namespace_ctype_upstream["meta"], common_namespace_ctype_upstream["spec"]
        if not spec['yandexBalancer']['config'].get('l7UpstreamMacro'):
            self.LOGGER.warning("Can't create upstream due to ctype upstream written not in l7UpstreamMacro")
            return False
        meta["id"] = upstream_id
        spec['yandexBalancer']['config']['l7UpstreamMacro']['id'] = upstream_id
        spec['labels']['order'] = common_label
        spec['yandexBalancer']['config']['l7UpstreamMacro']['matcher'] = common_matcher
        if spec['yandexBalancer']['config']['l7UpstreamMacro'].get("canHandleAnnounceChecks"):
            del spec['yandexBalancer']['config']['l7UpstreamMacro']["canHandleAnnounceChecks"]
        if spec['yandexBalancer']['config']['l7UpstreamMacro'].get("byDcScheme"):
            for dc_config in spec['yandexBalancer']['config']['l7UpstreamMacro']['byDcScheme']['dcs']:
                dc_config['monitoring']['uuid'] = monitoring_name.format(dc_config['name'])
        if sla_q999_timeout and timeout_from_sla:
            spec['yandexBalancer']['config']['l7UpstreamMacro']['byDcScheme']['balancer']['backendTimeout'] = "{}ms".format(
                sla_q999_timeout)
        if spec['yandexBalancer'].get('yaml'):
            del spec['yandexBalancer']['yaml']
        req_pb = json_format.ParseDict({"meta": meta, "spec": spec}, api_pb2.CreateUpstreamRequest())
        self.LOGGER.info("Creating upstream in common balancer for service {} in ctype {}".format(service, ctype))
        try:
            self._CLIENT.create_upstream(req_pb)
        except Exception as e:
            self.LOGGER.exception(e)
            return False
        return True

    def set_separated_upstream_for_service(self, service, ctype, timeout_from_sla=False):
        separated_label = self.label_for_new_upstream(ctype)
        separated_matcher = {"or_": [{"path_re": "/{}(/.*)?".format(service)},
                                     {"cgi_re": '(.*&)?service={}(&.*)?'.format(service)}]}
        sla_q999_timeout = self._API_DM.get_sla(ctype, service).get('search_q_999_ms')
        upstream_id = "{}".format(service)
        if self.get_upstream(ctype, upstream_id):
            self.LOGGER.warning("Can't create upstream due to upstream exists yet")
            return True
        monitoring_name = "requests_{}_to_{}_{{}}".format(ctype, service)

        separated_namespace_ctype_upstream = json_format.MessageToDict(self.get_upstream(ctype, "default"))
        meta, spec = separated_namespace_ctype_upstream["meta"], separated_namespace_ctype_upstream["spec"]
        if not spec['yandexBalancer']['config'].get('l7UpstreamMacro'):
            self.LOGGER.warning("Can't create upstream due to ctype upstream written not in l7UpstreamMacro")
            return False
        meta["id"] = upstream_id
        spec['yandexBalancer']['config']['l7UpstreamMacro']['id'] = upstream_id
        spec['labels']['order'] = separated_label
        spec['yandexBalancer']['config']['l7UpstreamMacro']['matcher'] = separated_matcher
        if spec['yandexBalancer']['config']['l7UpstreamMacro'].get("canHandleAnnounceChecks"):
            del spec['yandexBalancer']['config']['l7UpstreamMacro']["canHandleAnnounceChecks"]
        if spec['yandexBalancer']['config']['l7UpstreamMacro'].get("byDcScheme"):
            for dc_config in spec['yandexBalancer']['config']['l7UpstreamMacro']['byDcScheme']['dcs']:
                dc_config['monitoring']['uuid'] = monitoring_name.format(dc_config['name'])
        if sla_q999_timeout and timeout_from_sla:
            spec['yandexBalancer']['config']['l7UpstreamMacro']['byDcScheme']['balancer']['backendTimeout'] = "{}ms".format(
                sla_q999_timeout)
        if spec['yandexBalancer'].get('yaml'):
            del spec['yandexBalancer']['yaml']
        req_pb = json_format.ParseDict({"meta": meta, "spec": spec}, api_pb2.CreateUpstreamRequest())
        self.LOGGER.info("Creating upstream in separated balancer for service {} in ctype {}".format(service, ctype))
        try:
            self._CLIENT.create_upstream(req_pb)
        except Exception as e:
            self.LOGGER.exception(e)
            return False
        return True
