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

from oslo_db import api as oslo_db_api
from oslo_db import exception as db_exc
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from sqlalchemy.orm import exc

from neutron.api.v2 import attributes
from neutron.db import api as db_api
from neutron.db import common_db_mixin
from neutron.db import models_v2
from neutron.db import tag_db as tag_model
from neutron.extensions import tag as tag_ext


LOG = logging.getLogger(__name__)


resource_model_map = {
    attributes.NETWORKS: models_v2.Network,
    # other resources can be added
}


def _extend_tags_dict(plugin, response_data, db_data):
    tags = [tag_db.tag for tag_db in db_data.standard_attr.tags]
    response_data['tags'] = tags


class TagPlugin(common_db_mixin.CommonDbMixin, tag_ext.TagPluginBase):
    """Implementation of the Neutron Tag Service Plugin."""

    supported_extension_aliases = ['tag']

    def _get_resource(self, context, resource, resource_id):
        model = resource_model_map[resource]
        try:
            return self._get_by_id(context, model, resource_id)
        except exc.NoResultFound:
            raise tag_ext.TagResourceNotFound(resource=resource,
                                              resource_id=resource_id)

    @log_helpers.log_method_call
    def get_tags(self, context, resource, resource_id):
        res = self._get_resource(context, resource, resource_id)
        tags = [tag_db.tag for tag_db in res.standard_attr.tags]
        return dict(tags=tags)

    @log_helpers.log_method_call
    def get_tag(self, context, resource, resource_id, tag):
        res = self._get_resource(context, resource, resource_id)
        if not any(tag == tag_db.tag for tag_db in res.standard_attr.tags):
            raise tag_ext.TagNotFound(tag=tag)

    @log_helpers.log_method_call
    @oslo_db_api.wrap_db_retry(
        max_retries=db_api.MAX_RETRIES,
        exception_checker=lambda e: isinstance(e, db_exc.DBDuplicateEntry))
    def update_tags(self, context, resource, resource_id, body):
        res = self._get_resource(context, resource, resource_id)
        new_tags = set(body['tags'])
        old_tags = {tag_db.tag for tag_db in res.standard_attr.tags}
        tags_added = new_tags - old_tags
        tags_removed = old_tags - new_tags
        with context.session.begin(subtransactions=True):
            for tag_db in res.standard_attr.tags:
                if tag_db.tag in tags_removed:
                    context.session.delete(tag_db)
            for tag in tags_added:
                tag_db = tag_model.Tag(standard_attr_id=res.standard_attr_id,
                                       tag=tag)
                context.session.add(tag_db)
        return body

    @log_helpers.log_method_call
    def update_tag(self, context, resource, resource_id, tag):
        res = self._get_resource(context, resource, resource_id)
        if any(tag == tag_db.tag for tag_db in res.standard_attr.tags):
            return
        try:
            with context.session.begin(subtransactions=True):
                tag_db = tag_model.Tag(standard_attr_id=res.standard_attr_id,
                                       tag=tag)
                context.session.add(tag_db)
        except db_exc.DBDuplicateEntry:
            pass

    @log_helpers.log_method_call
    def delete_tags(self, context, resource, resource_id):
        res = self._get_resource(context, resource, resource_id)
        with context.session.begin(subtransactions=True):
            query = context.session.query(tag_model.Tag)
            query = query.filter_by(standard_attr_id=res.standard_attr_id)
            query.delete()

    @log_helpers.log_method_call
    def delete_tag(self, context, resource, resource_id, tag):
        res = self._get_resource(context, resource, resource_id)
        with context.session.begin(subtransactions=True):
            query = context.session.query(tag_model.Tag)
            query = query.filter_by(tag=tag,
                                    standard_attr_id=res.standard_attr_id)
            if not query.delete():
                raise tag_ext.TagNotFound(tag=tag)

    # support only _apply_dict_extend_functions supported resources
    # at the moment.
    for resource, model in resource_model_map.items():
        common_db_mixin.CommonDbMixin.register_dict_extend_funcs(
            resource, [_extend_tags_dict])
        common_db_mixin.CommonDbMixin.register_model_query_hook(
            model, "tag", None, None,
            functools.partial(tag_model.apply_tag_filters, model))
