from __future__ import absolute_import
from builtins import str
import copy
import uuid

from vnc_api.vnc_api import *

from .agent import Agent
from cfgm_common import exceptions as vnc_exc
from cfgm_common import svc_info
from .config_db import VirtualNetworkSM, LogicalRouterSM, \
    VirtualMachineInterfaceSM, ServiceInstanceSM, ServiceTemplateSM, \
    ProjectSM, DBBaseSM
from .module_logger import ServiceMonitorModuleLogger,MessageID
from .sandesh.snat_agent import ttypes as sandesh


SNAT_SERVICE_TEMPLATE_FQ_NAME = ['default-domain', 'netns-snat-template']


class SNATAgent(Agent):

    def __init__(self, svc_mon, vnc_lib, object_db, config_section, logger):
        super(SNATAgent, self).__init__(svc_mon, vnc_lib,
                                        object_db, config_section)
        self.logger = logger

        # Register log functions to be used for SNAT Agent logs.
        log_funcs = {
                        MessageID.DEBUG : sandesh.SNATAgentDebugLog,
                    }
        self.logger.add_messages(**log_funcs)

    def audit_snat_instances(self):
        for lr in list(LogicalRouterSM.values()):
            self.update_snat_instance(lr)
        for si in list(ServiceInstanceSM.values()):
            si_name = si.fq_name[-1]
            st_obj = ServiceTemplateSM.get(si.service_template)
            if st_obj == None or \
                'service_type' not in st_obj.params or \
                st_obj.params['service_type'] != "source-nat":
                continue
            lr_uuid = si.logical_router
            lr = LogicalRouterSM.get(lr_uuid)
            if lr is None or lr.virtual_network is None:
                self.cleanup_snat_instance(lr_uuid, si.uuid)
    # end audit_snat_instances

    def handle_service_type(self):
        return svc_info.get_snat_service_type()

    def pre_create_service_vm(self, instance_index, si, st, vm):
        for nic in si.vn_info:
            nic['user-visible'] = False
        return True

    def _create_snat_vn(self, si_obj, vn_name):
        snat_subnet = svc_info.get_snat_left_subnet()
        self._svc_mon.netns_manager.create_service_vn(
            vn_name, snat_subnet, None, si_obj.fq_name[:-1],
            user_visible=False)

    def _get_snat_vn(self, si_obj):
        vn_name = '%s_%s' % (svc_info.get_snat_left_vn_prefix(),
                             si_obj.name)
        vn_fq_name = si_obj.fq_name[:-1] + [vn_name]
        try:
            self._object_db.fq_name_to_uuid('virtual-network', vn_fq_name)
        except vnc_exc.NoIdError:
            self.logger.debug("Virtual Network %s not found. Creating One." % \
                              (vn_name))
            self._create_snat_vn(si_obj, vn_name)

        return ':'.join(vn_fq_name)

    def update_snat_instance(self, router_obj):
        # Even if the router does not have internal interface
        # we instantiate the SNAT gateway stuff
        if router_obj.virtual_network:
            if router_obj.service_instance is None:
                self._add_snat_instance(router_obj)
        else:
            if router_obj.service_instance:
                self.delete_snat_instance(router_obj)

        router_obj.last_virtual_machine_interfaces = copy.copy(
            router_obj.virtual_machine_interfaces)
        return router_obj
    # end update_snat_instance

    def _get_route_table(self, router_obj):
        rt_name = 'rt_' + router_obj.uuid
        rt_fq_name = router_obj.fq_name[:-1] + [rt_name]
        try:
            return DBBaseSM().read_vnc_obj(
                obj_type="route_table", fq_name=rt_fq_name)
        except vnc_exc.NoIdError:
            self.logger.debug("Route Table %s not found." % (rt_name))
            return

    def _del_route_table(self, net_uuid, rt_obj):
        try:
            self._vnc_lib.ref_update(
                  'virtual-network', net_uuid, 'route-table',
                  None, rt_obj.get_fq_name(), 'DELETE', None)
        except vnc_exc.NoIdError:
            self.logger.debug( \
                 "Route Table %s not found. Delete route table failed." % \
                 (rt_obj.get_fq_name()))
            return

    def _set_lr_route_table(self, lr_uuid, rt_obj):
        self._vnc_lib.ref_update(
               'logical-router', lr_uuid, 'route-table',
               None, rt_obj.get_fq_name(), 'ADD', None)

    def upgrade(self, router_obj):
        rt_obj = self._get_route_table(router_obj)
        if rt_obj and not rt_obj.get_logical_router_back_refs():
            # remove route table links from all networks connected to logical router
            vn_refs = rt_obj.get_virtual_network_back_refs()
            for vn_ref in vn_refs or []:
                net_uuid = vn_ref.get('uuid')
                self._del_route_table(net_uuid, rt_obj)
            # Associate route table to logical router
            self._set_lr_route_table(router_obj.uuid, rt_obj)
    #end

    def _add_snat_instance(self, router_obj):
        try:
            vnc_rtr_obj = self._vnc_lib.logical_router_read(id=router_obj.uuid)
        except vnc_exc.NoIdError:
            self.logger.debug("Unable to find logical router for id %s. " \
                              "Add SNAT instance failed." % \
                              (router_obj.uuid))
            return

        # Get netns SNAT service template
        try:
            st_obj = self._vnc_lib.service_template_read(
                fq_name=SNAT_SERVICE_TEMPLATE_FQ_NAME)
        except vnc_exc.NoIdError:
            self.logger.debug( \
                "Unable to read service template %s. " \
                " Add SNAT instance failed." % \
                (SNAT_SERVICE_TEMPLATE_FQ_NAME))
            return

        # Get the service instance if it exists
        si_obj = None
        si_uuid = router_obj.service_instance
        if si_uuid:
            try:
                si_obj = self._vnc_lib.service_instance_read(id=si_uuid)
            except vnc_exc.NoIdError:
                self.logger.debug("Service instance %s not found." % (si_uuid))
                pass

        # Get route table for default route it it exists
        rt_obj = self._get_route_table(router_obj)

        project_fq_name = router_obj.fq_name[:-1]
        # Set the service instance
        si_created = False
        if not si_obj:
            si_name = 'snat_' + router_obj.uuid + '_' + str(uuid.uuid4())
            si_obj = ServiceInstance(si_name)
            si_obj.fq_name = project_fq_name + [si_name]
            si_created = True
        si_prop_obj = ServiceInstanceType(
            scale_out=ServiceScaleOutType(max_instances=2,
                                          auto_scale=True),
            auto_policy=False)

        # set right interface in order of [right, left] to match template
        vn_left_fq_name = self._get_snat_vn(si_obj)
        left_if = ServiceInstanceInterfaceType(virtual_network=vn_left_fq_name)
        virtual_network = router_obj.virtual_network
        vn_obj = VirtualNetworkSM.get(virtual_network)
        right_if = ServiceInstanceInterfaceType(
            virtual_network=':'.join(vn_obj.fq_name))
        si_prop_obj.set_interface_list([right_if, left_if])
        si_prop_obj.set_ha_mode('active-standby')

        si_obj.set_service_instance_properties(si_prop_obj)
        si_obj.set_service_template(st_obj)

        if si_created:
            self._vnc_lib.service_instance_create(si_obj)
        else:
            self._vnc_lib.service_instance_update(si_obj)

        # Set the route table
        route_obj = RouteType(prefix="0.0.0.0/0",
                              next_hop=si_obj.get_fq_name_str())
        rt_created = False
        if not rt_obj:
            rt_name = 'rt_' + router_obj.uuid
            rt_obj = RouteTable(name=rt_name)
            rt_obj.fq_name = project_fq_name + [rt_name]
            rt_created = True
        rt_obj.set_routes(RouteTableType.factory([route_obj]))
        if rt_created:
            self._vnc_lib.route_table_create(rt_obj)
        else:
            self._vnc_lib.route_table_update(rt_obj)

        # Associate route table to logical router
        vnc_rtr_obj.add_route_table(rt_obj)

        # Add logical gateway virtual network
        vnc_rtr_obj.set_service_instance(si_obj)
        self._vnc_lib.logical_router_update(vnc_rtr_obj)
    # end add_snat_instance

    def delete_snat_vn(self, si_obj):
        vn_name = '%s_%s' % (svc_info.get_snat_left_vn_prefix(),
                             si_obj.name)
        vn_fq_name = si_obj.fq_name[:-1] + [vn_name]
        try:
            vn_obj = self._vnc_lib.virtual_network_read(fq_name=vn_fq_name)
        except vnc_exc.NoIdError:

            self.logger.debug("Unable to find virtual network %s. " \
                              "Delete of SNAT instance %s failed." % \
                              (vn_name, si_obj.name))
            return

        vn = VirtualNetworkSM.get(vn_obj.uuid)
        if not vn:
            return

        for vmi_id in vn.virtual_machine_interfaces:
            try:
                self._vnc_lib.ref_update('virtual-machine-interface',
                    vmi_id, 'virtual-network', vn.uuid, None, 'DELETE')
            except vnc_exc.NoIdError:
                self.logger.debug( \
                    "Update of vnc lib for vmi %s virtual network %s failed" % \
                    (vmi_id, vn_name))
                pass

        for iip_id in vn.instance_ips:
            try:
                self._vnc_lib.instance_ip_delete(id=iip_id)
            except vnc_exc.NoIdError:
                self.logger.debug( \
                     "Unable to find IIP %s in virtual network %s." % \
                     (iip_id, vn_name))
                pass

        try:
            self._vnc_lib.virtual_network_delete(id=vn.uuid)
        except (vnc_exc.RefsExistError, vnc_exc.NoIdError):
            self.logger.debug("Delete of virtual network %s failed." % \
                              (vn_name))
            pass

    def delete_snat_instance(self, router_obj):
        try:
            vnc_rtr_obj = self._vnc_lib.logical_router_read(id=router_obj.uuid)
        except vnc_exc.NoIdError:
            self.logger.debug("Unable to find logical router %s." % \
                              (router_obj.uuid))
            vnc_rtr_obj = None

        # Get the service instance if it exists
        si_obj = None
        si_uuid = router_obj.service_instance
        if si_uuid:
            try:
                si_obj = self._vnc_lib.service_instance_read(id=si_uuid)
            except vnc_exc.NoIdError:
                self.logger.debug("Unable to find service instance %s. " % \
                                  (si_uuid))
                pass

        # Get route table for default route it it exists
        rt_obj = self._get_route_table(router_obj)

        if vnc_rtr_obj:
            vnc_rtr_obj.set_service_instance_list([])
            # Clear logical gateway route table
            if rt_obj:
                vnc_rtr_obj.del_route_table(rt_obj)
            try:
                self._vnc_lib.logical_router_update(vnc_rtr_obj)
            except vnc_exc.NoIdError:
                self.logger.debug( \
                     "Update of vnc lib for logical router %s failed. " % \
                     (vnc_rtr_obj.name))
                pass

        # Delete route table
        if rt_obj:
            self._vnc_lib.route_table_delete(id=rt_obj.uuid)

        # Delete service instance
        if not si_obj:
            return

        # Delete left network
        self.delete_snat_vn(si_obj)

        # Delete service instance
        self._vnc_lib.service_instance_delete(id=si_uuid)
    # end delete_snat_instance

    def cleanup_snat_instance(self, lr_id, si_id):
        # Get the service instance if it exists
        try:
            si_obj = self._vnc_lib.service_instance_read(id=si_id)
        except vnc_exc.NoIdError:
            self.logger.debug( \
                 "Service instace %s not found. SNAT cleanup failed." % \
                 (si_id))
            return

        # Delete route table
        if lr_id:
            # Disassociate route table to all private networks connected
            # onto that router
            rt_name = 'rt_' + lr_id
            rt_fq_name = si_obj.get_fq_name()[0:2] + [rt_name]
            try:
                self._vnc_lib.ref_update(
                       'logical-router', lr_id, 'route-table',
                       None, rt_fq_name, 'DELETE', None)
            except vnc_exc.NoIdError:
                self.logger.debug( \
                       "Update of logical router %s failed." % \
                       (lr_id))
                pass
            try:
                self._vnc_lib.route_table_delete(fq_name=rt_fq_name)
            except vnc_exc.NoIdError:
                self.logger.debug("Route table %s delete failed. " % \
                                  (rt_name))
                pass

        # Delete service instance
        self._vnc_lib.service_instance_delete(id=si_id)
    # end cleanup_snat_instance
