"""This module contains the views of the ``accounts`` app"""

import os
import uuid
import logging

from django.conf import settings
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash, REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render_to_response, resolve_url
from django.template import RequestContext
from django.template.response import TemplateResponse
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.http import is_safe_url
from django.utils.translation import ugettext_lazy as _
from django.views import generic
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.csrf import csrf_protect

import waffle
from django_filters.views import FilterView

from mtp.apps.accounts.forms import MTPAuthenticationForm
from mtp.apps.common.notifications.tasks import send_async
from mtp.apps.common.utils import handle_uploaded_file
from mtp.apps.common.views_utils import (
    RestrictToSuperuserMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
    SyncableUndeleteView,
)
from mtp.apps.trainings.models import TrainingSession
from mtp.apps.translation.utils import set_language

from .filters import MTPUserFilterSet
from .forms import (
    GroupForm, PasswordChangeForm,
    UploadFileForm, UserForm,
    UserImportForm, UserBulkForm,
    PotentialDuplicateUserSearchForm, PotentialOriginalUserSearchForm
)
from .models import Group, MTPUser
from .utils import init_user, XLSTraineeImporter


logger = logging.getLogger('mtp.apps.accounts')


@sensitive_post_parameters()
@csrf_protect
@login_required
def password_change(request,
                    template_name='registration/password_change_form.html',
                    post_change_redirect=None,
                    password_change_form=PasswordChangeForm,
                    current_app=None, extra_context=None):
    """
    Display the password change form and handles the password change action.

    This is lifted from the Django source code because we need to implement
    extra checks there, and unfortunately there are no class-based views (yet?)
    in django.contrib.auth, so we can't properly extend it... :-/
    """
    if post_change_redirect is None:
        post_change_redirect = reverse('password_change_done')
    else:
        post_change_redirect = resolve_url(post_change_redirect)

    if request.method == 'POST':
        form = password_change_form(user=request.user, data=request.POST)
        if form.is_valid():
            form.save()

            # === MTP-specific code starts here ===
            user = form.user
            user.last_changed_password = timezone.now()
            user.save()
            # === MTP-specific code stops here ===

            # Updating the password logs out all other sessions for the user
            # except the current one if
            # django.contrib.auth.middleware.SessionAuthenticationMiddleware
            # is enabled.
            update_session_auth_hash(request, form.user)
            return HttpResponseRedirect(post_change_redirect)
    else:
        form = password_change_form(user=request.user)

    context = {
        'form': form,
        'title': _('Password change'),
    }
    if extra_context is not None:
        context.update(extra_context)
    return TemplateResponse(request, template_name, context,
                            current_app=current_app)


class UserBaseView:
    """Base view for all views related to the users

    Define the ``current_main_section`` in the context, and the queryset.

    """

    model = MTPUser
    pk_url_kwarg = 'user_uuid'

    def get_context_data(self, **kwargs):
        """Add ``current_main_section`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(
            current_main_section='users',
            **kwargs
        )

    def get_queryset(self):
        """Return the users list the current user is able to see

        Returns
        -------
        ``django.db.models.QuerySet``
            The django queryset of ``MTPUser``

        """

        return MTPUser.objects.filter(is_superuser=False)

    def get_success_url(self):
        """Get the default success_url"""

        return self.model.get_list_url()


class UserListView(RestrictToSuperuserMixin, UserBaseView, FilterView):
    """View to list the ``MTPUser``"""

    paginate_by = 30
    template_name = 'cms/accounts/mtpuser_list.html'
    filterset_class = MTPUserFilterSet

    def get_queryset(self):
        """Order the QuerySet by the ``username``"""

        return super().get_queryset().order_by('username')


class BaseUserMergeView(SuccessMessageMixin, RestrictToSuperuserMixin, UserBaseView):
    """Base view for all views related to the users "merge" functionality"""

    def get_success_url(self):
        """Get the default success_url"""

        return reverse('accounts:user-merge-list')

    def get_context_data(self, **kwargs):
        """Add the ``search_duplicate_form`` and ``search_original_form`` forms to the context

        For the parameters, see ``django.forms.generic.base.ContextMixin``

        """

        return super().get_context_data(
            search_duplicate_form=PotentialDuplicateUserSearchForm(),
            search_original_form=PotentialOriginalUserSearchForm(),
            **kwargs
        )


class UserMergeDetailView(BaseUserMergeView, generic.DetailView):
    """View to show a single user with the "potential duplicates"."""

    template_name = 'cms/accounts/mtpuser_merge_detail.html'

    def get_object(self, queryset=None):
        """Get the object from the GET params

        For the parameters, see ``django.forms.generic.base.SingleObjectMixin``

        Returns
        -------
        MTPUser

        Raises
        ------
        HTTP404
            If the user do not exists

        """

        return get_object_or_404(
            MTPUser.objects.non_deleted(),
            pk=self.request.GET['user_uuid']
        )


class UserMergeListView(BaseUserMergeView, generic.ListView):
    """View to list of user with "potential duplicates"."""

    paginate_by = 30
    template_name = 'cms/accounts/mtpuser_merge_list.html'

    def get_queryset(self):
        """Get the users with "potential duplicates" or with existing duplicates

        Returns
        -------
        MTPUserQuerySet

        """

        return MTPUser.objects.non_deleted().filter(
            has_potential_duplicates=True
        ).order_by('-username')


class UserMergeView(BaseUserMergeView, generic.DetailView):
    """View to "flag" a user as a duplicate of another user"""

    success_message = _('User successfully merged')
    template_name = 'cms/accounts/mtpuser_merge.html'

    @cached_property
    def duplicate_user(self):
        """Get the ``duplicate_user`` from the url

        Returns
        -------
        MTPUser

        Raises
        ------
        HTTP404
            If the user do not exists

        """

        return get_object_or_404(
            MTPUser.objects.non_deleted(),
            pk=self.request.GET['duplicate_user_uuid']
        )

    def get_context_data(self, **kwargs):
        """Add the ``original_user`` to the context

        For the parameters, see ``django.forms.generic.base.ContextMixin``

        """

        return super().get_context_data(
            duplicate_user=self.duplicate_user,
            **kwargs
        )

    def post(self, request, *args, **kwargs):  # pylint: disable=unused-argument
        """Flag the given user as a duplicate

        For the parameters, see ``django.views.generic.View``

        """

        original_user = self.get_object()

        original_user.flag_user_as_duplicate(self.duplicate_user, request.user)

        response = HttpResponseRedirect(self.get_success_url())

        super().send_message()

        return response


class UserRemoveAsPotentialDuplicateView(BaseUserMergeView, generic.DetailView):
    """View to "unflag" a user who was automatically flagged as a duplicate"""

    success_message = _('User was successfully removed from the potential duplicates')
    template_name = 'cms/accounts/mtpuser_merge_notaduplicate.html'

    @cached_property
    def duplicate_user(self):
        """Get the ``duplicate_user`` from the url

        Returns
        -------
        MTPUser
            The duplicate user

        Raises
        ------
        HTTP404
            If the user do not exists

        """

        return get_object_or_404(
            MTPUser.objects.non_deleted(),
            pk=self.request.GET['duplicate_user_uuid']
        )

    def get_context_data(self, **kwargs):
        """Add the ``original_user`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``

        """

        return super().get_context_data(
            duplicate_user=self.duplicate_user,
            **kwargs
        )

    def post(self, *args, **kwargs):  # pylint: disable=unused-argument
        """Unflag the current user as a duplicate of the original user

        For the parameters, see ``django.views.generic.View``

        """

        original_user = self.get_object()

        original_user.unflag_user_as_potential_duplicate(self.duplicate_user)

        response = HttpResponseRedirect(self.get_success_url())

        super().send_message()

        return response


class UserEditBaseView(SuccessMessageMixin, UserBaseView):
    """Base View to create or edit a ``MTPUser`` instance"""

    form_class = UserForm

    def get_queryset(self):
        if self.request.user.is_superuser:
            return MTPUser.objects.non_deleted()
        else:
            return super().get_queryset()

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()

        kwargs['current_user'] = self.request.user
        return kwargs


class UserUpdateView(UserEditBaseView, generic.UpdateView):
    """View to edit a ``MTPUser`` instance"""

    success_message = _('The user was successfully updated.')

    def get_queryset(self):
        """Get the users the current user can edit

        The users are filtering accordingly to the current user type:

        - superuser: Can edit all the users
        - not superusers: Can only edit themselves
        """

        if not self.request.user.is_superuser:
            return super().get_queryset().filter(
                pk=self.request.user.pk
            )
        else:
            return super().get_queryset()

    def form_valid(self, form):
        """Update the user, and change his password if applicable.

        Parameters
        ----------
        form : instance of ``mtp.apps.accounts.forms.UserForm``
            The validated form to save.

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the same page if the edited-user is the same as the current user,
            or a redirect to the user list view if that's not the case.

        """

        password = form.cleaned_data.get('pass1')
        user = form.save()

        if password:
            user.set_password(password)
            user.save()

        self.send_message(form.cleaned_data)

        # Change interface language according natural_language of user
        lang_code = form.cleaned_data.get('natural_language')

        if self.request.user == user:
            response = HttpResponseRedirect(self.request.user.get_edit_url())
            set_language(lang_code, self.request, response)
        else:
            response = HttpResponseRedirect(self.get_success_url())

        return response


class UserCreateView(RestrictToSuperuserMixin, UserEditBaseView, generic.CreateView):
    """View to create a ``MTPUser`` instance"""

    success_message = _('The user was successfully created.')

    def form_valid(self, form):
        """Create the new user, and send a welcome mail if applicable.

        Parameters
        ----------
        form : instance of ``mtp.apps.accounts.forms.UserForm``
            The validated form to save.

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the user list view or the create user view

        Notes
        -----
        This method doesn't call ``super`` but implements all the necessary stuff (saving and
        redirecting)

        """

        # We need to know if the user is a trainer
        is_trainer = bool(self.request.GET.get('trainer', False))

        password = form.cleaned_data.get('pass1')
        user = form.save(commit=False)

        user.created_from = MTPUser.ORIGINS.CMS_FORM
        user.save()

        # TODO: Do this init_user is still needed ?
        user, password = init_user(user, password, None, is_trainer)

        # Send the welcome mail if the instance is a ``MASTER``
        if settings.INSTANCE_IS_MASTER and waffle.switch_is_active('enable_user_welcome_mail'):
            send_async.delay([user], 'user_welcome')

        self.send_message(form.cleaned_data)

        if '_addanother' in self.request.POST:
            return HttpResponseRedirect(self.model.get_create_url(trainer=is_trainer))
        else:
            return HttpResponseRedirect(self.get_success_url())


class UserDeleteView(SuccessMessageMixin, UserBaseView, SyncableDeleteView):
    """View to soft-delete a ``MTPUser`` instance"""

    success_message = _('The user was successfully deactivated.')


class UserUndeleteView(SuccessMessageMixin, UserBaseView, SyncableUndeleteView):
    """View to un-soft-delete a ``MTPUser`` instance"""

    success_message = _('The user was successfully activated.')


class UserBulkBaseView(UserBaseView, generic.FormView):
    """Base view for bulk actions on users"""

    form_class = UserBulkForm

    def get_form_kwargs(self):
        """Attach current_user to Form"""
        kwargs = super().get_form_kwargs()

        kwargs['current_user'] = self.request.user
        return kwargs

    def do_action(self, user):
        """The bulk action to apply to the users, for each user"""
        pass

    def form_valid(self, form):
        """Do the bulk action for each user after form is valid

        For the parameters, see ``django.forms.generic.edit.FormMixin``
        """
        for user in form.cleaned_data['users']:
            self.do_action(user)
        messages.success(self.request, self.success_message)
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form):
        """Redirect with an error message to avoid showing the modal

        For the parameters, see ``django.forms.generic.edit.FormMixin``
        """
        logger.error('UserBulkBaseView error while validating form: %s', form.errors.as_data())
        messages.error(self.request, _('An error occured.'))
        return HttpResponseRedirect(self.get_success_url())


class UserBulkDeleteView(UserBulkBaseView):
    """View to soft-delete massively ``MTPUser`` instances"""

    success_message = _('The users were successfully deactivated.')
    template_name = 'cms/accounts/mtpuser_confirm_bulk_delete.html'

    def do_action(self, user):
        user.remove()


class UserBulkUndeleteView(UserBulkBaseView):
    """View to un-soft-delete massively ``MTPUser`` instances"""

    success_message = _('The users were successfully activated.')
    template_name = 'cms/accounts/mtpuser_confirm_bulk_undelete.html'

    def do_action(self, user):
        user.unremove()


class UserWelcomeView(UserBaseView, generic.DetailView):
    """View to send the welcome mail to a user"""

    def get(self, request, *args, **kwargs):
        """Send the welcome mail to the user and redirect the user

        For the parameters, see ``django.views.generic.View``"""

        user = self.get_object()

        # Send the welcome notification, without the user password
        send_async.delay([user], 'user_welcome', {
            'password': '********'
        })

        messages.success(request,
                         _('A welcome mail has been successfully resent to %(username)s.' %
                           {'username': user.username}))

        return HttpResponseRedirect(self.get_success_url())


@login_required
def users_import(request, session_id=None):
    if session_id:
        session = get_object_or_404(TrainingSession, pk=session_id)
    else:
        session = None

    if request.user.is_staff:
        if request.method == "POST":
            fileForm = UploadFileForm(request.POST, request.FILES)
            userForm = UserImportForm(request.POST)
            if userForm.is_valid():
                filename = userForm.cleaned_data['filename']
                save_path = os.path.join(settings.MEDIA_ROOT, "user_import")
                save_realname = os.path.join(save_path, filename)
                users = XLSTraineeImporter(
                    save_realname
                ).import_users(
                    commit=True,
                    send_mail=userForm.cleaned_data['send_mail']
                )
                if session:
                    session.users.add(*users)
                    session.save()

                    # for user in users:
                    # send_session_add_mail(user)

                messages.success(request, _(
                    "%d users successfully imported or updated." % (len(users))
                ))
                if session:
                    return redirect('trainings:session-list')
                else:
                    return redirect('accounts:user-list')

            elif fileForm.is_valid():
                save_filename = '%s.xls' % (uuid.uuid4())
                save_path = os.path.join(settings.MEDIA_ROOT, 'user_import')
                save_realname = os.path.join(save_path, save_filename)
                handle_uploaded_file(request.FILES['file'], save_realname)

                users = XLSTraineeImporter(
                    save_realname
                ).import_users()
                if len(users) == 0:
                    fileForm = UploadFileForm()
                    userForm = None
                    message = "No users to import or update with this file."

                else:
                    message = "%d users will be imported with this file." % (len(users))
                    userForm = UserImportForm(initial={'filename': save_filename})
                    fileForm = None
            else:
                userForm = None

        else:
            fileForm = UploadFileForm()
            userForm = None

        return render_to_response('accounts/users_import.html',
                                  locals(), context_instance=RequestContext(request))

    else:
        return redirect('accounts:user-list')


class GroupBaseView:
    """Base view for all views related to the groups

    Define the ``current_main_section`` in the context, and the queryset.

    """

    model = Group
    pk_url_kwarg = 'group_uuid'

    def get_context_data(self, **kwargs):
        """Add ``current_main_section`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(
            current_main_section='groups',
            **kwargs
        )

    def get_queryset(self):
        """Return the group list the current user is able to see

        The superusers can view and edit all the groups.
        The non-superusers can only view and edit the groups they are managing.

        Returns
        -------
        ``django.db.models.QuerySet``
            The django queryset of ``Group``

        """

        group_qs = Group.objects.non_deleted()

        if self.request.user.is_superuser:
            return group_qs
        else:
            return group_qs.managed_by(self.request.user)

    def get_success_url(self):
        """Get the default success_url"""

        return self.model.get_list_url()


class GroupListView(GroupBaseView, generic.ListView):
    """View to list the ``mtp.apps.accounts.models.Group`` instances."""

    paginate_by = 30


class GroupEditBaseView(SuccessMessageMixin, GroupBaseView):
    """Base View to edit a ``mtp.apps.accounts.models.Group`` instance."""

    form_class = GroupForm

    def get_success_url(self):
        """Return the url to redirect to when an action is done to a group

        It actually return the url of the group's list.

        """

        if '_addanother' in self.request.POST:
            return self.model.get_create_url()
        else:
            return self.model.get_list_url()


class GroupUpdateView(GroupEditBaseView, generic.UpdateView):
    """View to update a ``mtp.apps.accounts.models.Group`` instance."""

    success_message = _('The group was successfully updated.')


class GroupCreateView(GroupEditBaseView, generic.CreateView):
    """View to create a ``mtp.apps.accounts.models.Group`` instance."""

    success_message = _('The group was successfully created.')


class GroupDeleteView(SuccessMessageMixin, GroupBaseView, SyncableDeleteView):
    """View to soft-delete a ``mtp.apps.accounts.models.Group`` instance."""

    success_message = _('The group was successfully deleted.')


class LoginUserView(generic.FormView):
    template_name = 'registration/login.html'
    form_class = MTPAuthenticationForm

    def get_success_url(self):
        """Get the url to redirect to once successfully authenticated.

        Extracted ``from django.contrib.auth.views.login``

        Basically it check for a ``next`` parameter in ``POST`` or ``GET``, check for
        safety, and use it if OK. If no ``next`` or not a valid one, use the one defined in
        ``settings.LOGIN_REDIRECT_URL``.

        """

        redirect_to = self.request.POST.get(
            REDIRECT_FIELD_NAME,
            self.request.GET.get(REDIRECT_FIELD_NAME, '')
        )

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

        return redirect_to

    def form_valid(self, form):
        user = form.get_user()
        if user is not None and user.is_active:
            login(self.request, user)
            response = super().form_valid(form)
            set_language(user.natural_language, self.request, response)
            return response
        return self.render_to_response(self.get_context_data(form=form))
