###############################################################################
# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems)   #
# and others                                                                  #
#                                                                             #
# All rights reserved. This program and the accompanying materials            #
# are made available under the terms of the Apache License, Version 2.0       #
# which accompanies this distribution, and is available at                    #
# http://www.apache.org/licenses/LICENSE-2.0                                  #
###############################################################################
import time

from functools import partial

from discover.events.event_base import EventBase, EventResult
from discover.events.event_port_add import EventPortAdd
from discover.events.event_subnet_add import EventSubnetAdd
from discover.fetchers.api.api_access import ApiAccess
from discover.fetchers.api.api_fetch_regions import ApiFetchRegions
from discover.fetchers.cli.cli_fetch_host_vservice import CliFetchHostVservice
from discover.find_links_for_vservice_vnics import FindLinksForVserviceVnics
from discover.scanner import Scanner
from utils.util import decode_router_id, encode_router_id


class EventInterfaceAdd(EventBase):

    def __init__(self):
        super().__init__()
        self.delay = 2

    def add_gateway_port(self, env, project, network_name, router_doc, host_id):
        fetcher = CliFetchHostVservice()
        fetcher.set_env(env)
        router_id = router_doc['id']
        router = fetcher.get_vservice(host_id, router_id)
        device_id = decode_router_id(router_id)
        router_doc['gw_port_id'] = router['gw_port_id']

        # add gateway port documents.
        port_doc = EventSubnetAdd().add_port_document(env, router_doc['gw_port_id'], project_name=project)

        mac_address = port_doc['mac_address'] if port_doc else None

        # add vnic document
        host = self.inv.get_by_id(env, host_id)

        add_vnic_document = partial(EventPortAdd().add_vnic_document,
                                    env=env,
                                    host=host,
                                    object_id=device_id,
                                    object_type='router',
                                    network_name=network_name,
                                    router_name=router_doc['name'],
                                    mac_address=mac_address)

        ret = add_vnic_document()
        if not ret:
            time.sleep(self.delay)
            self.log.info("Wait %s second, and then fetch vnic document again." % self.delay)
            add_vnic_document()

    def update_router(self, env, project, network_id, network_name, router_doc, host_id):
        if router_doc:
            if 'network' in router_doc:
                if network_id not in router_doc['network']:
                    router_doc['network'].append(network_id)
            else:
                router_doc['network'] = [network_id]

            # if gw_port_id is None, add gateway port first.
            if not router_doc.get('gw_port_id'):
                self.add_gateway_port(env, project, network_name, router_doc, host_id)
            else:
                # check the gateway port document, add it if document does not exist.
                port = self.inv.get_by_id(env, router_doc['gw_port_id'])
                if not port:
                    self.add_gateway_port(env, project, network_name, router_doc, host_id)
            self.inv.set(router_doc)
        else:
            self.log.info("router document not found, aborting interface adding")

    def handle(self, env, values):
        interface = values['payload']['router_interface']
        project = values['_context_project_name']
        host_id = values["publisher_id"].replace("network.", "", 1)
        port_id = interface['port_id']
        subnet_id = interface['subnet_id']
        router_id = encode_router_id(host_id, interface['id'])

        network_document = self.inv.get_by_field(env, "network", "subnet_ids", subnet_id, get_single=True)
        if not network_document:
            self.log.info("network document not found, aborting interface adding")
            return EventResult(result=False, retry=True)
        network_name = network_document['name']
        network_id = network_document['id']

        # add router-interface port document.
        if len(ApiAccess.regions) == 0:
            fetcher = ApiFetchRegions()
            fetcher.set_env(env)
            fetcher.get(None)
        port_doc = EventSubnetAdd().add_port_document(env, port_id, network_name=network_name)

        mac_address = port_doc['mac_address'] if port_doc else None

        # add vnic document
        host = self.inv.get_by_id(env, host_id)
        router_doc = self.inv.get_by_id(env, router_id)

        add_vnic_document = partial(EventPortAdd().add_vnic_document,
                                    env=env,
                                    host=host,
                                    object_id=interface['id'],
                                    object_type='router',
                                    network_name=network_name,
                                    router_name=router_doc['name'],
                                    mac_address=mac_address)

        ret = add_vnic_document()
        if ret is False:
            # try it again to fetch vnic document, vnic will be created a little bit late before CLI fetch.
            time.sleep(self.delay)
            self.log.info("Wait {} seconds, and then fetch vnic document again.".format(self.delay))
            add_vnic_document()

        # update the router document: gw_port_id, network.
        self.update_router(env, project, network_id, network_name, router_doc, host_id)

        # update vservice-vnic, vnic-network,
        FindLinksForVserviceVnics().add_links(search={"parent_id": router_id})
        scanner = Scanner()
        scanner.set_env(env)

        scanner.scan_cliques()
        self.log.info("Finished router-interface added.")

        return EventResult(result=True,
                           related_object=interface['id'],
                           display_context=network_id)