# Copyright 2014, Rackspace, US, 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.
import functools
import json
import logging

from django.conf import settings
from django import http
from django.utils import decorators

from oslo_serialization import jsonutils

from horizon import exceptions

log = logging.getLogger(__name__)


class AjaxError(Exception):
    def __init__(self, http_status, msg):
        self.http_status = http_status
        super(AjaxError, self).__init__(msg)

http_errors = exceptions.UNAUTHORIZED + exceptions.NOT_FOUND + \
    exceptions.RECOVERABLE + (AjaxError, )


class CreatedResponse(http.HttpResponse):
    def __init__(self, location, data=None):
        if data is not None:
            content = jsonutils.dumps(data, sort_keys=settings.DEBUG)
            content_type = 'application/json'
        else:
            content = ''
            content_type = None
        super(CreatedResponse, self).__init__(status=201, content=content,
                                              content_type=content_type)
        self['Location'] = location


class JSONResponse(http.HttpResponse):
    def __init__(self, data, status=200):
        if status == 204:
            content = ''
        else:
            content = jsonutils.dumps(data, sort_keys=settings.DEBUG)

        super(JSONResponse, self).__init__(
            status=status,
            content=content,
            content_type='application/json',
        )


def ajax(authenticated=True, method=None):
    '''Provide a decorator to wrap a view method so that it may exist in an
    entirely AJAX environment:

    - data decoded from JSON as input and data coded as JSON as output
    - result status is coded in the HTTP status code; any non-2xx response
      data will be coded as a JSON string, otherwise the response type (always
      JSON) is specific to the method called.

    if authenticated is true then we'll make sure the current user is
    authenticated.

    If method='POST' then we'll assert that there is a JSON body
    present with the minimum attributes of "action" and "data".

    If method='PUT' then we'll assert that there is a JSON body
    present.

    The wrapped view method should return either:

    - JSON serialisable data
    - an object of the django http.HttpResponse subclass (one of JSONResponse
      or CreatedResponse is suggested)
    - nothing

    Methods returning nothing (or None explicitly) will result in a 204 "NO
    CONTENT" being returned to the caller.
    '''
    def decorator(function, authenticated=authenticated, method=method):
        @functools.wraps(function,
                         assigned=decorators.available_attrs(function))
        def _wrapped(self, request, *args, **kw):
            if authenticated and not request.user.is_authenticated():
                return JSONResponse('not logged in', 401)
            if not request.is_ajax():
                return JSONResponse('request must be AJAX', 400)

            # decode the JSON body if present
            request.DATA = None
            if request.body:
                try:
                    request.DATA = json.loads(request.body)
                except (TypeError, ValueError) as e:
                    return JSONResponse('malformed JSON request: %s' % e, 400)

            # if we're wrapping a POST action then ensure the action/data
            # expected parameters are present
            if method == 'POST':
                if not request.DATA:
                    return JSONResponse('POST requires JSON body', 400)
                if 'action' not in request.DATA or 'data' not in request.DATA:
                    return JSONResponse('POST JSON missing action/data', 400)

            # if we're wrapping a PUT action then ensure there's JSON data
            if method == 'PUT':
                if not request.DATA:
                    return JSONResponse('PUT requires JSON body', 400)

            # invoke the wrapped function, handling exceptions sanely
            try:
                data = function(self, request, *args, **kw)
                if isinstance(data, http.HttpResponse):
                    return data
                elif data is None:
                    return JSONResponse('', status=204)
                return JSONResponse(data)
            except http_errors as e:
                # exception was raised with a specific HTTP status
                if hasattr(e, 'http_status'):
                    http_status = e.http_status
                else:
                    http_status = e.code
                return JSONResponse(str(e), http_status)
            except Exception as e:
                log.exception('error invoking apiclient')
                return JSONResponse(str(e), 500)

        return _wrapped
    return decorator