# Copyright 2015, 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.
"""API for the swift service."""

import os
from urllib import parse

from django import forms
from django.http import StreamingHttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views import generic

from horizon import exceptions
from openstack_dashboard import api
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
from openstack_dashboard.api import swift


@urls.register
class Info(generic.View):
    """API for information about the Swift installation."""
    url_regex = r'swift/info/$'

    @rest_utils.ajax()
    def get(self, request):
        """Get information about the Swift installation."""
        capabilities = api.swift.swift_get_capabilities(request)
        return {'info': capabilities}


@urls.register
class Policies(generic.View):
    """API for information about available container storage policies"""
    url_regex = r'swift/policies/$'

    @rest_utils.ajax()
    def get(self, request):
        """List available container storage policies"""

        capabilities = api.swift.swift_get_capabilities(request)
        policies = capabilities['swift']['policies']

        for policy in policies:
            display_name = \
                api.swift.get_storage_policy_display_name(policy['name'])
            if display_name:
                policy["display_name"] = display_name

        return {'policies': policies}


@urls.register
class Containers(generic.View):
    """API for swift container listing for an account"""
    url_regex = r'swift/containers/$'

    @rest_utils.ajax()
    def get(self, request):
        """Get the list of containers for this account

        :param prefix: container name prefix value. Named items in the
        response begin with this value

        TODO(neillc): Add pagination
        """
        prefix = request.GET.get('prefix', None)
        if prefix:
            containers, has_more = api.swift.\
                swift_get_containers(request, prefix=prefix)
        else:
            containers, has_more = api.swift.swift_get_containers(request)

        containers = [container.to_dict() for container in containers]
        return {'items': containers, 'has_more': has_more}


@urls.register
class Container(generic.View):
    """API for swift container level information"""

    url_regex = r'swift/containers/(?P<container>[^/]+)/metadata/$'

    @rest_utils.ajax()
    def get(self, request, container):
        """Get the container details"""
        return api.swift.swift_get_container(request, container).to_dict()

    @rest_utils.ajax()
    def post(self, request, container):
        metadata = {}

        if 'is_public' in request.DATA:
            metadata['is_public'] = request.DATA['is_public']

        if 'storage_policy' in request.DATA:
            metadata['storage_policy'] = request.DATA['storage_policy']

        # This will raise an exception if the container already exists
        try:
            api.swift.swift_create_container(request, container,
                                             metadata=metadata)
        except exceptions.AlreadyExists as e:
            # 409 Conflict
            return rest_utils.JSONResponse(str(e), 409)

        return rest_utils.CreatedResponse(
            '/api/swift/containers/%s' % container,
        )

    @rest_utils.ajax()
    def delete(self, request, container):
        try:
            api.swift.swift_delete_container(request, container)
        except exceptions.Conflict as e:
            # It cannot be deleted if it's not empty.
            return rest_utils.JSONResponse(str(e), 409)

    @rest_utils.ajax(data_required=True)
    def put(self, request, container):
        metadata = {'is_public': request.DATA['is_public']}
        api.swift.swift_update_container(request, container, metadata=metadata)


@urls.register
class Objects(generic.View):
    """API for a list of swift objects"""
    url_regex = r'swift/containers/(?P<container>[^/]+)/objects/$'

    @rest_utils.ajax()
    def get(self, request, container):
        """Get object information.

        :param request:
        :param container:
        :return:
        """
        path = request.GET.get('path')
        if path is not None:
            path = parse.unquote(path)

        objects = api.swift.swift_get_objects(
            request,
            container,
            prefix=path
        )

        # filter out the folder from the listing if we're filtering for
        # contents of a (pseudo) folder
        contents = [{
            'path': o.subdir if isinstance(o, swift.PseudoFolder) else o.name,
            'name': o.name.split('/')[-1],
            'bytes': o.bytes,
            'is_subdir': isinstance(o, swift.PseudoFolder),
            'is_object': not isinstance(o, swift.PseudoFolder),
            'content_type': getattr(o, 'content_type', None)
        } for o in objects[0] if o.name != path]
        return {'items': contents}


class UploadObjectForm(forms.Form):
    file = forms.FileField(required=False)


@urls.register
class Object(generic.View):
    """API for a single swift object or pseudo-folder"""
    url_regex = r'swift/containers/(?P<container>[^/]+)/object/' \
        '(?P<object_name>.+)$'

    # note: not an AJAX request - the body will be raw file content
    @csrf_exempt
    def post(self, request, container, object_name):
        """Create or replace an object or pseudo-folder

        :param request:
        :param container:
        :param object_name:

        If the object_name (ie. POST path) ends in a '/' then a folder is
        created, rather than an object. Any file content passed along with
        the request will be ignored in that case.

        POST parameter:

        :param file: the file data for the upload.

        :return:
        """
        form = UploadObjectForm(request.POST, request.FILES)
        if not form.is_valid():
            raise rest_utils.AjaxError(500, 'Invalid request')

        data = form.clean()

        if object_name[-1] == '/':
            result = api.swift.swift_create_pseudo_folder(
                request,
                container,
                object_name
            )
        else:
            result = api.swift.swift_upload_object(
                request,
                container,
                object_name,
                data['file']
            )

        return rest_utils.CreatedResponse(
            '/api/swift/containers/%s/object/%s' % (container, result.name)
        )

    @rest_utils.ajax()
    def delete(self, request, container, object_name):
        if object_name[-1] == '/':
            try:
                api.swift.swift_delete_folder(request, container, object_name)
            except exceptions.Conflict as e:
                # In case the given object is pseudo folder
                # It cannot be deleted if it's not empty.
                return rest_utils.JSONResponse(str(e), 409)
        else:
            api.swift.swift_delete_object(request, container, object_name)

    def get(self, request, container, object_name):
        """Get the object contents."""
        obj = api.swift.swift_get_object(
            request,
            container,
            object_name
        )

        # Add the original file extension back on if it wasn't preserved in the
        # name given to the object.
        filename = object_name.rsplit(api.swift.FOLDER_DELIMITER)[-1]
        if not os.path.splitext(obj.name)[1] and obj.orig_name:
            name, ext = os.path.splitext(obj.orig_name)
            filename = "%s%s" % (filename, ext)
        response = StreamingHttpResponse(obj.data)
        safe = filename.replace(",", "")
        response['Content-Disposition'] = 'attachment; filename="%s"' % safe
        response['Content-Type'] = 'application/octet-stream'
        if obj.bytes is not None:
            response['Content-Length'] = obj.bytes
        return response


@urls.register
class ObjectMetadata(generic.View):
    """API for a single swift object"""
    url_regex = r'swift/containers/(?P<container>[^/]+)/metadata/' \
        '(?P<object_name>.+)$'

    @rest_utils.ajax()
    def get(self, request, container, object_name):
        return api.swift.swift_get_object(
            request,
            container_name=container,
            object_name=object_name,
            with_data=False
        ).to_dict()


@urls.register
class ObjectCopy(generic.View):
    """API to copy a swift object"""
    url_regex = r'swift/containers/(?P<container>[^/]+)/copy/' \
        '(?P<object_name>.+)$'

    @rest_utils.ajax()
    def post(self, request, container, object_name):
        dest_container = request.DATA['dest_container']
        dest_name = request.DATA['dest_name']
        try:
            result = api.swift.swift_copy_object(
                request,
                container,
                object_name,
                dest_container,
                dest_name
            )
        except exceptions.AlreadyExists as e:
            return rest_utils.JSONResponse(str(e), 409)
        return rest_utils.CreatedResponse(
            '/api/swift/containers/%s/object/%s' % (dest_container,
                                                    result.name)
        )
