# Copyright (c) 2017 Fujitsu Limited
# 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 logging
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib.db import api as db_api
from neutron_lib.services.logapi import constants as log_const

from neutron.db import db_base_plugin_common
from neutron.extensions import logging as log_ext
from neutron.objects import base as base_obj
from neutron.objects.logapi import logging_resource as log_object
from neutron.services.logapi.common import db_api as log_db_api
from neutron.services.logapi.common import exceptions as log_exc
from neutron.services.logapi.common import validators
from neutron.services.logapi.drivers import manager as driver_mgr


@registry.has_registry_receivers
class LoggingPlugin(log_ext.LoggingPluginBase):
    """Implementation of the Neutron logging api plugin."""

    supported_extension_aliases = [logging.ALIAS]

    __native_pagination_support = True
    __native_sorting_support = True
    __filter_validation_support = True

    def __init__(self):
        super().__init__()
        self.driver_manager = driver_mgr.LoggingServiceDriverManager()
        self.validator_mgr = validators.ResourceValidateRequest.get_instance()

    @property
    def supported_logging_types(self):
        # supported_logging_types are be dynamically loaded from log_drivers
        return self.driver_manager.supported_logging_types

    def start_rpc_listeners(self):
        return self.driver_manager._start_rpc_listeners()

    def _clean_logs(self, context, sg_id=None, port_id=None):
        with db_api.CONTEXT_WRITER.using(context):
            sg_logs = log_db_api.get_logs_bound_sg(
                context, sg_id=sg_id, port_id=port_id, exclusive=True)
            for log in sg_logs:
                self.delete_log(context, log['id'])

    @registry.receives(resources.SECURITY_GROUP, [events.AFTER_DELETE])
    def _clean_logs_by_resource_id(self, resource, event, trigger, payload):
        # log.resource_id == SG
        self._clean_logs(payload.context.elevated(), sg_id=payload.resource_id)

    @registry.receives(resources.PORT, [events.AFTER_DELETE])
    def _clean_logs_by_target_id(self, resource, event, trigger, payload):
        # log.target_id == port
        self._clean_logs(payload.context.elevated(),
                         port_id=payload.resource_id)

    @db_base_plugin_common.filter_fields
    @db_base_plugin_common.convert_result_to_dict
    def get_logs(self, context, filters=None, fields=None, sorts=None,
                 limit=None, marker=None, page_reverse=False):
        """Return information for available log objects"""
        filters = filters or {}
        pager = base_obj.Pager(sorts, limit, page_reverse, marker)
        return log_object.Log.get_objects(context, _pager=pager, **filters)

    def _get_log(self, context, log_id):
        """Return the log object or raise if not found"""
        log_obj = log_object.Log.get_object(context, id=log_id)
        if not log_obj:
            raise log_exc.LogResourceNotFound(log_id=log_id)
        return log_obj

    @db_base_plugin_common.filter_fields
    @db_base_plugin_common.convert_result_to_dict
    def get_log(self, context, log_id, fields=None):
        return self._get_log(context, log_id)

    @db_base_plugin_common.convert_result_to_dict
    def create_log(self, context, log):
        """Create a log object"""
        log_data = log['log']
        self.validator_mgr.validate_request(context, log_data)
        with db_api.CONTEXT_WRITER.using(context):
            # body 'log' contains both tenant_id and project_id
            # but only latter needs to be used to create Log object.
            # We need to remove redundant keyword.
            log_data.pop('tenant_id', None)
            log_obj = log_object.Log(context=context, **log_data)
            log_obj.create()
            if log_obj.enabled:
                self.driver_manager.call(
                    log_const.CREATE_LOG_PRECOMMIT, context, log_obj)
        if log_obj.enabled:
            self.driver_manager.call(
                log_const.CREATE_LOG, context, log_obj)
        return log_obj

    @db_base_plugin_common.convert_result_to_dict
    def update_log(self, context, log_id, log):
        """Update information for the specified log object"""
        log_data = log['log']
        with db_api.CONTEXT_WRITER.using(context):
            log_obj = log_object.Log(context, id=log_id)
            log_obj.update_fields(log_data, reset_changes=True)
            log_obj.update()
            need_notify = 'enabled' in log_data
            if need_notify:
                self.driver_manager.call(
                    log_const.UPDATE_LOG_PRECOMMIT, context, log_obj)
        if need_notify:
            self.driver_manager.call(
                log_const.UPDATE_LOG, context, log_obj)
        return log_obj

    def delete_log(self, context, log_id):
        """Delete the specified log object"""
        with db_api.CONTEXT_WRITER.using(context):
            log_obj = self._get_log(context, log_id)
            log_obj.delete()
            self.driver_manager.call(
                log_const.DELETE_LOG_PRECOMMIT, context, log_obj)
        self.driver_manager.call(
            log_const.DELETE_LOG, context, log_obj)

    def get_loggable_resources(self, context, filters=None, fields=None,
                               sorts=None, limit=None,
                               marker=None, page_reverse=False):
        """Get supported logging types"""
        return [{'type': type_}
                for type_ in self.supported_logging_types]
