# Copyright (c) 2015 Mirantis Inc.
# 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 oslo_log import log
import six

from manila.common import constants
from manila import exception
from manila.i18n import _LI

LOG = log.getLogger(__name__)


class ShareInstanceAccess(object):

    def __init__(self, db, driver):
        self.db = db
        self.driver = driver

    def update_access_rules(self, context, share_instance_id, add_rules=None,
                            delete_rules=None, share_server=None):
        """Update access rules in driver and database for given share instance.

        :param context: current context
        :param share_instance_id: Id of the share instance model
        :param add_rules: list with ShareAccessMapping models or None - rules
        which should be added
        :param delete_rules: list with ShareAccessMapping models, "all", None
        - rules which should be deleted. If "all" is provided - all rules will
        be deleted.
        :param share_server: Share server model or None
        """
        share_instance = self.db.share_instance_get(
            context, share_instance_id, with_share_data=True)

        add_rules = add_rules or []
        delete_rules = delete_rules or []
        remove_rules = None

        if six.text_type(delete_rules).lower() == "all":
            # NOTE(ganso): if we are deleting an instance or clearing all
            # the rules, we want to remove only the ones related
            # to this instance.
            delete_rules = self.db.share_access_get_all_for_instance(
                context, share_instance['id'])
            rules = []
        else:
            rules = self.db.share_access_get_all_for_share(
                context, share_instance['share_id'])
            if delete_rules:
                delete_ids = [rule['id'] for rule in delete_rules]
                rules = list(filter(lambda r: r['id'] not in delete_ids,
                                    rules))
                # NOTE(ganso): trigger maintenance mode
                if share_instance['access_rules_status'] == (
                        constants.STATUS_ERROR):
                    remove_rules = delete_rules
                    delete_rules = []

        try:
            try:
                self.driver.update_access(
                    context,
                    share_instance,
                    rules,
                    add_rules=add_rules,
                    delete_rules=delete_rules,
                    share_server=share_server
                )
            except NotImplementedError:
                # NOTE(u_glide): Fallback to legacy allow_access/deny_access
                # for drivers without update_access() method support

                self._update_access_fallback(add_rules, context, delete_rules,
                                             remove_rules, share_instance,
                                             share_server)
        except Exception as e:
            error = six.text_type(e)
            LOG.exception(error)
            self.db.share_instance_update_access_status(
                context,
                share_instance['id'],
                constants.STATUS_ERROR)
            raise exception.ManilaException(message=error)

        # NOTE(ganso): remove rules after maintenance is complete
        if remove_rules:
            delete_rules = remove_rules

        self._remove_access_rules(context, delete_rules, share_instance['id'])

        self.db.share_instance_update_access_status(
            context,
            share_instance['id'],
            constants.STATUS_ACTIVE
        )

        LOG.info(_LI("Access rules were successfully applied for "
                     "share instance: %s"),
                 share_instance['id'])

    def _update_access_fallback(self, add_rules, context, delete_rules,
                                remove_rules, share_instance, share_server):
        for rule in add_rules:
            LOG.info(
                _LI("Applying access rule '%(rule)s' for share "
                    "instance '%(instance)s'"),
                {'rule': rule['id'], 'instance': share_instance['id']}
            )

            self.driver.allow_access(
                context,
                share_instance,
                rule,
                share_server=share_server
            )

        # NOTE(ganso): Fallback mode temporary compatibility workaround
        if remove_rules:
            delete_rules = remove_rules
        for rule in delete_rules:
            LOG.info(
                _LI("Denying access rule '%(rule)s' from share "
                    "instance '%(instance)s'"),
                {'rule': rule['id'], 'instance': share_instance['id']}
            )

            self.driver.deny_access(
                context,
                share_instance,
                rule,
                share_server=share_server
            )

    def _remove_access_rules(self, context, access_rules, share_instance_id):
        if not access_rules:
            return

        for rule in access_rules:
            access_mapping = self.db.share_instance_access_get(
                context, rule['id'], share_instance_id)

            self.db.share_instance_access_delete(context, access_mapping['id'])
