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

"""The security service api."""

from http import client as http_client

from oslo_log import log
import webob
from webob import exc

from manila.api import common
from manila.api.openstack import api_version_request as api_version
from manila.api.openstack import wsgi
from manila.api.views import security_service as security_service_views
from manila.common import constants
from manila import db
from manila import exception
from manila.i18n import _
from manila import policy
from manila import utils


RESOURCE_NAME = 'security_service'
LOG = log.getLogger(__name__)


class SecurityServiceController(wsgi.Controller):
    """The Shares API controller for the OpenStack API."""

    _view_builder_class = security_service_views.ViewBuilder

    def show(self, req, id):
        """Return data about the given security service."""
        context = req.environ['manila.context']
        try:
            security_service = db.security_service_get(context, id)
            policy.check_policy(context, RESOURCE_NAME, 'show',
                                security_service)
        except exception.NotFound:
            raise exc.HTTPNotFound()

        return self._view_builder.detail(req, security_service)

    def delete(self, req, id):
        """Delete a security service."""
        context = req.environ['manila.context']

        LOG.info("Delete security service with id: %s",
                 id, context=context)

        try:
            security_service = db.security_service_get(context, id)
        except exception.NotFound:
            raise exc.HTTPNotFound()

        share_nets = db.share_network_get_all_by_security_service(
            context, id)
        if share_nets:
            msg = _("Cannot delete security service. It is "
                    "assigned to share network(s)")
            raise exc.HTTPForbidden(explanation=msg)
        policy.check_policy(context, RESOURCE_NAME,
                            'delete', security_service)
        db.security_service_delete(context, id)

        return webob.Response(status_int=http_client.ACCEPTED)

    def index(self, req):
        """Returns a summary list of security services."""
        policy.check_policy(req.environ['manila.context'], RESOURCE_NAME,
                            'index')
        return self._get_security_services(req, is_detail=False)

    def detail(self, req):
        """Returns a detailed list of security services."""
        policy.check_policy(req.environ['manila.context'], RESOURCE_NAME,
                            'detail')
        return self._get_security_services(req, is_detail=True)

    def _get_security_services(self, req, is_detail):
        """Returns a transformed list of security services.

        The list gets transformed through view builder.
        """
        context = req.environ['manila.context']

        search_opts = {}
        search_opts.update(req.GET)

        # NOTE(vponomaryov): remove 'status' from search opts
        # since it was removed from security service model.
        search_opts.pop('status', None)
        if 'share_network_id' in search_opts:
            share_nw = db.share_network_get(context,
                                            search_opts['share_network_id'])
            security_services = share_nw['security_services']
            del search_opts['share_network_id']
        else:
            # ignore all_tenants if not authorized to use it.
            security_services = None
            if utils.is_all_tenants(search_opts):
                allowed_to_list_all_tenants = policy.check_policy(
                    context, RESOURCE_NAME, 'get_all_security_services',
                    do_raise=False)
                if allowed_to_list_all_tenants:
                    security_services = db.security_service_get_all(context)
            if security_services is None:
                security_services = db.security_service_get_all_by_project(
                    context, context.project_id)
        search_opts.pop('all_tenants', None)
        common.remove_invalid_options(
            context,
            search_opts,
            self._get_security_services_search_options())
        if search_opts:
            results = []
            not_found = object()
            for ss in security_services:
                if all(ss.get(opt, not_found) == value for opt, value in
                       search_opts.items()):
                    results.append(ss)
            security_services = results

        limited_list = common.limited(security_services, req)

        if is_detail:
            security_services = self._view_builder.detail_list(
                req, limited_list)
            for ss in security_services['security_services']:
                share_networks = db.share_network_get_all_by_security_service(
                    context,
                    ss['id'])
                ss['share_networks'] = [sn['id'] for sn in share_networks]
        else:
            security_services = self._view_builder.summary_list(
                req, limited_list)
        return security_services

    def _get_security_services_search_options(self):
        return ('name', 'id', 'type', 'user',
                'server', 'dns_ip', 'domain', )

    def _share_servers_dependent_on_sn_exist(self, context,
                                             security_service_id):
        share_networks = db.share_network_get_all_by_security_service(
            context, security_service_id)

        for sn in share_networks:
            for sns in sn['share_network_subnets']:
                if 'share_servers' in sns and sns['share_servers']:
                    return True
        return False

    def update(self, req, id, body):
        """Update a security service."""
        context = req.environ['manila.context']

        if not body or 'security_service' not in body:
            raise exc.HTTPUnprocessableEntity()

        security_service_data = body['security_service']
        valid_update_keys = (
            'description',
            'name'
        )

        try:
            security_service = db.security_service_get(context, id)
            policy.check_policy(context, RESOURCE_NAME, 'update',
                                security_service)
        except exception.NotFound:
            raise exc.HTTPNotFound()

        if self._share_servers_dependent_on_sn_exist(context, id):
            for item in security_service_data:
                if item not in valid_update_keys:
                    msg = _("Cannot update security service %s. It is "
                            "attached to share network with share server "
                            "associated. Only 'name' and 'description' "
                            "fields are available for update.") % id
                    raise exc.HTTPForbidden(explanation=msg)

        server = security_service_data.get('server')
        default_ad_site = security_service_data.get('default_ad_site')
        if default_ad_site:
            if req.api_version_request < api_version.APIVersionRequest("2.76"):
                msg = _('"default_ad_site" is only supported from API '
                        'version 2.76.')
                raise webob.exc.HTTPBadRequest(explanation=msg)

            if (security_service['type'] == 'active_directory' and server and
                    default_ad_site):
                raise exception.InvalidInput(
                    reason=(_("Cannot create security service because both "
                              "server and 'default_ad_site' were provided. "
                              "Specify either server or 'default_ad_site'.")))

        policy.check_policy(context, RESOURCE_NAME, 'update', security_service)
        security_service = db.security_service_update(
            context, id, security_service_data)
        return self._view_builder.detail(req, security_service)

    def create(self, req, body):
        """Creates a new security service."""
        context = req.environ['manila.context']
        policy.check_policy(context, RESOURCE_NAME, 'create')

        if not self.is_valid_body(body, 'security_service'):
            raise exc.HTTPUnprocessableEntity()

        security_service_args = body['security_service']
        security_srv_type = security_service_args.get('type')
        allowed_types = constants.SECURITY_SERVICES_ALLOWED_TYPES
        if security_srv_type not in allowed_types:
            raise exception.InvalidInput(
                reason=(_("Invalid type %(type)s specified for security "
                          "service. Valid types are %(types)s") %
                        {'type': security_srv_type,
                         'types': ','.join(allowed_types)}))
        server = security_service_args.get('server')
        default_ad_site = security_service_args.get('default_ad_site')
        if default_ad_site:
            if req.api_version_request < api_version.APIVersionRequest("2.76"):
                msg = _('"default_ad_site" is only supported from API '
                        'version 2.76.')
                raise webob.exc.HTTPBadRequest(explanation=msg)

            if (security_srv_type == 'active_directory' and server and
                    default_ad_site):
                raise exception.InvalidInput(
                    reason=(_("Cannot create security service because both "
                              "server and 'default_ad_site' were provided, "
                              "Specify either server or 'default_ad_site'.")))
        security_service_args['project_id'] = context.project_id
        security_service = db.security_service_create(
            context, security_service_args)

        return self._view_builder.detail(req, security_service)


def create_resource():
    return wsgi.Resource(SecurityServiceController())
