# -*- coding: utf-8 -*-
from django.http import Http404, JsonResponse
from django.conf import settings
from django.utils.translation import ugettext as _
from ids.exceptions import AuthError
from rest_framework.exceptions import NotAuthenticated
from rest_framework.generics import get_object_or_404
from ylog.context import put_to_context

from events.common_app.utils import get_language_or_default
from events.surveyme.models import Survey
from events.rest_framework_contrib.permissions import IsAuthenticated, HasChangePermission
from events.rest_framework_contrib.exceptions import PermissionDenied
from events.common_app.utils import (
    get_lang_from_accept_language,
    get_lang_from_query_params,
    get_lang_with_fallback,
)


class ManyLookupFieldsMixin(object):
    """
    Можно указать список lookup_fields, в соответствии с которым будет забираться сущность из базы
    Возвращается первый найденный объект, так что нужно быть уверенным, что значения lookup_field-полей не будут совпадать
    """
    lookup_fields = []

    def get_object(self, *args, **kwargs):
        original_lookup_field = self.lookup_field
        if self.lookup_field not in self.lookup_fields:
            self.lookup_fields = list(self.lookup_fields) + [self.lookup_field]
        for lookup_field in self.lookup_fields:
            self.kwargs[lookup_field] = self.kwargs[original_lookup_field]
            self.lookup_field = lookup_field
            try:
                result = super(ManyLookupFieldsMixin, self).get_object(*args, **kwargs)
                not_found = False
                break
            except Http404:
                not_found = True
        self.lookup_field = original_lookup_field
        if not_found:
            raise Http404
        else:
            return result


class HttpNotAuthenticated(JsonResponse):
    status_code = 401

    def __init__(self):
        super(HttpNotAuthenticated, self).__init__({
            'detail': _('Authentication credentials were not provided.'),
        })


class HttpPermissionDenied(JsonResponse):
    status_code = 403

    def __init__(self, reason=None):
        reason = reason or 'external-user'
        super(HttpPermissionDenied, self).__init__({
            'detail': _('You do not have permission to perform this action.'),
            'reason': reason,
        })


class GenericViewV1Mixin:
    def _put_log_context(self, request):
        put_to_context('endpoint', {
            'name': self.__class__.__name__,
            'view_name': request.resolver_match.view_name,
        })
        put_to_context('user', {
            'uid': str(request.yauser.uid) if request.yauser.uid else '-',
            'cloud_uid': getattr(request.yauser, 'cloud_uid', '-'),
            'orgs': ','.join(request.orgs) if request.orgs else '-',
        })
        put_to_context('auth', {
            'mechanism': self._get_mechanism(request),
        })

    def _get_mechanism(self, request):
        if not request.yauser.is_authenticated():
            return '-'
        return getattr(request.yauser.authenticated_by, 'mechanism_name', '-')

    def dispatch(self, request, *args, **kwargs):
        self._put_log_context(request)
        return super().dispatch(request, *args, **kwargs)


class ExternalGenericApiViewV1Mixin(GenericViewV1Mixin):
    create_on_missing = True

    def dispatch(self, request, *args, **kwargs):
        try:
            return super().dispatch(request, *args, **kwargs)
        except AuthError:
            return HttpNotAuthenticated()

    def get_language_code(self, fallback):
        lang = get_lang_from_query_params(self.request) or get_lang_from_accept_language(self.request)
        if not lang:
            lang = settings.MODELTRANSLATION_DEFAULT_LANGUAGE
        if fallback and lang not in settings.MODELTRANSLATION_FALLBACK_LANGUAGES['default']:
            lang = (
                settings.MODELTRANSLATION_FALLBACK_LANGUAGES.get(lang)
                or settings.MODELTRANSLATION_DEFAULT_LANGUAGE
            )
        return lang

    def permission_denied(self, request, message=None, code=None):
        """
        Метод, в отличие от стандартного, учитывает то что request.user
        это всегда объект класса User, а не AnonymousUser,
        а понять успешная ли была аутентификация или нет можно
        по request.user.is_anonymous
        """
        if (
            request.authenticators and (
                not request.successful_authenticator
                or request.user.is_anonymous
            )
        ):
            raise NotAuthenticated()
        raise PermissionDenied(detail=message, code=code)


class InternalGenericApiViewV2Mixin(ExternalGenericApiViewV1Mixin):
    permission_classes = (IsAuthenticated, )


class InternalGenericApiViewV2MixinWithPermissions(InternalGenericApiViewV2Mixin):
    permission_classes = (IsAuthenticated, HasChangePermission)
    check_permissions_on_create = True

    def pre_save_check_permissions(self, obj):  # TODO: check permissions
        self._check_permissions_for_request(obj)

    def get_original_object(self, obj):
        if not hasattr(self, '_original_obj'):
            model = type(obj)
            try:
                self._original_obj = model.objects.get(pk=obj.pk)
            except model.DoesNotExist:
                self._original_obj = None
        return self._original_obj

    def get_object(self):
        # В отличии от стандартного кода не применяем метод filter_queryset
        queryset = self.get_queryset()

        # Perform the lookup filtering.
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

        assert lookup_url_kwarg in self.kwargs, (
            'Expected view %s to be called with a URL keyword argument '
            'named "%s". Fix your URL conf, or set the `.lookup_field` '
            'attribute on the view correctly.' %
            (self.__class__.__name__, lookup_url_kwarg)
        )

        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        obj = get_object_or_404(queryset, **filter_kwargs)

        # May raise a permission denied
        self.check_object_permissions(self.request, obj)

        return obj

    def perform_create(self, serializer):
        super(InternalGenericApiViewV2MixinWithPermissions, self).perform_create(serializer)
        obj = serializer.instance
        if self.check_permissions_on_create:
            self._check_permissions_for_request(obj)

    def perform_destroy(self, obj):  # TODO: check permissions
        self.pre_delete_check_permissions(obj)
        return super(InternalGenericApiViewV2MixinWithPermissions, self).perform_destroy(obj)

    def pre_delete_check_permissions(self, obj):
        self._check_permissions_for_request(obj)

    def _check_permissions_for_request(self, obj):
        for permission_class in self.permission_classes:
            if not permission_class().has_object_permission(self.request, self, obj):
                raise PermissionDenied

    def check_object_custom_permissions(self, request, obj, permission_classes):
        """
        Check if the request should be permitted for a given object.
        Raises an appropriate exception if the request is not permitted.
        """
        permissions = [permission() for permission in permission_classes]
        for permission in permissions:
            if not permission.has_object_permission(request, self, obj):
                self.permission_denied(
                    request, message=getattr(permission, 'message', None)
                )


class TranslationViewMixin(object):

    def _extract_translated_data(self, serializer):
        current_language = get_language_or_default()
        obj_language = serializer.instance.get_default_language()
        translation_fields_data = {}
        if obj_language and current_language != obj_language:
            for field in serializer.instance.FIELDS_FOR_TRANSLATION:
                field_value = serializer.validated_data.pop(field, None)
                if field_value:
                    translation_fields_data[field] = field_value
        return translation_fields_data

    def perform_update(self, serializer):
        """
        Перед сохранением объекта убираем из данных сериализатора
        переводные поля, если их язык отличен от дефолтного языка объекта
        и после сохранения других изменений дописываем эти поля в translations
        объекта
        """
        translation_fields_data = self._extract_translated_data(serializer)

        super(TranslationViewMixin, self).perform_update(serializer)

        obj = serializer.instance
        need_translation_update = False
        language = get_language_or_default()
        if obj.translations is None:
            obj.translations = {}

        for field in obj.FIELDS_FOR_TRANSLATION:
            field_value = translation_fields_data.get(field) or getattr(obj, field)
            field_translation = obj.translations.get(field)
            if not field_translation:
                need_translation_update = True
                obj.translations[field] = {language: field_value}
            else:
                if field_translation.get(language) != field_value:
                    need_translation_update = True
                    obj.translations[field][language] = field_value

        if need_translation_update:
            obj.save(update_fields=['translations'])

    def perform_create(self, serializer):
        super(TranslationViewMixin, self).perform_create(serializer)
        obj = serializer.instance
        language = getattr(obj, 'language', None) or get_language_or_default()
        translations = {}
        for field in obj.FIELDS_FOR_TRANSLATION:
            translations[field] = {language: getattr(obj, field)}
        obj.translations = translations
        obj.save(update_fields=['translations'])


class TranslationSerializerMixin(object):
    def _get_obj_language(self, obj):
        if isinstance(obj, Survey):
            return obj.language

    def to_representation(self, obj):
        lang_from_request, fallback_lang = get_lang_with_fallback()
        if obj.translations and self._get_obj_language(obj) != lang_from_request:
            translated_data = {}
            for field, translation_data in obj.translations.items():
                field_translation = translation_data.get(lang_from_request)
                if not field_translation and fallback_lang:
                    field_translation = translation_data.get(fallback_lang)
                if field_translation:
                    translated_data[field] = field_translation
            if translated_data:
                obj.__dict__.update(translated_data)

        return super(TranslationSerializerMixin, self).to_representation(obj)


class DetailSerializerMixin:
    """
    Add custom serializer for detail view
    """
    serializer_detail_class = None
    queryset_detail = None

    def get_serializer_class(self):
        error_message = "'{0}' should include a 'serializer_detail_class' attribute".format(self.__class__.__name__)
        assert self.serializer_detail_class is not None, error_message
        if self._is_request_to_detail_endpoint():
            return self.serializer_detail_class
        else:
            return super().get_serializer_class()

    def get_queryset(self, *args, **kwargs):
        if self._is_request_to_detail_endpoint() and self.queryset_detail is not None:
            return self.queryset_detail.all()  # todo: test all()
        else:
            return super().get_queryset(*args, **kwargs)

    def _is_request_to_detail_endpoint(self):
        if hasattr(self, 'lookup_url_kwarg'):
            lookup = self.lookup_url_kwarg or self.lookup_field
        return lookup and lookup in self.kwargs
