# ~*~ coding: utf-8 ~*~

from __future__ import unicode_literals

import time

from django.conf import settings
from django.core.cache import cache
from django.shortcuts import redirect, reverse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import FormView, RedirectView

from authentication.errors import IntervalTooShort
from authentication.utils import check_user_property_is_correct
from common.const.choices import COUNTRY_CALLING_CODES
from common.utils import FlashMessageUtil, get_object_or_none, random_string
from common.utils.verify_code import SendAndVerifyCodeUtil
from users.notifications import ResetPasswordSuccessMsg
from users.serializers import SmsUserSerializer
from ... import forms
from ...models import User
from ...utils import check_password_rules, get_password_check_rules

__all__ = [
    'UserLoginView',
    'UserResetPasswordView',
    'UserForgotPasswordView',
    'UserForgotPasswordPreviewingView',
]


class UserLoginView(RedirectView):
    url = reverse_lazy('authentication:login')
    query_string = True


class UserForgotPasswordPreviewingView(FormView):
    template_name = 'users/forgot_password_previewing.html'
    form_class = forms.UserForgotPasswordPreviewingForm

    @staticmethod
    def get_redirect_url(token):
        return reverse('authentication:forgot-password') + '?token=%s' % token

    @staticmethod
    def generate_previewing_token(user):
        sent_ttl = 60
        token_sent_at_key = '{}_send_at'.format(user.username)
        token_sent_at = cache.get(token_sent_at_key, 0)

        if token_sent_at:
            raise IntervalTooShort(sent_ttl)
        token = random_string(36)
        cache.set(token, SmsUserSerializer(user).data, 5 * 60)
        cache.set(token_sent_at_key, time.time(), sent_ttl)
        return token

    def form_valid(self, form):
        username = form.cleaned_data['username']
        user = get_object_or_none(User, username=username)
        if not user:
            form.add_error('username', _('User does not exist: {}').format(username))
            return super().form_invalid(form)
        if settings.ONLY_ALLOW_AUTH_FROM_SOURCE and not user.is_local:
            error = _('Non-local users can log in only from third-party platforms '
                      'and cannot change their passwords: {}').format(username)
            form.add_error('username', error)
            return super().form_invalid(form)

        try:
            token = self.generate_previewing_token(user)
        except IntervalTooShort as e:
            form.add_error('username', e)
            return super().form_invalid(form)
        return redirect(self.get_redirect_url(token))


class UserForgotPasswordView(FormView):
    template_name = 'users/forgot_password.html'
    form_class = forms.UserForgotPasswordForm

    def get(self, request, *args, **kwargs):
        token = self.request.GET.get('token')
        userinfo = cache.get(token)
        if not userinfo:
            return redirect(self.get_redirect_url(return_previewing=True))
        else:
            context = self.get_context_data(has_phone=bool(userinfo['phone']))
            return self.render_to_response(context)

    @staticmethod
    def get_validate_backends_context(has_phone):
        validate_backends = [{'name': _('Email'), 'is_active': True, 'value': 'email'}]
        if settings.XPACK_LICENSE_IS_VALID:
            if settings.SMS_ENABLED and has_phone:
                is_active = True
            else:
                is_active = False
            sms_backend = {'name': _('SMS'), 'is_active': is_active, 'value': 'sms'}
            validate_backends.append(sms_backend)
        return {'validate_backends': validate_backends}

    def get_context_data(self, has_phone=False, **kwargs):
        context = super().get_context_data(**kwargs)
        form = context['form']

        cleaned_data = getattr(form, 'cleaned_data', {})
        for k, v in cleaned_data.items():
            if v:
                context[k] = v
        context['countries'] = COUNTRY_CALLING_CODES
        context['form_type'] = 'email'
        context['XPACK_ENABLED'] = settings.XPACK_ENABLED
        validate_backends = self.get_validate_backends_context(has_phone)
        context.update(validate_backends)
        return context

    @staticmethod
    def get_redirect_url(user=None, return_previewing=False):
        if not user and return_previewing:
            return reverse('authentication:forgot-previewing')
        query_params = '?token=%s' % user.generate_reset_token()
        reset_password_url = reverse('authentication:reset-password')
        return reset_password_url + query_params

    @staticmethod
    def safe_verify_code(token, target, form_type, code):
        token_verified_key = '{}_verified'.format(token)
        token_verified_times = cache.get(token_verified_key, 0)

        if token_verified_times >= 3:
            cache.delete(token)
            raise ValueError('Verification code has been used more than 3 times, please re-verify')
        cache.set(token_verified_key, token_verified_times + 1, 5 * 60)
        sender_util = SendAndVerifyCodeUtil(target, backend=form_type)
        return sender_util.verify(code)

    def form_valid(self, form):
        token = self.request.GET.get('token')
        user_info = cache.get(token)
        if not user_info:
            return redirect(self.get_redirect_url(return_previewing=True))

        username = user_info.get('username')
        form_type = form.cleaned_data['form_type']
        target = form.cleaned_data[form_type]
        code = form.cleaned_data['code']
        country_code = form.cleaned_data.get('country_code', '')

        query_key = form_type
        if form_type == 'sms':
            query_key = 'phone'
            target = '{}{}'.format(country_code, target)

        try:
            self.safe_verify_code(token, target, form_type, code)
        except ValueError as e:
            return redirect(self.get_redirect_url(return_previewing=True))
        except Exception as e:
            form.add_error('code', str(e))
            return super().form_invalid(form)

        user = check_user_property_is_correct(username, **{query_key: target})
        if not user:
            form.add_error('code', _('No user matched'))
            return super().form_invalid(form)

        return redirect(self.get_redirect_url(user))


class UserResetPasswordView(FormView):
    template_name = 'users/reset_password.html'
    form_class = forms.UserTokenResetPasswordForm

    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        errors = kwargs.get('errors')
        if errors:
            context['errors'] = errors
        return self.render_to_response(context)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        token = self.request.GET.get('token', '')
        user = User.validate_reset_password_token(token)
        if not user:
            context['errors'] = _('Token invalid or expired')
            context['token_invalid'] = True
        else:
            check_rules = get_password_check_rules(user)
            context['password_check_rules'] = check_rules
        return context

    def form_valid(self, form):
        token = self.request.GET.get('token')
        user = User.validate_reset_password_token(token)
        if not user:
            error = _('Token invalid or expired')
            form.add_error('new_password', error)
            return self.form_invalid(form)

        if not user.can_update_password():
            error = _('User auth from {}, go there change password')
            form.add_error('new_password', error.format(user.get_source_display()))
            return self.form_invalid(form)

        password = form.cleaned_data['new_password']
        is_ok = check_password_rules(password, is_org_admin=user.is_org_admin)
        if not is_ok:
            error = _('* Your password does not meet the requirements')
            form.add_error('new_password', error)
            return self.form_invalid(form)

        if user.is_history_password(password):
            limit_count = settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT
            error = _('* The new password cannot be the last {} passwords').format(
                limit_count
            )
            form.add_error('new_password', error)
            return self.form_invalid(form)

        if user.check_leak_password(password):
            error = _('Your password is too simple, please change it for security')
            form.add_error('new_password', error)
            return self.form_invalid(form)

        user.reset_password(password)
        User.expired_reset_password_token(token)

        ResetPasswordSuccessMsg(user, self.request).publish_async()
        url = self.get_redirect_url()
        return redirect(url)

    @staticmethod
    def get_redirect_url():
        message_data = {
            'title': _('Reset password success'),
            'message': _('Reset password success, return to login page'),
            'redirect_url': reverse('authentication:login'),
            'auto_redirect': True,
        }
        return FlashMessageUtil.gen_message_url(message_data)
