#
#    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.
#

import abc
import six

from oslo_utils import timeutils

from networking_ovn.common import constants as ovn_const
from networking_ovn.common import utils as ovn_utils


@six.add_metaclass(abc.ABCMeta)
class NeutronAgent(object):
    types = {}

    def __init_subclass__(cls):
        # Register the subclasses to be looked up by their type
        for _type in cls.types:
            NeutronAgent.types[_type] = cls

    def __init__(self, chassis_private):
        self.chassis_private = chassis_private
        try:
            self.chassis = self.chassis_private.chassis[0]
        except (AttributeError, IndexError):
            # No Chassis_Private support, just use Chassis
            self.chassis = self.chassis_private

    @property
    def updated_at(self):
        try:
            return timeutils.parse_isotime(self.chassis.external_ids[self.key])
        except KeyError:
            return timeutils.utcnow(with_timezone=True)

    def as_dict(self, alive):
        return {
            'binary': self.binary,
            'host': self.chassis.hostname,
            'heartbeat_timestamp': timeutils.utcnow(),
            'availability_zone': ', '.join(
                ovn_utils.get_chassis_availability_zones(self.chassis)),
            'topic': 'n/a',
            'description': self.description,
            'configurations': {
                'chassis_name': self.chassis.name,
                'bridge-mappings':
                    self.chassis.external_ids.get('ovn-bridge-mappings', '')},
            'start_flag': True,
            'agent_type': self.agent_type,
            'id': self.agent_id,
            'alive': alive,
            'admin_state_up': True}

    @classmethod
    def from_type(cls, _type, chassis_private):
        return cls.types[_type](chassis_private)

    @staticmethod
    def agent_types():
        return NeutronAgent.__subclasses__()

    @property
    @abc.abstractmethod
    def agent_type(self):
        pass


class ControllerAgent(NeutronAgent):
    types = [ovn_const.OVN_CONTROLLER_AGENT, ovn_const.OVN_CONTROLLER_GW_AGENT]
    binary = 'ovn-controller'
    key = ovn_const.OVN_LIVENESS_CHECK_EXT_ID_KEY

    @property
    def agent_type(self):
        if ('enable-chassis-as-gw' in
                self.chassis.external_ids.get('ovn-cms-options', [])):
            return ovn_const.OVN_CONTROLLER_GW_AGENT
        return ovn_const.OVN_CONTROLLER_AGENT

    @property
    def nb_cfg(self):
        return self.chassis_private.nb_cfg

    @property
    def agent_id(self):
        return self.chassis_private.name

    @property
    def description(self):
        return self.chassis_private.external_ids.get(
            ovn_const.OVN_AGENT_DESC_KEY, '')


class MetadataAgent(NeutronAgent):
    agent_type = ovn_const.OVN_METADATA_AGENT
    types = [agent_type]
    binary = 'networking-ovn-metadata-agent'
    key = ovn_const.METADATA_LIVENESS_CHECK_EXT_ID_KEY

    @property
    def nb_cfg(self):
        return int(self.chassis_private.external_ids.get(
            ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY, 0))

    @property
    def agent_id(self):
        return self.chassis_private.external_ids.get(
            ovn_const.OVN_AGENT_METADATA_ID_KEY)

    @property
    def description(self):
        return self.chassis_private.external_ids.get(
            ovn_const.OVN_AGENT_METADATA_DESC_KEY, '')


# Note(lucasagomes): The code below  is only present in the stable/train
# backport in order to add support for python 2.7.
if six.PY2:
    NeutronAgent.types = {
        ovn_const.OVN_CONTROLLER_AGENT: ControllerAgent,
        ovn_const.OVN_CONTROLLER_GW_AGENT: ControllerAgent,
        ovn_const.OVN_METADATA_AGENT: MetadataAgent}
