# Copyright 2014
# The Cloudscaling Group, Inc.
#
# 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 as logging
from oslo_serialization import jsonutils
import webob

from gceapi import exception
from gceapi.i18n import _
from gceapi import wsgi_ext as openstack_wsgi

LOG = logging.getLogger(__name__)


class JSONDictSerializer(openstack_wsgi.DictSerializer):
    """JSON request body serialization."""

    def serialize(self, data, request):
        params = {'false': False, 'true': True}
        pretty_print = request.params.get("prettyPrint", True)
        if pretty_print in params:
            pretty_print = params[pretty_print]
        ident = None
        if pretty_print:
            ident = 4
        ret = jsonutils.dumps(data,
                default=jsonutils.to_primitive, indent=ident)
        return ret


class GCEResponse(openstack_wsgi.ResponseObject):
    """GCE Response body serialization."""

    def serialize(self, request, content_type, default_serializers=None):
        if self.serializer:
            serializer = self.serializer
        else:
            _mtype, _serializer = self.get_serializer(content_type,
                                                      default_serializers)
            serializer = _serializer()

        response = webob.Response()
        response.status_int = self.code
        for hdr, value in self._headers.items():
            response.headers[hdr] = value
        response.headers['Content-Type'] = content_type
        if self.obj is not None:
            response.body = serializer.serialize(self.obj, request)

        return response


class GCEFault(webob.exc.HTTPException):
    """Wrap webob.exc.HTTPException to provide API friendly response."""

    def __init__(self, exception):
        """
        Create a Fault for the given webob.exc.exception or gceapi.exception.
        """
        self.wrapped_exc = exception
        for key, value in self.wrapped_exc.headers.items():
            self.wrapped_exc.headers[key] = str(value)


class GCEResourceExceptionHandler(object):
    """Context manager to handle Resource exceptions.

    Used when processing exceptions generated by API implementation
    methods (or their extensions).  Converts most exceptions to Fault
    exceptions, with the appropriate logging.
    """

    def __enter__(self):
        return None

    def __exit__(self, ex_type, ex_value, ex_traceback):
        if not ex_value:
            return True

        if isinstance(ex_value, exception.NotAuthorized):
            msg = unicode(ex_value)
            raise GCEFault(webob.exc.HTTPForbidden(explanation=msg))
        elif isinstance(ex_value, exception.Invalid):
            msg = unicode(ex_value)
            raise GCEFault(exception.ConvertedException(
                    code=ex_value.code, explanation=msg))

        # Under python 2.6, TypeError's exception value is actually a string,
        # so test # here via ex_type instead:
        # http://bugs.python.org/issue7853
        elif issubclass(ex_type, TypeError):
            exc_info = (ex_type, ex_value, ex_traceback)
            LOG.error(_('Exception handling resource: %s') % ex_value,
                    exc_info=exc_info)
            raise GCEFault(webob.exc.HTTPBadRequest())
        elif isinstance(ex_value, GCEFault):
            LOG.info(_("Fault thrown: %s"), unicode(ex_value))
            raise ex_value
        elif isinstance(ex_value, webob.exc.HTTPException):
            LOG.info(_("HTTP exception thrown: %s"), unicode(ex_value))
            raise GCEFault(ex_value)
        elif isinstance(ex_value, exception.GceapiException):
            LOG.info(_("Gceapi exception thrown: %s"), unicode(ex_value))
            raise GCEFault(ex_value)
        else:
            msg = unicode(ex_value)
            raise GCEFault(exception.ConvertedException(
                    code=500, title=ex_type.__name__, explanation=msg))


class GCEResource(openstack_wsgi.Resource):
    """Common GCE resource response formatter"""

    def __init__(self, *args, **kwargs):
        super(GCEResource, self).__init__(*args, **kwargs)
        self.default_serializers = dict(json=JSONDictSerializer)

    def _check_requested_project(self, project_id, context):
        if (not context or project_id is None
        or (project_id not in [context.project_id, context.project_name])):
            msg = _("Project '%s' could not be found") % project_id \
                if project_id is not None \
                else _("Project hasn`t been provided")

            raise GCEFault(webob.exc.HTTPBadRequest(
                explanation=msg))

    def _process_stack(self, request, action, action_args,
                       content_type, body, accept):
        """Implement the processing stack."""
        method = None
        try:
            # Get the implementing method
            try:
                method = self.get_method(request, action, content_type, body)
            except (AttributeError, TypeError):
                msg = _("There is no such action: %s") % action
                raise GCEFault(webob.exc.HTTPNotFound(
                    explanation=msg))
            except KeyError as ex:
                msg = _("There is no such action: %s") % ex.args[0]
                raise GCEFault(webob.exc.HTTPBadRequest(
                    explanation=msg))
            except exception.MalformedRequestBody:
                msg = _("Malformed request body")
                raise GCEFault(webob.exc.HTTPBadRequest(
                    explanation=msg))

            # Now, deserialize the request body...
            try:
                if content_type:
                    contents = self.deserialize(method, content_type, body)
                else:
                    contents = {}
            except exception.InvalidContentType:
                msg = _("Unsupported Content-Type")
                raise GCEFault(webob.exc.HTTPBadRequest(
                    explanation=msg))
            except exception.MalformedRequestBody:
                msg = _("Malformed request body")
                raise GCEFault(webob.exc.HTTPBadRequest(
                    explanation=msg))

            # Update the action args
            action_args.update(contents)

            # Check project
            project_id = action_args.pop("project_id", None)
            context = request.environ.get('gceapi.context')
            action_result = self._check_requested_project(project_id, context)

            if action_result is None:
                with GCEResourceExceptionHandler():
                    action_result = self.dispatch(method, request, action_args)

        except GCEFault as ex:
            action_result = ex.wrapped_exc

        response = None
        resp_obj = None
        if (action_result is None or type(action_result) is dict or
                isinstance(action_result, Exception)):
            action_result, result_code = self.controller.process_result(
                    request, action, action_result)
            resp_obj = GCEResponse(action_result, code=result_code)
        elif isinstance(action_result, GCEResponse):
            resp_obj = action_result
        else:
            response = action_result

        # Serialize response object
        if resp_obj:
            if method is not None:
                serializers = getattr(method, 'wsgi_serializers', {})
            else:
                serializers = {}
            resp_obj._bind_method_serializers(serializers)
            if method is not None and hasattr(method, 'wsgi_code'):
                resp_obj._default_code = method.wsgi_code
            resp_obj.preserialize(accept, self.default_serializers)
            response = resp_obj.serialize(request, accept,
                                          self.default_serializers)

        try:
            msg_dict = dict(url=request.url, status=response.status_int)
            msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
        except AttributeError as e:
            msg_dict = dict(url=request.url, e=e)
            msg = _("%(url)s returned a fault: %(e)s") % msg_dict

        LOG.info(msg)
        return response