import urllib.parse

from django.contrib import messages
from django.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured
from django.db.transaction import atomic
from django.http import HttpResponseRedirect
from django.utils.translation import gettext as _
from django.views.generic.base import TemplateResponseMixin, View
from django.views.generic.edit import FormView, UpdateView
from django_journal.models import Journal

from docbow_project.docbow import app_settings, cbv, forms, models, utils


class ProfileView(cbv.FormWithRequestMixin, cbv.FormWithPostTarget, UpdateView):
    form_class = forms.ProfileForm
    template_name = 'docbow/profile.html'
    prefix = 'profile'
    success_url = '#profile'

    def get_object(self):
        try:
            return models.DocbowProfile.objects.get(user=self.request.user)
        except models.DocbowProfile.DoesNotExist:
            return None

    def is_post_target(self):
        return 'profile-validate' in self.request.POST

    def form_valid(self, form):
        self.request.record('update-profile', 'modified its profile', **form.cleaned_data)
        return super().form_valid(form)


class DelegateView(cbv.FormWithPostTarget, FormView):
    form_class = forms.DelegationForm
    template_name = 'docbow/delegate.html'
    success_url = '#delegate'
    prefix = 'delegate'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def add_journal_to_delegations(self, delegations):
        delegations__to = [delegation.to for delegation in delegations]
        journals = (
            Journal.objects.for_objects(delegations__to)
            .filter(tag__name='login')
            .order_by('-time')[: len(delegations__to) * 5]
        )
        journal_by_users = dict()
        for journal in journals:
            for objectdata in journal.objectdata_set.all():
                if objectdata.tag.name == 'delegate':
                    journal_by_users.setdefault(objectdata.object_id, []).append(journal.time)
        for delegation in delegations:
            delegation.journals = journal_by_users.get(delegation.to.id, [])[:5]

    @property
    def delegations(self):
        qs = models.Delegation.objects.all()
        qs = qs.filter(by=self.request.user)
        qs = qs.select_related()
        qs = qs.prefetch_related('to__docbowprofile')
        self.add_journal_to_delegations(qs)
        return qs

    def get_form_kwargs(self, **kwargs):
        kwargs = super().get_form_kwargs(**kwargs)
        kwargs['user'] = self.request.user
        kwargs['delegatees'] = [delegation.to for delegation in self.delegations]
        kwargs['request'] = self.request
        return kwargs

    def delete(self, delegation):
        request = self.request
        delegation.delete()
        delegate_user = delegation.to
        request.record(
            'delete-delegation',
            'deleted delegation ' '{delegation} to user {delegated}',
            delegation=delegation.id,
            delegated=delegate_user,
        )
        user = request.user
        # notify delegate
        ctx = {
            'user': utils.clean_ldap_user(user),
            'delegate': delegate_user,
            'to': [delegate_user.email],
            'reply_to': user.email,
        }

        models.Notification.objects.create(kind='delete-delegation', ctx=ctx)
        messages.info(request, _('Delegation %s supressed') % delegate_user)
        messages.info(request, _('%s has been notified.') % delegate_user.email)
        request.record(
            'notify', 'notified {email} of ' 'the deletion of its delegate user', email=delegate_user.email
        )

        # delete guest accounts
        if delegation.guest_delegate:
            if 'mellon' in app_settings.settings.INSTALLED_APPS and delegate_user.saml_identifiers.count():
                err, json_data, err_desc = utils.a2_wscall(
                    urllib.parse.urljoin(
                        app_settings.settings.AUTHENTIC_URL,
                        'api/users/%s' % delegate_user.saml_identifiers.first().name_id,
                    ),
                    'delete',
                )

            delegate_user.delete()
            request.record(
                'delete-delegate',
                'deleted delegate ' 'user {username}, {first_name} {last_name}, ' '({email})',
                username=delegate_user.username,
                first_name=delegate_user.first_name,
                last_name=delegate_user.last_name,
                email=delegate_user.email,
            )
        return HttpResponseRedirect(self.success_url)

    def is_post_target(self):
        if 'delegate-create' in self.request.POST:
            return True
        for delegation in self.delegations:
            username = delegation.to.username
            if f'delegate-delete-{username}.x' in self.request.POST:
                return True
        return False

    @atomic
    def post(self, request, *args, **kwargs):
        if 'delegate-create' in request.POST:
            return super().post(request, *args, **kwargs)
        for delegation in self.delegations:
            username = delegation.to.username
            if f'delegate-delete-{username}.x' in self.request.POST:
                return self.delete(delegation)
        return self.get(request, *args, **kwargs)

    def form_valid(self, form):
        request = self.request
        is_guest = not form.cleaned_data.get('existing_user')
        ctx = {
            'user': utils.clean_ldap_user(request.user),
            'reply_to': request.user.email,
            'is_guest': is_guest,
        }
        if is_guest:
            delegate_username = utils.get_free_delegation_number(request.user)
            delegate_user = User(
                username=delegate_username,
                first_name=form.cleaned_data.get('first_name', ''),
                last_name=form.cleaned_data.get('last_name', ''),
                email=form.cleaned_data.get('email', ''),
            )
            delegate_user.set_unusable_password()
            delegate_user.save()
            models.DocbowProfile.objects.create(
                user=delegate_user,
                is_guest=True,
                accept_notifications=app_settings.DEFAULT_ACCEPT_NOTIFICATIONS_FOR_GUEST,
            )
            delegation, created = models.Delegation.objects.get_or_create(by=request.user, to=delegate_user)
            if 'mellon' in app_settings.settings.INSTALLED_APPS:
                import mellon

                ctx['sso'] = True
                issuer = mellon.models.Issuer.objects.filter(
                    entity_id__startswith=app_settings.settings.AUTHENTIC_URL
                ).first()
                if not issuer:
                    raise ImproperlyConfigured('Mellon issuer not found')

                mellon.models.UserSAMLIdentifier.objects.create(
                    name_id=form.cleaned_data['name_id'],
                    issuer=issuer,
                    user=delegate_user,
                )

            request.record(
                'create-delegate',
                'created delegate with ' 'parameters {first_name} {last_name} <{email}>',
                **form.cleaned_data,
            )
            ctx.update(
                {
                    'password_reset_link': utils.make_password_reset_url(request, delegate_user),
                    'to': [form.cleaned_data['email']],
                    'delegate': delegate_user,
                    'delegation': delegation,
                }
            )
            messages.info(request, _('New delegation to user %s created.') % delegate_user.username)
            messages.info(
                request, _('A notification was sent to %s about this new delegation') % delegate_user.email
            )
        else:
            delegate_user = form.cleaned_data.get('existing_user')
            delegate_username = delegate_user.username
            delegation, created = models.Delegation.objects.get_or_create(by=request.user, to=delegate_user)
            ctx.update(
                {
                    'to': models.all_emails(delegate_user),
                    'delegate': delegate_user,
                    'delegation': delegation,
                }
            )
            messages.info(request, _('New delegation to user %s created.') % delegate_user.username)
            messages.info(
                request,
                _('A notification was sent to %s about this new delegation') % delegate_user.get_full_name(),
            )
        models.Notification.objects.create(kind='new-delegation', ctx=ctx)
        request.record(
            'create-delegation', 'created delegation to ' 'user {delegated}', delegated=delegate_user
        )
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['delegations'] = self.delegations
        ctx['received_delegations'] = self.request.user.delegations_by.all()
        ctx['delegate_to_existing_user'] = app_settings.DELEGATE_TO_EXISTING_USER
        return ctx


class PasswordChangeView(cbv.FormWithPostTarget, FormView):
    template_name = 'registration/password_change_form.html'
    form_class = forms.PasswordChangeFormWithLogging
    prefix = 'password-change'
    success_url = '#password-change'

    def is_post_target(self):
        return 'password-change-validate' in self.request.POST

    def form_valid(self, form):
        messages.info(self.request, _('Password changed'))
        form.save()
        return super().form_valid(form)

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


class EmailView(cbv.FormWithPostTarget, UpdateView):
    form_class = forms.EmailForm
    template_name = 'docbow/email.html'
    prefix = 'email'
    success_url = '#email'

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

    def form_valid(self, form):
        messages.info(self.request, _('Email changed'))
        self.request.record('update-email', 'modified its email', **form.cleaned_data)
        return super().form_valid(form)


class NotificationPreferenceView(cbv.FormWithPostTarget, cbv.FormWithRequestMixin, FormView):
    form_class = forms.NotificationPreferencesForm
    template_name = 'docbow/notifications.html'
    success_url = '#notifications'
    prefix = 'notifications'

    def form_valid(self, form):
        form.save()
        messages.info(self.request, _('Notification preferences saved'))
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['mobile_phone'] = app_settings.MOBILE_PHONE
        ctx['mobile_phone_paragraph'] = _(
            'If You would like to receive a SMS alert each '
            'time your inbox receives a document, provide your '
            'mobile phone number. If you do not fill this field '
            'you won\'t receive any SMS alert'
        )
        return ctx


class FullProfileView(TemplateResponseMixin, View):
    # multiplex all profile views
    template_name = 'docbow/full-profile.html'
    subviews = (
        ('notifications_form', NotificationPreferenceView),
        ('delegate_form', DelegateView),
    )
    if 'mellon' not in app_settings.settings.INSTALLED_APPS:
        subviews = subviews + (('password_change_form', PasswordChangeView),)
    if app_settings.MOBILE_PHONE or app_settings.PERSONAL_EMAIL:
        subviews = (('profile_form', ProfileView),) + subviews
    if app_settings.EDIT_EMAIL and 'mellon' not in app_settings.settings.INSTALLED_APPS:
        subviews += (('email_form', EmailView),)

    def dispatch(self, request, *args, **kwargs):
        if models.is_guest(request.user):
            self.subviews = filter(lambda s: s[0] != 'delegate_form', self.subviews)
            print(self.subviews)
        return super().dispatch(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        ctx = {}
        for var_name, view_class in self.subviews:
            res = self.get_view(var_name, view_class)
            if isinstance(res, dict):
                ctx.update(res)
            else:
                return res
        return self.render_to_response(ctx)

    def get_view(self, var_name, view_class):
        view = view_class()
        view.request, view.args, view.kwargs = self.request, self.args, self.kwargs
        if hasattr(view, 'get_object'):
            view.object = view.get_object()
        if self.request.method == 'POST' and view.is_post_target():
            res = view.post(self.request, *self.args, **self.kwargs)
            if isinstance(res, HttpResponseRedirect):
                return res
            else:
                ctx = res.context_data
                ctx[var_name] = ctx.pop('form')
                return ctx
        else:
            form_class = view.get_form_class()
            form = view.get_form(form_class)
            return view.get_context_data(**{var_name: form})

    def get(self, request, *args, **kwargs):
        ctx = {}
        for var_name, view_class in self.subviews:
            ctx.update(self.get_view(var_name, view_class))
        return self.render_to_response(ctx)
