# -*- coding: utf-8 -*-
#
# Copyright © 2012 - 2020 Michal Čihař <michal@cihar.com>
#
# This file is part of Weblate <https://weblate.org/>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#

import os.path

from django.conf import settings
from django.contrib.messages import get_messages
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.encoding import force_text, smart_text
from django.utils.safestring import mark_safe
from rest_framework import parsers, status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ParseError
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.settings import api_settings
from rest_framework.utils import formatting
from rest_framework.views import APIView

from weblate.api.serializers import (
    ChangeSerializer,
    ComponentSerializer,
    LanguageSerializer,
    LockRequestSerializer,
    LockSerializer,
    ProjectSerializer,
    RepoRequestSerializer,
    ScreenshotFileSerializer,
    ScreenshotSerializer,
    StatisticsSerializer,
    TranslationSerializer,
    UnitSerializer,
    UploadRequestSerializer,
)
from weblate.auth.models import User
from weblate.checks.models import Check
from weblate.formats.exporters import EXPORTERS
from weblate.lang.models import Language
from weblate.screenshots.models import Screenshot
from weblate.trans.models import (
    Change,
    Component,
    Project,
    Suggestion,
    Translation,
    Unit,
)
from weblate.trans.stats import get_project_stats
from weblate.trans.tasks import component_removal, project_removal
from weblate.utils.celery import get_queue_stats
from weblate.utils.docs import get_doc_url
from weblate.utils.stats import GlobalStats
from weblate.utils.views import download_translation_file, zip_download
from weblate.wladmin.models import ConfigurationError

REPO_OPERATIONS = {
    'push': ('vcs.push', 'do_push', (), True),
    'pull': ('vcs.update', 'do_update', (), True),
    'reset': ('vcs.reset', 'do_reset', (), True),
    'cleanup': ('vcs.reset', 'do_cleanup', (), True),
    'commit': ('vcs.commit', 'commit_pending', ('api',), False),
}

DOC_TEXT = """
<p>See <a href="{0}">the Weblate's Web API documentation</a> for detailed
description of the API.</p>
"""


def get_view_description(view_cls, html=False):
    """
    Given a view class, return a textual description to represent the view.
    This name is used in the browsable API, and in OPTIONS responses.

    This function is the default for the `VIEW_DESCRIPTION_FUNCTION` setting.
    """
    description = view_cls.__doc__ or ''
    description = formatting.dedent(smart_text(description))

    if hasattr(getattr(view_cls, 'serializer_class', 'None'), 'Meta'):
        doc_url = get_doc_url(
            'api', '{0}s'.format(view_cls.serializer_class.Meta.model.__name__.lower())
        )
    else:
        doc_url = get_doc_url('api')

    if html:
        return formatting.markup_description(description) + mark_safe(
            DOC_TEXT.format(doc_url)
        )
    return description


class MultipleFieldMixin(object):
    """
    Apply this mixin to any view or viewset to get multiple field filtering
    based on a `lookup_fields` attribute, instead of the default single field
    filtering.
    """

    def get_object(self):
        # Get the base queryset
        queryset = self.get_queryset()
        # Apply any filter backends
        queryset = self.filter_queryset(queryset)
        lookup = {}
        for field in self.lookup_fields:
            lookup[field] = self.kwargs[field]
        # Lookup the object
        return get_object_or_404(queryset, **lookup)


class DownloadViewSet(viewsets.ReadOnlyModelViewSet):
    raw_urls = ()
    raw_formats = {}

    def perform_content_negotiation(self, request, force=False):
        """Custom content negotiation"""
        if request.resolver_match.url_name in self.raw_urls:
            fmt = self.format_kwarg or request.query_params.get('format')
            if fmt is None or fmt in self.raw_formats:
                renderers = self.get_renderers()
                return (renderers[0], renderers[0].media_type)
            raise Http404('Not supported format')
        return super(DownloadViewSet, self).perform_content_negotiation(request, force)

    def download_file(self, filename, content_type, component=None):
        """Wrapper for file download"""
        if os.path.isdir(filename):
            response = zip_download(filename, filename)
            filename = '{}.zip'.format(component.slug if component else 'weblate')
        else:
            with open(filename, 'rb') as handle:
                response = HttpResponse(handle.read(), content_type=content_type)
            filename = os.path.basename(filename)
        response['Content-Disposition'] = 'attachment; filename="{0}"'.format(filename)
        return response


class WeblateViewSet(DownloadViewSet):
    """Allow to skip content negotiation for certain requests."""

    def repository_operation(self, request, obj, project, operation):
        permission, method, args, takes_request = REPO_OPERATIONS[operation]

        if not request.user.has_perm(permission, project):
            raise PermissionDenied()

        if takes_request:
            args = args + (request,)
        else:
            args = args + (request.user,)

        return getattr(obj, method)(*args)

    @action(
        detail=True, methods=['get', 'post'], serializer_class=RepoRequestSerializer
    )
    def repository(self, request, **kwargs):
        obj = self.get_object()

        if isinstance(obj, Translation):
            project = obj.component.project
        elif isinstance(obj, Component):
            project = obj.project
        else:
            project = obj

        if request.method == 'POST':
            serializer = RepoRequestSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)

            data = {
                'result': self.repository_operation(
                    request, obj, project, serializer.validated_data['operation']
                )
            }

            storage = get_messages(request)
            if storage:
                data['detail'] = '\n'.join([m.message for m in storage])

            return Response(data)

        if not request.user.has_perm('meta:vcs.status', project):
            raise PermissionDenied()

        data = {
            'needs_commit': obj.needs_commit(),
            'needs_merge': obj.repo_needs_merge(),
            'needs_push': obj.repo_needs_push(),
        }

        if isinstance(obj, Project):
            data['url'] = reverse(
                'api:project-repository', kwargs={'slug': obj.slug}, request=request
            )
        else:
            data['remote_commit'] = obj.get_last_remote_commit()

            if isinstance(obj, Translation):
                component = obj.component
                data['url'] = reverse(
                    'api:translation-repository',
                    kwargs={
                        'component__project__slug': component.project.slug,
                        'component__slug': component.slug,
                        'language__code': obj.language.code,
                    },
                    request=request,
                )
                data['status'] = obj.component.repository.status()
                changes = Change.objects.filter(
                    action__in=Change.ACTIONS_REPOSITORY, component=obj.component
                ).order_by('-id')
            else:
                data['url'] = reverse(
                    'api:component-repository',
                    kwargs={'project__slug': obj.project.slug, 'slug': obj.slug},
                    request=request,
                )
                data['status'] = obj.repository.status()
                changes = Change.objects.filter(
                    action__in=Change.ACTIONS_REPOSITORY, component=obj
                ).order_by('-id')

            if changes.exists() and changes[0].is_merge_failure():
                data['merge_failure'] = changes[0].target
            else:
                data['merge_failure'] = None

        return Response(data)


class ProjectViewSet(WeblateViewSet, CreateModelMixin, DestroyModelMixin):
    """Translation projects API."""

    queryset = Project.objects.none()
    serializer_class = ProjectSerializer
    lookup_field = 'slug'

    def get_queryset(self):
        return self.request.user.allowed_projects.prefetch_related(
            'source_language'
        ).order_by('id')

    @action(detail=True, methods=['get', 'post'], serializer_class=ComponentSerializer)
    def components(self, request, **kwargs):
        obj = self.get_object()
        if request.method == 'POST':
            if not request.user.has_perm('project.edit', obj):
                self.permission_denied(request, message='Can not create components')
            with transaction.atomic():
                serializer = ComponentSerializer(
                    data=request.data, context={'request': request}
                )
                serializer.is_valid(raise_exception=True)
                serializer.save(project=obj)
                serializer.instance.post_create(self.request.user)
                return Response(
                    serializer.data,
                    status=status.HTTP_201_CREATED,
                    headers={
                        'Location': str(serializer.data[api_settings.URL_FIELD_NAME])
                    },
                )

        queryset = obj.component_set.all().order_by('id')
        page = self.paginate_queryset(queryset)

        serializer = ComponentSerializer(
            page, many=True, context={'request': request}, remove_fields=('project',)
        )

        return self.get_paginated_response(serializer.data)

    @action(detail=True, methods=['get'])
    def statistics(self, request, **kwargs):
        obj = self.get_object()

        serializer = StatisticsSerializer(obj, context={'request': request})

        return Response(serializer.data)

    @action(detail=True, methods=['get'])
    def languages(self, request, **kwargs):
        obj = self.get_object()

        return Response(get_project_stats(obj))

    @action(detail=True, methods=['get'])
    def changes(self, request, **kwargs):
        obj = self.get_object()

        queryset = Change.objects.prefetch().filter(project=obj).order_by('id')
        page = self.paginate_queryset(queryset)

        serializer = ChangeSerializer(page, many=True, context={'request': request})

        return self.get_paginated_response(serializer.data)

    def create(self, request, *args, **kwargs):
        if not request.user.has_perm('project.add'):
            self.permission_denied(request, message='Can not create projects')
        self.request = request
        return super(ProjectViewSet, self).create(request, *args, **kwargs)

    def perform_create(self, serializer):
        with transaction.atomic():
            super(ProjectViewSet, self).perform_create(serializer)
            if (
                not self.request.user.is_superuser
                and 'weblate.billing' in settings.INSTALLED_APPS
            ):
                from weblate.billing.models import Billing

                try:
                    billing = Billing.objects.get_valid().for_user(self.request.user)[0]
                except IndexError:
                    billing = None
            else:
                billing = None
            serializer.instance.post_create(self.request.user, billing)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        if not request.user.has_perm('project.edit', instance):
            self.permission_denied(request, message='Can not delete project')
        project_removal.delay(instance.pk, request.user.pk)
        return Response(status=status.HTTP_204_NO_CONTENT)


class ComponentViewSet(MultipleFieldMixin, WeblateViewSet, DestroyModelMixin):
    """Translation components API."""

    queryset = Component.objects.none()
    serializer_class = ComponentSerializer
    lookup_fields = ('project__slug', 'slug')

    def get_queryset(self):
        return (
            Component.objects.prefetch()
            .filter(project__in=self.request.user.allowed_projects)
            .prefetch_related('project__source_language')
            .order_by('id')
        )

    @action(
        detail=True, methods=['get', 'post'], serializer_class=LockRequestSerializer
    )
    def lock(self, request, **kwargs):
        obj = self.get_object()

        if request.method == 'POST':
            if not request.user.has_perm('component.lock', obj):
                raise PermissionDenied()

            serializer = LockRequestSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)

            obj.do_lock(request.user, serializer.validated_data['lock'])

        return Response(data=LockSerializer(obj).data)

    @action(detail=True, methods=['get'])
    def monolingual_base(self, request, **kwargs):
        obj = self.get_object()

        if not obj.has_template():
            raise Http404('No template found!')

        return self.download_file(
            obj.get_template_filename(), obj.template_store.mimetype(), component=obj
        )

    @action(detail=True, methods=['get'])
    def new_template(self, request, **kwargs):
        obj = self.get_object()

        if not obj.new_base:
            raise Http404('No file found!')

        return self.download_file(obj.get_new_base_filename(), 'application/binary')

    @action(detail=True, methods=['get', 'post'])
    def translations(self, request, **kwargs):
        obj = self.get_object()

        if request.method == 'POST':
            if not request.user.has_perm('translation.add', obj):
                self.permission_denied(request, message='Can not create translation')

            if 'language_code' not in request.data:
                raise ParseError("Missing 'language_code' parameter")

            language_code = request.data['language_code']

            try:
                language = Language.objects.get(code=language_code)
            except Language.DoesNotExist:
                raise Http404("No language code '%s' found!" % language_code)

            translation = obj.add_new_language(language, request)
            serializer = TranslationSerializer(
                translation, context={'request': request}, remove_fields=('component',)
            )

            return Response(
                data={'data': serializer.data}, status=status.HTTP_201_CREATED
            )

        queryset = obj.translation_set.all().order_by('id')
        page = self.paginate_queryset(queryset)

        serializer = TranslationSerializer(
            page, many=True, context={'request': request}, remove_fields=('component',)
        )

        return self.get_paginated_response(serializer.data)

    @action(detail=True, methods=['get'])
    def statistics(self, request, **kwargs):
        obj = self.get_object()

        queryset = obj.translation_set.all().order_by('id')
        page = self.paginate_queryset(queryset)

        serializer = StatisticsSerializer(page, many=True, context={'request': request})

        return self.get_paginated_response(serializer.data)

    @action(detail=True, methods=['get'])
    def changes(self, request, **kwargs):
        obj = self.get_object()

        queryset = Change.objects.prefetch().filter(component=obj).order_by('id')
        page = self.paginate_queryset(queryset)

        serializer = ChangeSerializer(page, many=True, context={'request': request})

        return self.get_paginated_response(serializer.data)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        if not request.user.has_perm('component.edit', instance):
            self.permission_denied(request, message='Can not delete component')
        component_removal.delay(instance.pk, request.user.pk)
        return Response(status=status.HTTP_204_NO_CONTENT)


class TranslationViewSet(MultipleFieldMixin, WeblateViewSet, DestroyModelMixin):
    """Translation components API."""

    queryset = Translation.objects.none()
    serializer_class = TranslationSerializer
    lookup_fields = ('component__project__slug', 'component__slug', 'language__code')
    raw_urls = ('translation-file',)
    raw_formats = EXPORTERS

    def get_queryset(self):
        return (
            Translation.objects.prefetch()
            .filter(component__project__in=self.request.user.allowed_projects)
            .prefetch_related('component__project__source_language')
            .order_by('id')
        )

    @action(
        detail=True,
        methods=['get', 'put', 'post'],
        parser_classes=(
            parsers.MultiPartParser,
            parsers.FormParser,
            parsers.FileUploadParser,
        ),
        serializer_class=UploadRequestSerializer,
    )
    def file(self, request, **kwargs):
        obj = self.get_object()
        user = request.user
        if request.method == 'GET':
            fmt = self.format_kwarg or request.query_params.get('format')
            return download_translation_file(obj, fmt)

        if not user.has_perm('upload.perform', obj) or obj.component.locked:
            raise PermissionDenied()

        if 'file' not in request.data:
            raise ParseError('Missing file parameter')

        serializer = UploadRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.check_perms(request.user, obj)

        data = serializer.validated_data

        author_name = None
        author_email = None
        if request.user.has_perm('upload.authorship', obj):
            author_name = data.get('author_name')
            author_email = data.get('author_email')

        try:
            not_found, skipped, accepted, total = obj.merge_upload(
                request,
                data['file'],
                data['overwrite'],
                author_name,
                author_email,
                data['method'],
                data['fuzzy'],
            )

            return Response(
                data={
                    'not_found': not_found,
                    'skipped': skipped,
                    'accepted': accepted,
                    'total': total,
                    # Compatibility with older less detailed API
                    'result': accepted > 0,
                    'count': total,
                }
            )
        except Exception as error:
            return Response(
                data={'result': False, 'detail': force_text(error)}, status=400
            )

    @action(detail=True, methods=['get'])
    def statistics(self, request, **kwargs):
        obj = self.get_object()

        serializer = StatisticsSerializer(obj, context={'request': request})

        return Response(serializer.data)

    @action(detail=True, methods=['get'])
    def changes(self, request, **kwargs):
        obj = self.get_object()

        queryset = Change.objects.prefetch().filter(translation=obj).order_by('id')
        page = self.paginate_queryset(queryset)

        serializer = ChangeSerializer(page, many=True, context={'request': request})

        return self.get_paginated_response(serializer.data)

    @action(detail=True, methods=['get'])
    def units(self, request, **kwargs):
        obj = self.get_object()

        queryset = obj.unit_set.all().order_by('id')
        page = self.paginate_queryset(queryset)

        serializer = UnitSerializer(page, many=True, context={'request': request})

        return self.get_paginated_response(serializer.data)

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        if not request.user.has_perm('translation.delete', instance):
            self.permission_denied(request, message='Can not delete translation')
        instance.remove(request.user)
        return Response(status=status.HTTP_204_NO_CONTENT)


class LanguageViewSet(viewsets.ReadOnlyModelViewSet):
    """Languages API."""

    queryset = Language.objects.none()
    serializer_class = LanguageSerializer
    lookup_field = 'code'

    def get_queryset(self):
        return Language.objects.have_translation().order_by('id')


class UnitViewSet(viewsets.ReadOnlyModelViewSet):
    """Units API"""

    queryset = Unit.objects.none()
    serializer_class = UnitSerializer

    def get_queryset(self):
        allowed_projects = self.request.user.allowed_projects
        return Unit.objects.filter(
            translation__component__project__in=allowed_projects
        ).order_by('id')


class ScreenshotViewSet(DownloadViewSet):
    """Screenshots API"""

    queryset = Screenshot.objects.none()
    serializer_class = ScreenshotSerializer
    raw_urls = ('screenshot-file',)

    def get_queryset(self):
        return Screenshot.objects.filter(
            component__project__in=self.request.user.allowed_projects
        ).order_by('id')

    @action(
        detail=True,
        methods=['get', 'put', 'post'],
        parser_classes=(
            parsers.MultiPartParser,
            parsers.FormParser,
            parsers.FileUploadParser,
        ),
        serializer_class=ScreenshotFileSerializer,
    )
    def file(self, request, **kwargs):
        obj = self.get_object()
        if request.method == 'GET':
            return self.download_file(obj.image.path, 'application/binary')

        if not request.user.has_perm('screenshot.edit', obj.component):
            raise PermissionDenied()

        serializer = ScreenshotFileSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        obj.image.save(
            serializer.validated_data['image'].name, serializer.validated_data['image']
        )

        return Response(data={'result': True})


class ChangeViewSet(viewsets.ReadOnlyModelViewSet):
    """Changes API"""

    queryset = Change.objects.none()
    serializer_class = ChangeSerializer

    def get_queryset(self):
        return Change.objects.last_changes(self.request.user).order_by('id')


class Metrics(APIView):
    """Metrics view for monitoring"""

    permission_classes = (IsAuthenticated,)

    # pylint: disable=redefined-builtin
    def get(self, request, format=None):
        """
        Return a list of all users.
        """
        stats = GlobalStats()
        queues = get_queue_stats()

        return Response(
            {
                'units': stats.all,
                'units_translated': stats.translated,
                'users': User.objects.count(),
                'changes': Change.objects.count(),
                'projects': Project.objects.count(),
                'components': Component.objects.count(),
                'translations': Translation.objects.count(),
                'languages': stats.languages,
                'checks': Check.objects.count(),
                'configuration_errors': ConfigurationError.objects.filter(
                    ignored=False
                ).count(),
                'suggestions': Suggestion.objects.count(),
                'index_updates': queues.get('search', 0),
                'celery_queue': queues.get('celery', 0),
                'celery_memory_queue': queues.get('memory', 0),
                'celery_notification_queue': queues.get('notification', 0),
                'celery_queues': queues,
                'name': settings.SITE_TITLE,
            }
        )
