# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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.

"""
Server-side (i.e. worker side) Keystone notification related classes and logic.
"""
import oslo_messaging
from oslo_service import service

from barbican.common import utils
from barbican.model import repositories
from barbican import queue
from barbican.tasks import keystone_consumer


LOG = utils.getLogger(__name__)


class NotificationTask(object):
    """Task which exposes the API for consuming priority based notifications.

    The Oslo notification framework delivers notifications based on priority to
    matching callback APIs as defined in its notification listener endpoint
    list.

    Currently from Keystone perspective, `info` API is sufficient as Keystone
    send notifications at `info` priority ONLY. Other priority level APIs
    (warn, error, critical, audit, debug) are not needed here.
    """

    def __init__(self, conf):
        self.conf = conf
        repositories.setup_database_engine_and_factory()

    def info(self, ctxt, publisher_id, event_type, payload, metadata):
        """Receives notification at info level."""
        return self.process_event(ctxt, publisher_id, event_type, payload,
                                  metadata)

    def process_event(self, ctxt, publisher_id, event_type, payload, metadata):
        """Process Keystone Event based on event_type and payload data.

        Parses notification data to identify if the event is related to delete
        project or not. In case of delete project event, it passes project_id
        to KeystoneEventConsumer logic for further processing. Barbican service
        is not interested in other events so in that case it just returns None
        as acknowledgment.

        Messaging server considers message is acknowledged when either return
        value is `oslo_messaging.NotificationResult.HANDLED` or None.

        In case of successful processing of notification, the returned value is
        `oslo_messaging.NotificationResult.HANDLED`

        In case of notification processing error, the value returned
        is oslo_messaging.NotificationResult.REQUEUE when transport
        supports this feature otherwise
        `oslo_messaging.NotificationResult.HANDLED` is returned.

        """

        LOG.debug("Input keystone event publisher_id = %s", publisher_id)
        LOG.debug("Input keystone event payload = %s", payload)
        LOG.debug("Input keystone event type = %s", event_type)
        LOG.debug("Input keystone event metadata = %s", metadata)
        project_id = self._parse_payload_for_project_id(payload)
        resource_type, operation_type = self._parse_event_type(event_type)
        LOG.debug('Keystone Event: resource type={0}, operation type={1}, '
                  'keystone id={2}'.format(resource_type, operation_type,
                                           project_id))

        if (project_id and resource_type == 'project' and
                operation_type == 'deleted'):

            task = keystone_consumer.KeystoneEventConsumer()
            try:
                task.process(project_id=project_id,
                             resource_type=resource_type,
                             operation_type=operation_type)
                return oslo_messaging.NotificationResult.HANDLED
            except Exception:
                # No need to log message here as task process method has
                # already logged it
                # TODO(john-wood-w) This really should be retried on a
                #   schedule and really only if the database is down, not
                #   for any exception otherwise tasks will be re-queued
                #   repeatedly. Revisit as part of the retry task work later.
                if self.conf.keystone_notifications.allow_requeue:
                    return oslo_messaging.NotificationResult.REQUEUE
                else:
                    return oslo_messaging.NotificationResult.HANDLED
        return None  # in case event is not project delete

    def _parse_event_type(self, event_type):
        """Parses event type provided as part of notification.

        Parses to identify what operation is performed and on which Keystone
        resource.

        A few event type sample values are provided below::
            identity.project.deleted
            identity.role.created
            identity.domain.updated
            identity.authenticate
        """
        resource_type = None
        operation_type = None
        if event_type:
            type_list = event_type.split('.')
            # 2 is min. number of dot delimiters expected in event_type value.
            if len(type_list) > 2:
                resource_type = type_list[-2].lower()
                operation_type = type_list[-1].lower()

        return resource_type, operation_type

    def _parse_payload_for_project_id(self, payload_s):
        """Gets project resource identifier from payload

        Sample payload is provided below::
            {'resource_info': u'2b99a94ad02741978e613fb52dd1f4cd'}
        """
        if payload_s:
            return payload_s.get('resource_info')


class MessageServer(NotificationTask, service.Service):
    """Server to retrieve messages from queue used by Keystone.

    This is used to send public notifications for openstack service
    consumption.

    This server is an Oslo notification server that exposes set of standard
    APIs for events consumption based on event priority.

    Some of messaging server configuration needs to match with Keystone
    deployment notification configuration e.g. exchange name, topic name
    """
    def __init__(self, conf):
        pool_size = conf.keystone_notifications.thread_pool_size
        NotificationTask.__init__(self, conf)
        service.Service.__init__(self, threads=pool_size)

        self.target = queue.get_notification_target()
        self._msg_server = queue.get_notification_server(targets=[self.target],
                                                         endpoints=[self])

    def start(self):
        self._msg_server.start()
        super(MessageServer, self).start()

    def stop(self):
        super(MessageServer, self).stop()
        self._msg_server.stop()
        queue.cleanup()
