from __future__ import unicode_literals

from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse
from django.utils import six
from django.utils.six.moves.urllib.parse import quote as urllib_quote
from djblets.util.decorators import augment_method_from
from djblets.util.http import get_http_requested_mimetype, set_last_modified
from djblets.webapi.decorators import (webapi_login_required,
                                       webapi_request_fields,
                                       webapi_response_errors)
from djblets.webapi.errors import (DOES_NOT_EXIST, NOT_LOGGED_IN,
                                   PERMISSION_DENIED)
from djblets.webapi.responses import WebAPIResponse

from reviewboard.attachments.models import FileAttachment
from reviewboard.diffviewer.diffutils import (get_diff_files,
                                              populate_diff_chunks)
from reviewboard.diffviewer.models import FileDiff
from reviewboard.webapi.base import CUSTOM_MIMETYPE_BASE, WebAPIResource
from reviewboard.webapi.decorators import (webapi_check_login_required,
                                           webapi_check_local_site)
from reviewboard.webapi.resources import resources
from reviewboard.webapi.resources.diff_file_attachment import \
    DiffFileAttachmentResource


class FileDiffResource(WebAPIResource):
    """Provides information on per-file diffs.

    Each of these contains a single, self-contained diff file that
    applies to exactly one file on a repository.
    """
    model = FileDiff
    name = 'file'
    policy_id = 'file_diff'
    allowed_methods = ('GET', 'PUT')
    fields = {
        'id': {
            'type': int,
            'description': 'The numeric ID of the file diff.',
        },
        'extra_data': {
            'type': dict,
            'description': 'Extra data as part of the diff. '
                           'This can be set by the API or extensions.',
        },
        'source_file': {
            'type': six.text_type,
            'description': 'The original name of the modified file in the '
                           'diff.',
        },
        'dest_file': {
            'type': six.text_type,
            'description': 'The new name of the patched file. This may be '
                           'the same as the existing file.',
        },
        'source_revision': {
            'type': six.text_type,
            'description': 'The revision of the file being modified. This '
                           'is a valid revision in the repository.',
        },
        'dest_detail': {
            'type': six.text_type,
            'description': 'Additional information of the destination file. '
                           'This is parsed from the diff, but is usually '
                           'not used for anything.',
        },
        'source_attachment': {
            'type': DiffFileAttachmentResource,
            'description': "The file attachment for the contents of the "
                           "original file for this file diff, if representing "
                           "a binary file.",
        },
        'dest_attachment': {
            'type': DiffFileAttachmentResource,
            'description': "The file attachment for the contents of the "
                           "patched file for this file diff, if representing "
                           "a binary file.",
        },
    }
    item_child_resources = [
        resources.filediff_comment,
        resources.original_file,
        resources.patched_file,
    ]

    uri_object_key = 'filediff_id'
    model_parent_key = 'diffset'

    DIFF_DATA_MIMETYPE_BASE = CUSTOM_MIMETYPE_BASE + '.diff.data'
    DIFF_DATA_MIMETYPE_JSON = DIFF_DATA_MIMETYPE_BASE + '+json'
    DIFF_DATA_MIMETYPE_XML = DIFF_DATA_MIMETYPE_BASE + '+xml'

    allowed_mimetypes = WebAPIResource.allowed_mimetypes + [
        {'item': 'text/x-patch'},
        {'item': DIFF_DATA_MIMETYPE_JSON},
        {'item': DIFF_DATA_MIMETYPE_XML},
    ]

    def serialize_source_attachment_field(self, filediff, **kwargs):
        try:
            return FileAttachment.objects.get_for_filediff(filediff,
                                                           modified=False)
        except FileAttachment.DoesNotExist:
            return None

    def serialize_dest_attachment_field(self, filediff, **kwargs):
        try:
            return FileAttachment.objects.get_for_filediff(filediff,
                                                           modified=True)
        except FileAttachment.DoesNotExist:
            return None

    def get_last_modified(self, request, obj, *args, **kwargs):
        return obj.diffset.timestamp

    def get_queryset(self, request, review_request_id, diff_revision,
                     local_site_name=None, *args, **kwargs):
        if local_site_name:
            review_request = resources.review_request.get_object(
                request,
                review_request_id=review_request_id,
                diff_revision=diff_revision,
                local_site_name=local_site_name,
                *args,
                **kwargs)
            review_request_id = review_request.pk

        return self.model.objects.filter(
            diffset__history__review_request=review_request_id,
            diffset__revision=diff_revision)

    def has_access_permissions(self, request, filediff, *args, **kwargs):
        review_request = resources.review_request.get_object(
            request, *args, **kwargs)

        return resources.review_request.has_access_permissions(
            request, review_request, *args, **kwargs)

    def has_modify_permissions(self, request, filedif, *args, **kwargs):
        review_request = resources.review_request.get_object(
            request, *args, **kwargs)

        return resources.review_request.has_modify_permissions(
            request, review_request, *args, **kwargs)

    @webapi_check_local_site
    @augment_method_from(WebAPIResource)
    def get_list(self, *args, **kwargs):
        """Returns the list of public per-file diffs on the review request.

        Each per-file diff has information about the diff. It does not
        provide the contents of the diff. For that, access the per-file diff's
        resource directly and use the correct mimetype.
        """
        pass

    def get_links(self, *args, **kwargs):
        """Returns a dictionary of links coming off this resource.

        If the file represented by the FileDiffResource is new,
        the link to the OriginalFileResource will be removed.
        Alternatively, if the file is deleted, the link to the
        PatchedFileResource will be removed.
        """
        links = super(FileDiffResource, self).get_links(*args, **kwargs)

        obj = kwargs.get('obj')

        # Only remove the links if we are returning them for
        # a specific filediff, and not a list of filediffs.
        if obj:
            if obj.is_new:
                del links[resources.original_file.name_plural]

            if obj.deleted:
                del links[resources.patched_file.name_plural]

        return links

    @webapi_check_login_required
    @webapi_check_local_site
    def get(self, request, *args, **kwargs):
        """Returns the information or contents on a per-file diff.

        The output varies by mimetype.

        If :mimetype:`application/json` or :mimetype:`application/xml` is
        used, then the fields for the diff are returned, like with any other
        resource.

        If :mimetype:`text/x-patch` is used, then the actual diff file itself
        is returned. This diff should be as it was when uploaded originally,
        for this file only, with potentially some extra SCM-specific headers
        stripped.

        If :mimetype:`application/vnd.reviewboard.org.diff.data+json` or
        :mimetype:`application/vnd.reviewboard.org.diff.data+xml` is used,
        then the raw diff data (lists of inserts, deletes, replaces, moves,