# coding: utf-8

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

import logging
from time import sleep
from concurrent.futures import ThreadPoolExecutor

from saas.library.python.puncher import PuncherApi, RuleRequest, Protocol, PortRange
from saas.library.python.puncher.errors import RuleAlreadyExists
from saas.library.python.racktables import ProjectNetworksApi
from saas.library.python.token_store import TokenStore


class BundleNetworks(object):
    LOGGER = logging.getLogger(__name__)
    DEFAULT_NETWORK_OWNERS = ['svc_saas_administration', 'svc_saas_virtual', 'coffeeman', 'i024', 'saku', 'salmin', 'saas-robot']

    @staticmethod
    def normalise_bundle_name(bundle_name):
        return bundle_name.replace('-', '_').upper()

    @staticmethod
    def get_balancer_network_name(bundle_name):
        return '_SAAS_{}_BALANCER_NETS_'.format(BundleNetworks.normalise_bundle_name(bundle_name))

    @staticmethod
    def get_proxy_network_name(bundle_name):
        return '_SAAS_{}_PROXY_NETS_'.format(BundleNetworks.normalise_bundle_name(bundle_name))

    @staticmethod
    def get_backend_network_name(bundle_name):
        return '_SAAS_{}_BASE_NETS_'.format(BundleNetworks.normalise_bundle_name(bundle_name))

    def __init__(self, racktables_token=None, puncher_token=None):
        if racktables_token is not None:
            TokenStore.add_token('racktables', racktables_token)
        if puncher_token is not None:
            TokenStore.add_token('puncher', puncher_token)

        TokenStore.add_tokens_from_env()

        if not TokenStore.has_tokens_for(['racktables', 'puncher']):
            raise RuntimeError('Not all required tokens provided. Check tokens for racktables={},puncher{}'.format(TokenStore.get_token('racktables'), TokenStore.get_token('puncher')))

        self.racktables = ProjectNetworksApi()
        self.puncher = PuncherApi()

    def wait_puncher_suggest(self, target):
        while True:
            suggests = self.puncher.suggest_destinations(target)['suggests']
            if len(suggests) > 0:
                for s in suggests:
                    if s['machine_name'] == target:
                        return True
            sleep(3)

    def wait_puncher(self, targets):
        with ThreadPoolExecutor(max_workers=2) as executor:
            target_futures = executor.map(self.wait_puncher_suggest, targets, timeout=1800)
        return target_futures

    def get_or_create_macro(self, bundle_name, network_type):
        network_name = '_SAAS_{}_{}_NETS_'.format(bundle_name, network_type)
        network_name = network_name.upper()
        macro = self.racktables.macro_exists(network_name)
        if not macro:
            self.LOGGER.info('Creating new macro %s', network_name)
            macro = self.racktables.create_macro(
                network_name,
                owner_service='svc_saas',
                owners=self.DEFAULT_NETWORK_OWNERS,
                parent='_SAAS_{}_NETS_'.format(network_type),
                description='{} saas bundle {}'.format(bundle_name, network_type.lower())
            )
        if len(macro.ids) < 1:
            self.racktables.create_network(macro.name)
        return macro

    def get_or_create_macros(self, bundle_name, balancer_required=False):
        result = {}
        network_types = ['BASE', 'PROXY']
        if balancer_required:
            network_types.append('BALANCER')

        for network_type in network_types:
            result[network_type] = self.get_or_create_macro(bundle_name, network_type)
        return result

    def create_bundle_networks(self, bundle_name, create_balancer_network=False):
        macros = self.get_or_create_macros(bundle_name, create_balancer_network)
        rule_requests = [RuleRequest(
            sources=[macros['PROXY'].name, ],
            destinations=[macros['BASE'].name, ],
            protocol=Protocol.tcp,
            ports=[PortRange(80, 82)],
            comment='SaaS bundle {} proxy access to backends'.format(bundle_name)
        )]
        if create_balancer_network:
            rule_requests.append(RuleRequest(
                sources=[macros['BALANCER'].name, ],
                destinations=[macros['PROXY'].name, ],
                protocol=Protocol.tcp,
                ports=[PortRange(80)],
                comment='SaaS bundle {} balancer to searchproxy'.format(bundle_name)
            ))

        self.LOGGER.info('Waiting for puncher suggest')
        result = self.wait_puncher([network.name for network in macros.values()])
        self.LOGGER.debug('Puncher wait result=%s', result)

        for rule_request in rule_requests:
            self.LOGGER.info('Adding puncher rule %s', rule_request)
            try:
                self.puncher.create_request(rule_request)
            except RuleAlreadyExists:
                self.LOGGER.warning('Skip rule request creation for already existing rule {} -> {}'.format(
                    rule_request.sources,
                    rule_request.destinations
                ))

        return macros

    def create_balancer_network_for_bundle(self, bundle_name):
        balancer_macro = self.get_or_create_macro(bundle_name, 'BALANCER')
        proxy_macro = self.get_or_create_macro(bundle_name, 'PROXY')
        rule_request = RuleRequest(
            sources=[balancer_macro.name, ], destinations=[proxy_macro.name, ],
            protocol=Protocol.tcp, ports=[PortRange(80)],
            comment='SaaS bundle {} balancer to searchproxy'.format(bundle_name)
        )
        self.wait_puncher([balancer_macro.name, proxy_macro.name])
        try:
            self.puncher.create_request(rule_request)
        except RuleAlreadyExists:
            self.LOGGER.warning('Skip rule request creation for already existing rule {} -> {}'.format(
                rule_request.sources,
                rule_request.destinations
            ))
        return balancer_macro.name
