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

import os
import uuid
import logging
from functools import partial

from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login, update_session_auth_hash, REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import password_change as auth_password_change
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render, resolve_url
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

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')


# Use our own form to save the last time the password was changed
# pylint: disable=invalid-name
password_change = partial(auth_password_change, password_change_form=PasswordChangeForm)


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()
            if self.request.user == user:
                update_session_auth_hash(self.request, user)

        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()
        form.save_m2m()

        # 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(
            request=request,
            template_name='accounts/users_import.html',
            context=locals(),  # from dimitri with love
        )

    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))
