# -*- coding: utf-8 -*-
#
# This file is part of the jabber.at homepage (https://github.com/jabber-at/hp).
#
# This project 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 project 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 project. If not, see
# <http://www.gnu.org/licenses/>.

import logging
import os

from celery import chain

from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth import login
from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.cache import cache
from django.db import transaction
from django.db.models import Sum
from django.http import HttpResponse
from django.http import HttpResponseForbidden
from django.http import HttpResponseRedirect
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import resolve_url
from django.template.defaultfilters import filesizeformat
from django.template.response import TemplateResponse
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_noop
from django.views.generic import DetailView
from django.views.generic import View
from django.views.generic.base import RedirectView
from django.views.generic.base import TemplateView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import CreateView
from django.views.generic.edit import FormView
from django.views.generic.edit import UpdateView

from xmpp_backends.django import xmpp_backend
from xmpp_http_upload.models import Upload
from xmpp_http_upload.utils import get_config

from antispam.exceptions import BlockedException
from antispam.models import BlockedEmail
from antispam.models import BlockedIpAddress
from antispam.utils import normalize_email
from core.constants import ACTIVITY_FAILED_LOGIN
from core.constants import ACTIVITY_REGISTER
from core.constants import ACTIVITY_RESEND_CONFIRMATION
from core.constants import ACTIVITY_RESET_PASSWORD
from core.constants import ACTIVITY_SET_EMAIL
from core.constants import ACTIVITY_SET_PASSWORD
from core.models import AddressActivity
from core.utils import format_timedelta
from core.utils import version
from core.views import AnonymousRequiredMixin
from core.views import AntiSpamMixin
from core.views import HomepageViewMixin
from stats.constants import STAT_DELETE_ACCOUNT
from stats.constants import STAT_DELETE_ACCOUNT_CONFIRMED
from stats.constants import STAT_FAILED_LOGIN
from stats.constants import STAT_REGISTER
from stats.constants import STAT_REGISTER_CONFIRMED
from stats.constants import STAT_RESEND_CONFIRMATION
from stats.constants import STAT_RESET_PASSWORD
from stats.constants import STAT_RESET_PASSWORD_CONFIRMED
from stats.constants import STAT_SET_EMAIL
from stats.constants import STAT_SET_EMAIL_CONFIRMED
from stats.constants import STAT_SET_PASSWORD
from stats.models import stat

from .constants import PURPOSE_DELETE
from .constants import PURPOSE_REGISTER
from .constants import PURPOSE_RESET_PASSWORD
from .constants import PURPOSE_SET_EMAIL
from .forms import AddGpgForm
from .forms import ConfirmRegistrationForm
from .forms import ConfirmResetPasswordForm
from .forms import CreateUserForm
from .forms import DeleteAccountForm
from .forms import LoginForm
from .forms import NotificationsForm
from .forms import PasswordChangeForm
from .forms import ResetPasswordForm
from .forms import SetEmailForm
from .models import Confirmation
from .tasks import add_gpg_key_task
from .tasks import send_confirmation_task
from .tasks import set_email_task

User = get_user_model()
log = logging.getLogger(__name__)
_confirmation_qs = Confirmation.objects.select_related('user')


class AccountPageMixin(HomepageViewMixin):
    """Mixin that adds the usermenu on the left to views where the user is logged in."""

    usermenu_item = None
    requires_email = False
    requires_confirmation = True

    def dispatch(self, request, *args, **kwargs):
        if request.user.blocked is True:
            kwargs = {}
            if isinstance(self, SingleObjectMixin):
                self.object = self.get_object()
                kwargs['object'] = self.object
            context = self.get_context_data(**kwargs)

            return TemplateResponse(request, 'account/blocked.html', context)
        elif self.requires_confirmation and not request.user.created_in_backend:
            kwargs = {}
            if isinstance(self, SingleObjectMixin):
                self.object = self.get_object()
                kwargs['object'] = self.object
            context = self.get_context_data(**kwargs)

            return TemplateResponse(request, 'account/requires_confirmation.html', context)
        elif self.requires_email and not request.user.email:
            kwargs = {}
            if isinstance(self, SingleObjectMixin):
                self.object = self.get_object()
                kwargs['object'] = self.object
            context = self.get_context_data(**kwargs)

            return TemplateResponse(request, 'account/requires_email.html', context)

        return super(AccountPageMixin, self).dispatch(request, *args, **kwargs)

    def get_usermenu(self):
        usermenu = []
        for urlname, config in settings.ACCOUNT_USER_MENU:
            req_confirmation = config.get('requires_confirmation', True)
            if self.request.user.created_in_backend is False and req_confirmation is True:
                continue

            usermenu.append({
                'path': reverse(urlname),
                'title': config.get('title', 'No title'),
                'active': ' active' if urlname == self.usermenu_item else '',
            })
        return usermenu

    def get_context_data(self, **kwargs):
        context = super(AccountPageMixin, self).get_context_data(**kwargs)
        context['usermenu'] = self.get_usermenu()
        return context


class UserObjectMixin(object):
    """Mixin that returns the current user as object for views using SingleObjectMixin."""

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


class RegistrationView(AntiSpamMixin, AnonymousRequiredMixin, HomepageViewMixin, CreateView):
    form_class = CreateUserForm
    model = User
    rate_activity = ACTIVITY_REGISTER
    success_url = reverse_lazy('account:detail')
    template_name_suffix = '_register'

    def dispatch(self, request, *args, **kwargs):
        if not settings.REGISTER_HOSTS:
            context = {}
            return TemplateResponse(request, 'account/no_registration.html', context)
        return super(RegistrationView, self).dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        request = self.request
        self.ratelimit(request)
        address = request.META['REMOTE_ADDR']
        lang = request.LANGUAGE_CODE
        base_url = '%s://%s' % (request.scheme, request.get_host())

        if BlockedEmail.objects.is_blocked(form.cleaned_data['email']):
            BlockedIpAddress.objects.block(address)
            raise BlockedException(_('You cannot register with this email address.'))

        with transaction.atomic(), version(comment='User created'):
            response = super(RegistrationView, self).form_valid(form)
            user = self.object

            # Compute the normalized email address
            user.normalized_email = normalize_email(user.email)

            # save default language
            user.default_language = lang
            user.save()

            # log user creation, display help message.
            user.log(gettext_noop('Account created.'), address=address)
            AddressActivity.objects.log(request, ACTIVITY_REGISTER, user=user, note=user.email)
            stat(STAT_REGISTER)

            messages.success(request, _(
                """Successfully created the account %(username)s. A confirmation email was
just sent to the email address you provided (%(email)s). Before you can use
your account, you must click on the confirmation link in that email.""") % {
                    'username': user.username,
                    'email': user.email,
            })

            user.backend = settings.AUTHENTICATION_BACKENDS[0]
            login(self.request, user)

        task = send_confirmation_task.si(
            user_pk=user.pk, purpose=PURPOSE_REGISTER, language=lang, address=address,
            to=user.email, base_url=base_url, hostname=request.site['NAME'])

        # Store GPG key if any
        fp, key = form.get_gpg_data()
        if fp or key:
            gpg_task = add_gpg_key_task.si(
                user_pk=user.pk, address=address, fingerprint=fp, key=key)
            task = chain(gpg_task, task)
        task.delay()

        return response


class ConfirmationMixin(object):
    _key = None

    @property
    def key(self):
        if self._key is None:
            try:
                self._key = self.queryset.valid().get(key=self.kwargs['key'])
            except Confirmation.DoesNotExist:
                name, ext = os.path.splitext(self.template_name)
                template = '%s_not_found%s' % (name, ext)
                self._key = TemplateResponse(self.request, template, {}, status=404)

        return self._key

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

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['user'] = self.get_form_user()
        return kwargs


class KeyUserConfirmationMixin(ConfirmationMixin):
    """A mixin for confirmation views where the user comes from the confirmation key.

    This is the case when the user is not logged in, e.g. during password reset.
    """
    def get_form_user(self):
        key = self.key
        if isinstance(key, HttpResponse):
            return None
        else:
            return key.user


class ConfirmRegistrationView(KeyUserConfirmationMixin, FormView):
    """View for confirming a registration e-mail."""

    form_class = ConfirmRegistrationForm
    queryset = _confirmation_qs.purpose(PURPOSE_REGISTER)
    success_url = reverse_lazy('account:detail')
    template_name = 'account/user_register_confirm.html'

    def form_valid(self, form):
        key = self.key
        if isinstance(key, HttpResponse):
            return key

        request = self.request
        address = request.META['REMOTE_ADDR']
        password = form.cleaned_data['new_password1']

        with transaction.atomic(), version(comment="email address confirmed"):
            key.user.confirmed = timezone.now()
            key.user.created_in_backend = True
            key.user.save()

            if request.user != key.user:
                logout(request)  # logout any previous user
                key.user.backend = settings.AUTHENTICATION_BACKENDS[0]
                login(request, key.user)

            # Actually create the user on the XMPP server
            xmpp_backend.create_user(username=key.user.node, domain=key.user.domain,
                                     password=password, email=key.user.email)

            key.user.log(gettext_noop('Email address %(email)s confirmed.'), address,
                         email=key.user.email)
            # TODO: More meaningful help message on a webchat, usable clients, follow updates, ...
            messages.success(request, _(
                "Successfully confirmed your email address. You can now use your account."))
            stat(STAT_REGISTER_CONFIRMED)

            # Delete the registration key
            key.delete()

        return super(ConfirmRegistrationView, self).form_valid(form)


class LoginView(AntiSpamMixin, AnonymousRequiredMixin, HomepageViewMixin, FormView):
    """Class-based adaption of django.contrib.auth.views.login.

    We duplicate the functionality here because we want to redirect the user to the account
    detail page if already logged in. The default view always views the login form.
    """
    REDIRECT_FIELD_NAME = 'next'
    form_class = LoginForm
    rate_activity = ACTIVITY_FAILED_LOGIN
    template_name = 'account/user_login.html'

    def form_valid(self, form):
        redirect_to = self.request.POST.get(self.REDIRECT_FIELD_NAME, '')

        # Ensure the user-originating redirection url is safe.
        if not url_has_allowed_host_and_scheme(url=redirect_to, allowed_hosts=[self.request.get_host()]):
            redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)

        user = form.get_user()
        now = timezone.now()

        # Okay, security check complete. Log the user in.
        login(self.request, user)
        user.last_activity = now
        xmpp_backend.set_last_activity(user.node, user.domain,
                                       status='Logged in via homepage.',
                                       timestamp=now)
        user.save()
        return HttpResponseRedirect(redirect_to)

    def form_invalid(self, form):
        self.ratelimit(self.request)
        stat(STAT_FAILED_LOGIN)
        return super(LoginView, self).form_invalid(form)

    def get_context_data(self, **kwargs):
        context = super(LoginView, self).get_context_data(**kwargs)
        context[self.REDIRECT_FIELD_NAME] = self.request.POST.get(
            self.REDIRECT_FIELD_NAME, self.request.GET.get(self.REDIRECT_FIELD_NAME, ''))
        return context


class UserView(LoginRequiredMixin, AccountPageMixin, UserObjectMixin, DetailView):
    """Main user settings view (/account)."""

    usermenu_item = 'account:detail'
    requires_confirmation = False


class ResetPasswordView(AntiSpamMixin, AnonymousRequiredMixin, HomepageViewMixin, FormView):
    form_class = ResetPasswordForm
    rate_activity = ACTIVITY_RESET_PASSWORD
    template_name = 'account/user_password_reset.html'

    def form_valid(self, form):
        self.ratelimit(self.request)

        user = None
        try:
            user = User.objects.confirmed().get(username=form.cleaned_data['username'])
        except User.DoesNotExist:
            pass  # checked in next branch

        if user is None or not xmpp_backend.user_exists(user.node, user.domain):
            code = 'not-found'
            error = forms.ValidationError(form.fields['username'].error_messages[code], code=code)
            form.add_error('username', error)
            return self.form_invalid(form)

        request = self.request
        address = request.META['REMOTE_ADDR']
        lang = request.LANGUAGE_CODE
        base_url = '%s://%s' % (request.scheme, request.get_host())

        user.log(gettext_noop('Requested password reset.'), address)
        AddressActivity.objects.log(request, ACTIVITY_RESET_PASSWORD, user=user)
        stat(STAT_RESET_PASSWORD)

        send_confirmation_task.delay(
            user_pk=user.pk, purpose=PURPOSE_RESET_PASSWORD, language=lang, address=address,
            to=user.email, base_url=base_url, hostname=request.site['NAME'])

        return self.render_to_response(self.get_context_data(form=form))


class ConfirmResetPasswordView(KeyUserConfirmationMixin, FormView):
    form_class = ConfirmResetPasswordForm
    queryset = _confirmation_qs.purpose(PURPOSE_RESET_PASSWORD)
    success_url = reverse_lazy('account:detail')
    template_name = 'account/user_password_reset_confirm.html'

    def form_valid(self, form):
        key = self.key
        if isinstance(key, HttpResponse):
            return key

        request = self.request
        address = request.META['REMOTE_ADDR']
        now = timezone.now()

        with transaction.atomic():
            xmpp_backend.set_password(username=key.user.node, domain=key.user.domain,
                                      password=form.cleaned_data['new_password1'])
            xmpp_backend.set_last_activity(key.user.node, key.user.domain, timestamp=now)

            key.user.last_activity = now
            key.user.log(gettext_noop('Set new password.'), address)
            messages.success(request, _('Successfully changed your password.'))
            stat(STAT_RESET_PASSWORD_CONFIRMED)

            key.user.backend = settings.AUTHENTICATION_BACKENDS[0]
            key.user.save()
            login(self.request, key.user)
            key.delete()

        return super(ConfirmResetPasswordView, self).form_valid(form)


class SessionsView(LoginRequiredMixin, AccountPageMixin, TemplateView):
    template_name = 'account/sessions.html'
    usermenu_item = 'account:sessions'

    def get_context_data(self, **kwargs):
        context = super(SessionsView, self).get_context_data(**kwargs)
        user = self.request.user
        context['sessions'] = xmpp_backend.user_sessions(user.node, user.domain)
        return context


class NotificationsView(LoginRequiredMixin, AccountPageMixin, UpdateView):
    form_class = NotificationsForm
    requires_email = True
    template_name = 'account/notifications.html'
    usermenu_item = 'account:notifications'

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

    def form_valid(self, form):
        self.object = form.save()
        return HttpResponse('Ok.')


class SetPasswordView(LoginRequiredMixin, AccountPageMixin, FormView):
    form_class = PasswordChangeForm
    success_url = reverse_lazy('account:detail')
    template_name = 'account/set_password.html'
    usermenu_item = 'account:set_password'

    def get_form_kwargs(self):
        kwargs = super(SetPasswordView, self).get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs

    def form_valid(self, form):
        request = self.request

        form.save()

        request.user.log(gettext_noop('Set new password.'), request.META['REMOTE_ADDR'])
        AddressActivity.objects.log(request, ACTIVITY_SET_PASSWORD)
        stat(STAT_SET_PASSWORD)
        messages.success(request, _('Successfully changed your password.'))
        return super(SetPasswordView, self).form_valid(form)


class SetEmailView(AntiSpamMixin, LoginRequiredMixin, AccountPageMixin, FormView):
    form_class = SetEmailForm
    success_url = reverse_lazy('account:detail')
    template_name = 'account/user_set_email.html'
    usermenu_item = 'account:set_email'
    requires_confirmation = False
    rate_activity = ACTIVITY_RESEND_CONFIRMATION

    def handle_new_account(self, request, form):
        self.ratelimit(request)
        user = request.user

        address = request.META['REMOTE_ADDR']
        lang = request.LANGUAGE_CODE
        base_url = '%s://%s' % (request.scheme, request.get_host())

        fp, key = form.get_gpg_data()

        with transaction.atomic():
            request.user.email = form.cleaned_data['email']

            # Compute the normalized email address
            request.user.normalized_email = normalize_email(user.email)

            request.user.save()

            # Remove existing confirmation keys for different email addresses, otherwise the user might be
            # able to get a confirmed address with the old key, but the new address is already set above
            Confirmation.objects.filter(user=user, purpose=PURPOSE_REGISTER).exclude(to=user.email).delete()

            # If the user has not set any keys, remove them before sending.
            if not fp and not key:
                user.gpg_keys.all().delete()

            # log user creation, display help message.
            user.log(gettext_noop('Resent confirmation.'), address=address)
            AddressActivity.objects.log(request, ACTIVITY_RESEND_CONFIRMATION, user=user, note=user.email)
            stat(STAT_RESEND_CONFIRMATION)
            messages.success(request, _("Resent confirmation email to %(email)s.") % {'email': user.email})

        task = send_confirmation_task.si(
            user_pk=user.pk, purpose=PURPOSE_REGISTER, language=lang, address=address,
            to=user.email, base_url=base_url, hostname=request.site['NAME'])

        # Store GPG key if any
        if fp or key:
            # TODO: If the user is not confirmed yet and a key is added, this does not seem to work
            gpg_task = add_gpg_key_task.si(
                user_pk=user.pk, address=address, fingerprint=fp, key=key)
            task = chain(gpg_task, task)

        # User has not set a new GPG key, so we remove any keys
        task.delay()

        return HttpResponseRedirect(reverse('account:detail'))

    def form_valid(self, form):
        request = self.request
        user = request.user

        if not user.created_in_backend:
            return self.handle_new_account(request, form)

        address = request.META['REMOTE_ADDR']
        to = form.cleaned_data['email']

        # If REQUIRE_UNIQUE_EMAIL is true, validate uniquenss

        qs = User.objects.exclude(pk=user.pk).filter(email=to)
        if settings.REQUIRE_UNIQUE_EMAIL and qs.exists():
            form.add_error('email', _('Email address is already used by another account.'))
            return self.form_invalid(form)

        lang = request.LANGUAGE_CODE
        base_url = '%s://%s' % (request.scheme, request.get_host())

        fp, key = form.get_gpg_data()
        set_email_task.delay(
            user_pk=user.pk, to=to, language=lang, address=address, fingerprint=fp, key=key,
            base_url=base_url, hostname=request.site['NAME'])

        messages.success(request, _(
            'We sent you an email to your new email address (%(email)s). Click on the link in it '
            'to confirm it.') % {'email': to})
        user.log(gettext_noop('Requested change of email address to %(email)s.'), address,
                 email=to)
        stat(STAT_SET_EMAIL)
        AddressActivity.objects.log(request, ACTIVITY_SET_EMAIL, note=to)

        return super(SetEmailView, self).form_valid(form)


class ConfirmSetEmailView(LoginRequiredMixin, RedirectView):
    """Confirmation view for a user setting his email address, redirects to account detail page."""

    pattern_name = 'account:detail'  # where to redirect to
    queryset = _confirmation_qs.purpose(PURPOSE_SET_EMAIL)

    def get_redirect_url(self, *args, **kwargs):
        request = self.request
        user = request.user
        qs = self.queryset.filter(user=user)
        key = get_object_or_404(qs, key=kwargs['key'])

        user.email = key.to
        # Compute the normalized email address
        user.normalized_email = normalize_email(user.email)

        user.confirmed = timezone.now()

        with transaction.atomic():
            # Update list of GPG keys
            user.gpg_keys.all().delete()
            gpg_keys = key.payload.get('gpg_recv_pub')
            if gpg_keys:
                add_gpg_key_task.delay(user_pk=user.pk, address=key.address.address, key=gpg_keys)

            user.save()
            key.delete()

            messages.success(request,
                             _('Confirmed email address change to %(email)s.')
                             % {'email': user.email, })
            user.log(gettext_noop('Confirmed email address change to %(email)s.'),
                     request.META['REMOTE_ADDR'], email=key.to)
            stat(STAT_SET_EMAIL_CONFIRMED)

            return super(ConfirmSetEmailView, self).get_redirect_url()


class HttpUploadView(LoginRequiredMixin, AccountPageMixin, UserObjectMixin, DetailView):
    usermenu_item = 'account:xep0363'
    template_name = 'account/xep0363.html'

    def get_context_data(self, **kwargs):
        context = super(HttpUploadView, self).get_context_data(**kwargs)
        user = context['object']
        qs = Upload.objects.filter(jid=user.username)

        # If a client requests an upload slot but never uploads the file, file field will be empty
        context['uploads'] = qs.exclude(file='')
        limit_config = get_config(user.username)
        if limit_config is False:
            context['limits'] = False
        elif limit_config == {}:
            context['limits'] = 'no-limits'
        else:
            context['limits'] = []
            context['can_upload'] = True
            now = timezone.now()

            for key, value in limit_config.items():
                if key == 'max_total_size':
                    current = qs.aggregate(total=Sum('size'))['total'] or 0
                    limit = max(value - current, 0)

                    row = {
                        'header': _('Total bytes'),
                        'value': filesizeformat(value),
                        'current': filesizeformat(current),
                        'left': filesizeformat(limit),
                    }

                    if limit == 0:
                        context['can_upload'] = False
                        row['exceeded'] = True

                elif key == 'max_file_size':
                    row = {
                        'header': _('Maximum file size'),
                        'value': filesizeformat(value),
                    }
                elif key == 'bytes_per_timedelta':
                    delta = value['delta']
                    quota = value['bytes']
                    current = qs.filter(created__gt=now - delta).aggregate(total=Sum('size'))['total'] or 0
                    limit = max(quota - current, 0)

                    row = {
                        'header': _('Bytes/%(delta)s') % {'delta': format_timedelta(delta), },
                        'value': filesizeformat(quota),
                        'current': filesizeformat(current),
                        'left': filesizeformat(limit),
                    }
                    if limit == 0:
                        context['can_upload'] = False
                        row['exceeded'] = True
                elif key == 'uploads_per_timedelta':
                    delta = value['delta']
                    quota = value['uploads']
                    current = qs.filter(created__gt=now - delta).count()
                    limit = max(quota - current, 0)

                    row = {
                        'header': _('Uploads/%(delta)s') % {'delta': format_timedelta(delta), },
                        'value': quota,
                        'current': current,
                        'left': limit,
                    }

                    if limit == 0:
                        context['can_upload'] = False
                        row['exceeded'] = True
                else:
                    log.error('Unknown limit encountered: %s', key)
                    continue

                context['limits'].append(row)

        return context


class GpgView(LoginRequiredMixin, AccountPageMixin, UserObjectMixin, DetailView):
    """Main user settings view (/account)."""

    usermenu_item = 'account:gpg'
    template_name = 'account/user_gpg.html'


class AddGpgView(LoginRequiredMixin, AccountPageMixin, FormView):
    form_class = AddGpgForm
    success_url = reverse_lazy('account:gpg')
    template_name = 'account/add_gpg.html'
    usermenu_item = 'account:gpg'

    def form_valid(self, form):
        request = self.request
        user = request.user
        address = request.META['REMOTE_ADDR']

        fp, key = form.get_gpg_data()
        add_gpg_key_task.delay(user_pk=user.pk, address=address, fingerprint=fp, key=key)

        messages.success(request, _('Processing new GPG key, it will be added in a moment.'))

        return super(AddGpgView, self).form_valid(form)


class RecentActivityView(LoginRequiredMixin, AccountPageMixin, UserObjectMixin, DetailView):
    """Main user settings view (/account)."""

    requires_confirmation = False
    template_name = 'account/user_recent_activity.html'
    usermenu_item = 'account:log'

    def get_context_data(self, **kwargs):
        context = super(RecentActivityView, self).get_context_data(**kwargs)
        context['logentry_expires'] = settings.USER_LOGENTRY_EXPIRES
        return context


class DeleteAccountView(LoginRequiredMixin, AccountPageMixin, FormView):
    usermenu_item = 'account:delete'
    form_class = DeleteAccountForm
    template_name = 'account/delete.html'
    requires_email = True

    def form_valid(self, form):
        password = form.cleaned_data['password']
        request = self.request
        user = request.user

        if not xmpp_backend.check_password(user.node, user.domain, password=password):
            form.add_error('password', _('The password is incorrect.'))
            return self.form_invalid(form)

        address = request.META['REMOTE_ADDR']
        lang = request.LANGUAGE_CODE
        base_url = '%s://%s' % (request.scheme, request.get_host())

        send_confirmation_task.delay(
            user_pk=user.pk, purpose=PURPOSE_DELETE, language=lang, address=address,
            to=user.email, base_url=base_url, hostname=request.site['NAME'])

        messages.success(request, _(
            'We sent you an email to %(email)s to confirm your request.') %
            {'email': user.email, })
        user.log(gettext_noop('Requested deletion of account.'), address)
        stat(STAT_DELETE_ACCOUNT)
        AddressActivity.objects.log(request, ACTIVITY_SET_EMAIL, note=user.email)

        return HttpResponseRedirect(reverse('account:detail'))


class ConfirmDeleteAccountView(LoginRequiredMixin, AccountPageMixin, FormView):
    usermenu_item = 'account:delete'
    form_class = DeleteAccountForm
    template_name = 'account/delete_confirm.html'
    queryset = _confirmation_qs.purpose(PURPOSE_DELETE)

    def form_valid(self, form):
        password = form.cleaned_data['password']
        request = self.request
        user = request.user

        # Check the password of the user again
        if not xmpp_backend.check_password(user.node, user.domain, password=password):
            form.add_error('password', _('The password is incorrect.'))
            return self.form_invalid(form)

        # Verify the confirmation key
        key = get_object_or_404(self.queryset.filter(user=user), key=self.kwargs['key'])

        # Log the user out, delete data
        logout(request)
        xmpp_backend.remove_user(user.node, user.domain)
        key.delete()
        user.delete()
        stat(STAT_DELETE_ACCOUNT_CONFIRMED)

        return HttpResponseRedirect(reverse('blog:home'))


class UserAvailableView(View):
    """Ajax view to check if a username is still available (used during registration)."""

    def post(self, request):
        # Note: XMPP usernames are case insensitive
        username = request.POST.get('username', '').strip().lower()
        domain = request.POST.get('domain', '').strip().lower()
        jid = '%s@%s' % (username, domain)

        cache_key = 'exists_%s' % jid
        exists = cache.get(cache_key)
        if exists is True:
            return HttpResponse('', status=409)
        elif exists is False:
            return HttpResponse('')

        # Check if the user exists in the database
        if User.objects.filter(username=jid).exists():
            cache.set(cache_key, True, 30)
            return HttpResponse('', status=409)
        else:
            cache.set(cache_key, False, 30)
            return HttpResponse('')


class ResendRegistrationConfirmation(LoginRequiredMixin, View):
    def get(self, request):
        if request.user.confirmed is not None:
            return HttpResponseForbidden(_("Your email address is already confirmed."))


class StopUserSessionView(LoginRequiredMixin, View):
    def delete(self, request, resource):
        user = request.user

        xmpp_backend.stop_user_session(
            user.node, user.domain, resource,
            str(_('This session was forcefully stopped via our homepage.'))
        )
        return HttpResponse('ok')


class DeleteHttpUploadView(LoginRequiredMixin, SingleObjectMixin, View):
    queryset = Upload.objects.all()

    def get_object(self, queryset=None):
        if queryset is None:
            queryset = self.get_queryset()

        queryset = queryset.filter(jid=self.request.user.get_username())

        return super(DeleteHttpUploadView, self).get_object(queryset=queryset)

    def delete(self, request, pk):
        obj = self.get_object()
        obj.file.delete()
        obj.delete()
        return HttpResponse('ok')


class ManageGpgView(LoginRequiredMixin, SingleObjectMixin, View):
    queryset = Upload.objects.all()

    def get_object(self, queryset=None):
        queryset = self.request.user.gpg_keys.all()
        return super(ManageGpgView, self).get_object(queryset=queryset)

    def get(self, request, pk):
        key = self.get_object()
        address = request.META['REMOTE_ADDR']
        add_gpg_key_task.delay(user_pk=self.request.user.pk, address=address,
                               fingerprint=key.fingerprint)
        return JsonResponse({
            'status': 'success',
            'message': _('Refreshing GPG key...'),
        })

    def delete(self, request, pk):
        self.get_object().delete()
        return HttpResponse('ok')
