# coding: utf-8

from collections import OrderedDict

from django.conf import settings
from django.contrib.auth import logout
from django.core import mail
from django.core.exceptions import ObjectDoesNotExist
from django.middleware.csrf import rotate_token
from django.utils.translation import gettext as _
from django_yauth.util import get_passport_url, get_yauth_type
from rest_framework import exceptions, status
from rest_framework.response import Response
from rest_framework.views import APIView

from procu import jsonschema as js
from procu.api import models
from procu.api.auth import auth_login
from procu.api.push.external import notify_login
from procu.rest import generics
from procu.rest.permissions import IsAuthenticated, NonColdOnly
from . import serializers


# ------------------------------------------------------------------------------


class CurrentUser(generics.RetrieveAPIView):
    permission_classes = (IsAuthenticated,)

    def get_serializer_class(self):
        if self.request.user.is_staff:
            return serializers.UserCurrent
        else:
            return serializers.UserBrief

    def get_object(self):
        return self.request.user


# ------------------------------------------------------------------------------


class CSRF(APIView):
    authentication_classes = ()

    @staticmethod
    def get(request):
        rotate_token(request)
        return Response({'results': request.META['CSRF_COOKIE']})


# ------------------------------------------------------------------------------


class ExternalLogin(generics.CreateMetaMixin, generics.GenericAPIView):

    serializer_class = serializers.LoginSerializer

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        if auth_login(request, data['username'], data['password']):
            return Response({'result': _('AUTH_EXTERNAL::LOGIN_SUCCESSFUL')})

        else:
            raise exceptions.AuthenticationFailed(
                {
                    'detail': _('AUTH_EXTERNAL::INCORRECT_CREDENTIALS'),
                    'error_code': 'INCORRECT_LOGIN_CREDENTIALS',
                }
            )

    def meta_post(self, components):

        info = super().meta_post(components)

        if 'out' in components:
            info['responses'] = OrderedDict(
                [
                    (
                        'default',
                        {
                            'type': js.Object(
                                ('result', js.String()), required=js.ALL_FIELDS
                            ).asdict()
                        },
                    )
                ]
            )

        return info


# ------------------------------------------------------------------------------


def hide_email(email):

    user, domain = email.split('@')

    th = len(user) - int(len(user) * 2 / 3)
    user = ''.join(c if i < th else '*' for i, c in enumerate(user))

    th = len(domain) - int(len(domain) * 2 / 3)
    domain = ''.join(c if i >= th else '*' for i, c in enumerate(domain))

    return '%s@%s' % (user, domain)


class QuickLogin(
    generics.CreateMetaMixin,
    generics.RetrieveMetaMixin,
    generics.GenericAPIView,
):

    serializer_class = serializers.QuickLoginSerializer

    def maybe_fetch_email(self):
        return self.request.session.get(settings.SESSION_KEY_EMAIL)

    def get(self, request):

        email = self.maybe_fetch_email()

        if email is None:
            response = {}
        else:
            response = {'email': hide_email(email)}

        return Response(response)

    def post(self, request):

        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        if 'email' in data:
            email = data['email']
            source = 'form'
        else:
            email = self.maybe_fetch_email()
            source = 'session'

        if email is None:
            raise exceptions.ValidationError(
                _('AUTH_EXTERNAL::NO_CREDENTIALS_GIVEN')
            )

        try:
            user = models.User.objects.get(
                email__iexact=email, is_deleted=False
            )

            retpath = request.GET.get('retpath')

            with mail.get_connection(fail_silently=True) as conn:
                conn.send_messages(notify_login(user, retpath))

        except ObjectDoesNotExist:
            pass

        return Response(
            {
                'source': source,
                'email': hide_email(email) if source == 'session' else email,
                'result': hide_email(email) if source == 'session' else email,
            }
        )

    def meta_post(self, components):

        info = super().meta_post(components)

        if 'out' in components:
            info['responses'] = OrderedDict(
                [
                    (
                        'default',
                        {
                            'type': js.Object(
                                ('result', js.String()), required=js.ALL_FIELDS
                            ).asdict()
                        },
                    )
                ]
            )

        return info


# ------------------------------------------------------------------------------


class ExternalLogout(APIView):
    permission_classes = (IsAuthenticated,)

    @staticmethod
    def post(request):
        logout(request)
        return Response({'result': _('AUTH_EXTERNAL::LOGOUT_SUCCESSFUL')})


# ------------------------------------------------------------------------------


class InternalLogin(APIView):
    @staticmethod
    def get(request):

        yauth_type = get_yauth_type(request)
        retpath = request.GET.get('retpath', '')

        if request.user.is_authenticated and request.user.is_staff:

            if request.yauser.need_reset and settings.YAUTH_REFRESH_SESSION:
                url = get_passport_url(
                    'refresh', yauth_type, request=request, retpath=retpath
                )

                response = Response(
                    OrderedDict(
                        [
                            ('result', _('AUTH_INTERNAL::REFRESH_REQUIRED')),
                            ('url', url),
                            ('mode', 'update'),
                        ]
                    ),
                    status=status.HTTP_401_UNAUTHORIZED,
                )

                domain = get_passport_url(
                    'passport_domain', yauth_type, request=request
                )
                domain = domain[domain.find('.') :]

                response.set_cookie(
                    settings.YAUTH_COOKIE_CHECK_COOKIE_NAME,
                    'Cookie_check',
                    None,
                    None,
                    '/',
                    domain,
                    secure=settings.SESSION_COOKIE_SECURE,
                    httponly=settings.SESSION_COOKIE_HTTPONLY,
                )
                return response

            rotate_token(request)

            return Response({'result': _('AUTH_INTERNAL::SUCCESSFUL')})

        passport_url = get_passport_url(
            'create', yauth_type, request=request, retpath=retpath
        )

        return Response(
            OrderedDict(
                [
                    ('result', _('AUTH_INTERNAL::PASSPORT_AUTH_REQUIRED')),
                    ('url', passport_url),
                    ('mode', 'auth'),
                ]
            ),
            status=status.HTTP_401_UNAUTHORIZED,
        )


# ------------------------------------------------------------------------------


class ExternalChangePassword(generics.CreateMetaMixin, generics.GenericAPIView):
    serializer_class = serializers.UserChangePasswordSerializer
    permission_classes = (IsAuthenticated, NonColdOnly)

    def post(self, request, *args, **kwargs):

        user = request.user

        # Just a sanity check that a successfully logged external user
        # has a valid password.
        assert user.has_usable_password()

        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        user.set_password(data['new_password'])
        user.save()

        old_meta = {'CSRF_COOKIE': request.META['CSRF_COOKIE']}

        auth_login(request, user.username, data['new_password'])

        # Prevent CSRF token update from reauthentication.
        # Otherwise the token would be updated in cookies only, but not
        # in the local storage.
        request.META.update(old_meta)
        request.csrf_cookie_needs_reset = False

        return Response({'result': _('CHANGE_PASSWORD::CHANGED')})

    def meta_post(self, components):
        meta = super().meta_post(components)

        try:
            meta['responses']['default'] = {'type': 'string'}
        except KeyError:
            pass

        return meta
