#    Copyright (c) 2015, Telefonica I+D.
#
#    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_db import exception as db_exc
from oslo_log import log as logging
import six
from webob import exc

from murano.api.v1 import request_statistics
from murano.common.i18n import _, _LE
from murano.common import policy
from murano.common import utils
from murano.common import wsgi
from murano.db import models
from murano.db.services import core_services
from murano.db.services import environment_templates as env_temps
from murano.db.services import environments as envs
from murano.db.services import sessions

LOG = logging.getLogger(__name__)

API_NAME = 'Templates'


class Controller(object):
    @request_statistics.stats_count(API_NAME, 'Index')
    def index(self, request):
        """Lists the env templates associated to an tenant-id

        It lists the env templates associated to an tenant-id.
        :param request: The operation request.
        :return: the env template description list.
        """
        LOG.debug('EnvTemplates:List')
        policy.check('list_env_templates', request.context)
        tenant_id = request.context.tenant
        filters = {}
        if request.GET.get('is_public'):
            is_public = request.GET.get('is_public', 'false').lower() == 'true'
            if not is_public:

                filters['is_public'] = False
                filters['tenant_id'] = tenant_id
            elif is_public:
                filters['is_public'] = True

            list_templates = env_temps.EnvTemplateServices.\
                get_env_templates_by(filters)

        else:
            filters = (models.EnvironmentTemplate.is_public,
                       models.EnvironmentTemplate.tenant_id == tenant_id)
            list_templates = env_temps.EnvTemplateServices.\
                get_env_templates_or_by(filters)

        list_templates = [temp.to_dict() for temp in list_templates]
        return {"templates": list_templates}

    @request_statistics.stats_count(API_NAME, 'Create')
    def create(self, request, body):
        """Creates the env template from the payload

        This payload can contain just the template name, or include
        also service information.
        :param request: the operation request.
        :param body: the env template description
        :return: the description of the created template.
        """
        LOG.debug('EnvTemplates:Create <Body {body}>'.format(body=body))
        policy.check('create_env_template', request.context)

        self._validate_body_name(body)
        try:
            LOG.debug('ENV TEMP NAME: {templ_name}>'.
                      format(templ_name=body['name']))
            template = env_temps.EnvTemplateServices.create(
                body.copy(), request.context.tenant)
            return template.to_dict()
        except db_exc.DBDuplicateEntry:
            msg = _('Env Template with specified name already exists')
            LOG.exception(msg)
            raise exc.HTTPConflict(msg)

    @request_statistics.stats_count(API_NAME, 'Show')
    def show(self, request, env_template_id):
        """It shows the description of a template

        :param request: the operation request.
        :param env_template_id: the env template ID.
        :return: the description of the env template.
        """
        LOG.debug('Templates:Show <Id: {templ_id}>'.format(
            templ_id=env_template_id))
        target = {"env_template_id": env_template_id}
        policy.check('show_env_template', request.context, target)

        self._validate_request(request, env_template_id)

        template = env_temps.EnvTemplateServices.\
            get_env_template(env_template_id)
        temp = template.to_dict()

        get_data = core_services.CoreServices.get_template_data
        temp['services'] = get_data(env_template_id, '/services')
        return temp

    @request_statistics.stats_count(API_NAME, 'Update')
    def update(self, request, env_template_id, body):
        """It updates the description template

        :param request: the operation request.
        :param env_template_id: the env template ID.
        :param body: the description to be updated
        :return: the updated template description.
        """
        LOG.debug('Templates:Update <Id: {templ_id}, '
                  'Body: {body}>'.format(templ_id=env_template_id, body=body))
        target = {"env_template_id": env_template_id}
        policy.check('update_env_template', request.context, target)

        self._validate_request(request, env_template_id)
        try:
            LOG.debug('ENV TEMP NAME: {temp_name}>'.format(
                temp_name=body['name']))
            if not str(body['name']).strip():
                msg = _('Environment Template must contain at least one '
                        'non-white space symbol')
                LOG.exception(msg)
                raise exc.HTTPBadRequest(msg)
        except Exception:
                msg = _('EnvTemplate body is incorrect')
                LOG.exception(msg)
                raise exc.HTTPBadRequest(msg)

        template = env_temps.EnvTemplateServices.update(env_template_id, body)
        return template.to_dict()

    @request_statistics.stats_count(API_NAME, 'Delete')
    def delete(self, request, env_template_id):
        """It deletes the env template

        :param request: the operation request.
        :param env_template_id: the template ID.
        """
        LOG.debug('EnvTemplates:Delete <Id: {templ_id}>'.format(
            templ_id=env_template_id))
        target = {"env_template_id": env_template_id}
        policy.check('delete_env_template', request.context, target)
        self._validate_request(request, env_template_id)
        env_temps.EnvTemplateServices.delete(env_template_id)
        env_temps.EnvTemplateServices.remove(env_template_id)
        return

    def has_services(self, template):
        """"It checks if the template has services

        :param template: the template to check.
        :return: True or False
        """
        if not template.description:
            return False

        if (template.description.get('services')):
            return True
        return False

    @request_statistics.stats_count(API_NAME, 'Create_environment')
    def create_environment(self, request, env_template_id, body):
        """Creates environment and session from template

        :param request: operation request
        :param env_template_id: environment template ID
        :param body: the environment name
        :return: session_id and environment_id
        """
        target = {"env_template_id": env_template_id}
        policy.check('create_environment', request.context, target)

        self._validate_request(request, env_template_id)
        LOG.debug('Templates:Create environment <Id: {templ_id}>'.
                  format(templ_id=env_template_id))
        template = env_temps.EnvTemplateServices.\
            get_env_template(env_template_id)
        self._validate_body_name(body)
        try:
            environment = envs.EnvironmentServices.create(
                body.copy(), request.context)
        except db_exc.DBDuplicateEntry:
            msg = _('Environment with specified name already exists')
            LOG.exception(msg)
            raise exc.HTTPConflict(explanation=msg)

        user_id = request.context.user
        session = sessions.SessionServices.create(environment.id, user_id)

        if self.has_services(template):
            services_node = utils.TraverseHelper.get("services",
                                                     template.description)
            utils.TraverseHelper.update("/Objects/services",
                                        services_node,
                                        environment.description)

        envs.EnvironmentServices.save_environment_description(
            session.id,
            environment.description,
            inner=False
        )
        return {"session_id": session.id, "environment_id": environment.id}

    @request_statistics.stats_count(API_NAME, 'Clone')
    def clone(self, request, env_template_id, body):
        """Clones env template from another tenant

        It clones the env template from another env template
        from other tenant.
        :param request: the operation request.
        :param env_template_id: the env template ID.
        :param body: the request body.
        :return: the description of the created template.
        """

        LOG.debug('EnvTemplates:Clone <Env Template {0} for body {1}>'.
                  format(body, env_template_id))
        policy.check('clone_env_template', request.context)

        old_env_template = self._validate_exists(env_template_id)

        if not old_env_template.get('is_public'):
            msg = _LE('User has no access to these resources.')
            LOG.error(msg)
            raise exc.HTTPForbidden(explanation=msg)
        self._validate_body_name(body)
        LOG.debug('ENV TEMP NAME: {0}'.format(body['name']))

        try:
            is_public = body.get('is_public', False)
            template = env_temps.EnvTemplateServices.clone(
                env_template_id, request.context.tenant, body['name'],
                is_public)
        except db_exc.DBDuplicateEntry:
            msg = _('Environment with specified name already exists')
            LOG.error(msg)
            raise exc.HTTPConflict(explanation=msg)

        return template.to_dict()

    def _validate_request(self, request, env_template_id):
        self._validate_exists(env_template_id)
        get_env_template = env_temps.EnvTemplateServices.get_env_template
        env_template = get_env_template(env_template_id)
        if env_template.is_public or request.context.is_admin:
            return
        if env_template.tenant_id != request.context.tenant:
            msg = _LE('User has no access to these resources.')
            LOG.error(msg)
            raise exc.HTTPForbidden(explanation=msg)

    def _validate_exists(self, env_template_id):
        env_template_exists = env_temps.EnvTemplateServices.env_template_exist
        if not env_template_exists(env_template_id):
            msg = _('EnvTemplate <TempId {temp_id}> is not found').format(
                temp_id=env_template_id)
            LOG.error(msg)
            raise exc.HTTPNotFound(explanation=msg)
        get_env_template = env_temps.EnvTemplateServices.get_env_template
        return get_env_template(env_template_id)

    def _validate_body_name(self, body):

        if not('name' in body and body['name'].strip()):
            msg = _('Please, specify a name of the environment template.')
            LOG.error(msg)
            raise exc.HTTPBadRequest(explanation=msg)

        name = six.text_type(body['name'])
        if len(name) > 255:
            msg = _('Environment template name should be 255 characters '
                    'maximum')
            LOG.error(msg)
            raise exc.HTTPBadRequest(explanation=msg)


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