# Copyright 2018 OpenStack Foundation
# Copyright 2017 Letv Cloud Computing
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#

from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.api.definitions import qos_gateway_ip
from neutron_lib.api import extensions
from neutron_lib.db import api as db_api
from neutron_lib.db import resource_extend
from neutron_lib.services.qos import constants as qos_consts
from oslo_log import log as logging

from neutron.db import l3_db
from neutron.db import l3_gwmode_db
from neutron.objects.qos import policy as policy_object

LOG = logging.getLogger(__name__)


@resource_extend.has_resource_extenders
class L3_gw_ip_qos_dbonly_mixin(l3_gwmode_db.L3_NAT_dbonly_mixin):
    """Mixin class to add router gateway IP's QoS extra attributes."""

    _gw_ip_qos = None

    @staticmethod
    @resource_extend.extends([l3_apidef.ROUTERS])
    def _extend_router_dict_gw_qos(router_res, router_db):
        if router_db.gw_port and (
                router_db.qos_policy_binding or
                router_db.gw_port.qos_network_policy_binding):
            qos_bind = router_db.qos_policy_binding
            qos_net_bind = router_db.gw_port.qos_network_policy_binding
            policy_id = qos_bind.policy_id if qos_bind else None
            net_policy_id = qos_net_bind.policy_id if qos_net_bind else None
            router_res[l3_apidef.EXTERNAL_GW_INFO].update({
                qos_consts.QOS_POLICY_ID: policy_id})
            router_res[qos_consts.QOS_POLICY_ID] = policy_id
            router_res[qos_consts.QOS_NETWORK_POLICY_ID] = net_policy_id

    @property
    def _is_gw_ip_qos_supported(self):
        if self._gw_ip_qos is None:
            # Check L3 service plugin
            self._gw_ip_qos = extensions.is_extension_supported(
                self, qos_gateway_ip.ALIAS)
        return self._gw_ip_qos

    def _create_gw_ip_qos_db(self, context, router_id, policy_id):
        policy = policy_object.QosPolicy.get_policy_obj(context, policy_id)
        policy.attach_router(router_id)

    def _delete_gw_ip_qos_db(self, context, router_id, policy_id):
        policy = policy_object.QosPolicy.get_policy_obj(context, policy_id)
        policy.detach_router(router_id, if_exists=True)

    def _update_router_gw_info(self, context, router_id, info,
                               request_body, router=None):
        # Calls superclass, pass router db object for avoiding re-loading
        router = super()._update_router_gw_info(
                           context, router_id, info, request_body, router)

        if not self._is_gw_ip_qos_supported:
            return router

        with db_api.CONTEXT_WRITER.using(context):
            if not router.gw_port:
                info = {qos_consts.QOS_POLICY_ID: None}
            self._update_router_gw_qos_policy(context, router_id, info, router)

        return router

    def _get_router_gateway_policy_binding(self, context, router_id):
        router = self._get_router(context, router_id)
        return router.qos_policy_binding

    def _update_router_gw_qos_policy(self, context, router_id, info, router):
        if not info or qos_consts.QOS_POLICY_ID not in info:
            # An explicit 'None' for `qos_policy_id` indicates to clear
            # the router gateway IP policy. So if info does not have
            # the key `qos_policy_id`, we can not decide what behavior
            # to be done, then directly return here.
            return

        new_qos_policy_id = info[qos_consts.QOS_POLICY_ID]
        if router.qos_policy_binding:
            old_qos_policy_id = router.qos_policy_binding.policy_id

            if old_qos_policy_id == new_qos_policy_id:
                return
            if old_qos_policy_id:
                self._delete_gw_ip_qos_db(context,
                                          router_id,
                                          old_qos_policy_id)

        if new_qos_policy_id:
            self._create_gw_ip_qos_db(
                context, router_id, new_qos_policy_id)

    def _build_routers_list(self, context, routers, gw_ports):
        routers = super()._build_routers_list(
                            context, routers, gw_ports)

        if not self._is_gw_ip_qos_supported:
            return routers

        for rtr in routers:
            gw_port_id = rtr['gw_port_id']
            # Collect gw ports only if available
            if gw_port_id and gw_ports.get(gw_port_id):
                rtr['gw_port'] = gw_ports[gw_port_id]
                router_gateway_policy_binding = (
                    self._get_router_gateway_policy_binding(
                        context, rtr['id']))
                qos_policy_id = None
                if router_gateway_policy_binding:
                    qos_policy_id = router_gateway_policy_binding.policy_id
                rtr['gw_port'][qos_consts.QOS_POLICY_ID] = qos_policy_id
        return routers


class L3_gw_ip_qos_db_mixin(L3_gw_ip_qos_dbonly_mixin,
                            l3_db.L3_NAT_db_mixin):
    pass
