import textwrap
import urllib

from csp.decorators import csp_update
from django.contrib import messages
from django.contrib.auth import logout
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.forms.models import BaseModelFormSet, inlineformset_factory
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic import (
    DetailView,
    FormView,
    ListView,
    TemplateView,
    UpdateView,
    View,
)
from django_context_decorator import context
from rest_framework.authtoken.models import Token

from pretalx.cfp.forms.submissions import SubmissionInvitationForm
from pretalx.cfp.views.event import LoggedInEventPageMixin
from pretalx.common.phrases import phrases
from pretalx.common.views import is_form_bound
from pretalx.person.forms import LoginInfoForm, SpeakerProfileForm
from pretalx.person.permissions import person_can_view_information
from pretalx.schedule.forms import AvailabilitiesFormMixin
from pretalx.submission.forms import InfoForm, QuestionsForm, ResourceForm
from pretalx.submission.models import Resource, Submission, SubmissionStates


@method_decorator(csp_update(IMG_SRC="https://www.gravatar.com"), name="dispatch")
class ProfileView(LoggedInEventPageMixin, TemplateView):
    template_name = "cfp/event/user_profile.html"

    @context
    @cached_property
    def login_form(self):
        return LoginInfoForm(
            user=self.request.user,
            data=self.request.POST if is_form_bound(self.request, "login") else None,
        )

    @context
    def token(self):
        return Token.objects.filter(
            user=self.request.user
        ).first() or Token.objects.create(user=self.request.user)

    @context
    @cached_property
    def profile_form(self):
        bind = is_form_bound(self.request, "profile")
        return SpeakerProfileForm(
            user=self.request.user,
            event=self.request.event,
            read_only=False,
            with_email=False,
            field_configuration=self.request.event.cfp_flow.config.get(
                "profile", {}
            ).get("fields"),
            data=self.request.POST if bind else None,
            files=self.request.FILES if bind else None,
        )

    @context
    @cached_property
    def questions_form(self):
        bind = is_form_bound(self.request, "questions")
        return QuestionsForm(
            data=self.request.POST if bind else None,
            files=self.request.FILES if bind else None,
            speaker=self.request.user,
            event=self.request.event,
            target="speaker",
            request_user=self.request.user,
        )

    @context
    def questions_exist(self):
        return self.request.event.questions.filter(target="speaker").exists()

    def post(self, request, *args, **kwargs):
        if request.POST.get("form") == "token":
            request.user.regenerate_token()
            messages.success(
                request,
                _(
                    "Your API token has been regenerated. The previous token will not be usable any longer."
                ),
            )
            return super().get(request, *args, **kwargs)
        elif self.login_form.is_bound and self.login_form.is_valid():
            self.login_form.save()
            request.user.log_action("pretalx.user.password.update")
        elif self.profile_form.is_bound and self.profile_form.is_valid():
            self.profile_form.save()
            profile = self.request.user.profiles.get_or_create(
                event=self.request.event
            )[0]
            profile.log_action("pretalx.user.profile.update", person=request.user)
            if self.profile_form.has_changed():
                self.request.event.cache.set("rebuild_schedule_export", True, None)
        elif self.questions_form.is_bound and self.questions_form.is_valid():
            self.questions_form.save()
            if self.questions_form.has_changed():
                self.request.event.cache.set("rebuild_schedule_export", True, None)
        else:
            messages.error(self.request, phrases.base.error_saving_changes)
            return super().get(request, *args, **kwargs)

        messages.success(self.request, phrases.base.saved)
        return redirect("cfp:event.user.view", event=self.request.event.slug)


class SubmissionViewMixin:
    permission_required = "submission.edit_submission"

    def get_object(self):
        users = [self.request.user] if not self.request.user.is_anonymous else []
        return get_object_or_404(
            self.request.event.submissions.prefetch_related(
                "answers", "answers__options"
            ),
            speakers__in=users,
            code__iexact=self.kwargs.get("code"),
        )


class SubmissionsListView(LoggedInEventPageMixin, ListView):
    template_name = "cfp/event/user_submissions.html"
    context_object_name = "submissions"

    @context
    def information(self):
        return [
            i
            for i in self.request.event.information.all()
            if person_can_view_information(self.request.user, i)
        ]

    def get_queryset(self):
        return self.request.event.submissions.filter(speakers__in=[self.request.user])


class SubmissionsWithdrawView(LoggedInEventPageMixin, SubmissionViewMixin, DetailView):
    template_name = "cfp/event/user_submission_withdraw.html"
    model = Submission
    context_object_name = "submission"
    permission_required = "submission.perform_actions"

    def get_permission_object(self):
        return self.get_object()

    def post(self, request, *args, **kwargs):
        obj = self.get_object()
        if self.request.user.has_perm("submission.withdraw_submission", obj):
            if obj.state == SubmissionStates.ACCEPTED:
                obj.event.send_orga_mail(
                    str(
                        _(
                            textwrap.dedent(
                                """
                    Hi,

                    this is your content system at {event_dashboard}.
                    Your accepted talk “{title}” by {speakers} was just withdrawn by {user}.
                    You can find details at {url}.

                    Best regards,
                    pretalx
                    """
                            )
                        )
                    ).format(
                        title=obj.title,
                        speakers=obj.display_speaker_names,
                        user=request.user.get_display_name(),
                        event_dashboard=request.event.orga_urls.base.full(),
                        url=obj.orga_urls.edit.full(),
                    )
                )
            obj.withdraw(person=request.user)
            messages.success(self.request, phrases.cfp.submission_withdrawn)
        else:
            messages.error(self.request, phrases.cfp.submission_not_withdrawn)
        return redirect("cfp:event.user.submissions", event=self.request.event.slug)


class SubmissionConfirmView(LoggedInEventPageMixin, SubmissionViewMixin, FormView):
    permission_required = "submission.perform_actions"
    template_name = "cfp/event/user_submission_confirm.html"
    form_class = AvailabilitiesFormMixin

    def get_object(self):
        return get_object_or_404(
            self.request.event.submissions, code__iexact=self.kwargs.get("code")
        )

    @context
    @cached_property
    def submission(self, **kwargs):
        return self.get_object()

    def get_permission_object(self):
        return self.submission

    def get_form_kwargs(self):
        result = super().get_form_kwargs()
        result["instance"] = self.request.user.profiles.filter(
            event=self.request.event
        ).first()
        result["event"] = self.request.event
        return result

    def get_form(self):
        form = super().get_form()
        if not self.request.event.settings.cfp_request_availabilities:
            form.fields.pop("availabilities")
        else:
            form.fields[
                "availabilities"
            ].required = self.request.event.settings.cfp_require_availabilities
        return form

    def form_valid(self, form):
        submission = self.submission
        if self.request.user.has_perm("submission.confirm_submission", submission):
            submission.confirm(person=self.request.user)
            messages.success(self.request, phrases.cfp.submission_confirmed)
        elif submission.state == SubmissionStates.CONFIRMED:
            messages.success(self.request, phrases.cfp.submission_was_confirmed)
        else:
            messages.error(self.request, phrases.cfp.submission_not_confirmed)
        return redirect("cfp:event.user.submissions", event=self.request.event.slug)


class SubmissionsEditView(LoggedInEventPageMixin, SubmissionViewMixin, UpdateView):
    template_name = "cfp/event/user_submission_edit.html"
    model = Submission
    form_class = InfoForm
    context_object_name = "submission"
    permission_required = "submission.view_submission"
    write_permission_required = "submission.edit_submission"

    def get_permission_object(self):
        return self.object

    @context
    @cached_property
    def formset(self):
        formset_class = inlineformset_factory(
            Submission,
            Resource,
            form=ResourceForm,
            formset=BaseModelFormSet,
            can_delete=True,
            extra=0,
        )
        submission = self.object
        return formset_class(
            self.request.POST if self.request.method == "POST" else None,
            files=self.request.FILES if self.request.method == "POST" else None,
            queryset=submission.resources.all()
            if submission
            else Resource.objects.none(),
            prefix="resource",
        )

    def save_formset(self, obj):
        if not self.formset.is_valid():
            return False
        for form in self.formset.initial_forms:
            if form in self.formset.deleted_forms:
                if not form.instance.pk:
                    continue
                obj.log_action(
                    "pretalx.submission.resource.delete",
                    person=self.request.user,
                    data={"id": form.instance.pk},
                )
                form.instance.delete()
                form.instance.pk = None
            elif form.has_changed():
                form.instance.submission = obj
                form.save()
                change_data = {k: form.cleaned_data.get(k) for k in form.changed_data}
                change_data["id"] = form.instance.pk
                obj.log_action(
                    "pretalx.submission.resource.update", person=self.request.user
                )

        extra_forms = [
            form
            for form in self.formset.extra_forms
            if form.has_changed
            and not self.formset._should_delete_form(form)
            and form.instance.resource
        ]
        for form in extra_forms:
            form.instance.submission = obj
            form.save()
            obj.log_action(
                "pretalx.submission.resource.create",
                person=self.request.user,
                data={"id": form.instance.pk},
            )

        return True

    @context
    @cached_property
    def qform(self):
        return QuestionsForm(
            data=self.request.POST if self.request.method == "POST" else None,
            files=self.request.FILES if self.request.method == "POST" else None,
            submission=self.object,
            target="submission",
            event=self.request.event,
            readonly=not self.can_edit,
        )

    @cached_property
    def object(self):
        return self.get_object()

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid() and self.qform.is_valid():
            return self.form_valid(form)
        return self.form_invalid(form)

    @context
    @cached_property
    def can_edit(self):
        return self.object.editable

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["event"] = self.request.event
        kwargs["field_configuration"] = self.request.event.cfp_flow.config.get(
            "info", {}
        ).get("fields")
        kwargs["readonly"] = not self.can_edit
        return kwargs

    def form_valid(self, form):
        if self.can_edit:
            form.save()
            self.qform.save()
            result = self.save_formset(form.instance)
            if not result:
                return self.get(self.request, *self.args, **self.kwargs)
            if form.has_changed():
                if form.instance.pk and "duration" in form.changed_data:
                    form.instance.update_duration()
                form.instance.log_action(
                    "pretalx.submission.update", person=self.request.user
                )
                self.request.event.cache.set("rebuild_schedule_export", True, None)
            messages.success(self.request, phrases.base.saved)
        else:
            messages.error(self.request, phrases.cfp.submission_uneditable)
        return redirect(self.object.urls.user_base)


class DeleteAccountView(LoggedInEventPageMixin, View):
    @staticmethod
    def post(request, event):

        if request.POST.get("really"):
            request.user.deactivate()
            logout(request)
            messages.success(request, phrases.cfp.account_deleted)
            return redirect(request.event.urls.base)
        messages.error(request, phrases.cfp.account_delete_confirm)
        return redirect(request.event.urls.user + "?really")


class SubmissionInviteView(LoggedInEventPageMixin, SubmissionViewMixin, FormView):
    form_class = SubmissionInvitationForm
    template_name = "cfp/event/user_submission_invitation.html"
    permission_required = "cfp.add_speakers"

    def get_permission_object(self):
        return self.get_object()

    @context
    @cached_property
    def submission(self, **kwargs):
        return self.get_object()

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["submission"] = self.submission
        kwargs["speaker"] = self.request.user
        if "email" in self.request.GET and not self.request.method == "POST":
            initial = kwargs.get("initial", {})
            initial["speaker"] = urllib.parse.unquote(self.request.GET["email"])
            kwargs["initial"] = initial

            try:
                validate_email(initial["speaker"])
            except ValidationError:
                messages.warning(self.request, phrases.cfp.invite_invalid_email)
        return kwargs

    def form_valid(self, form):
        form.save()
        messages.success(self.request, phrases.cfp.invite_sent)
        self.submission.log_action(
            "pretalx.submission.speakers.invite", person=self.request.user
        )
        return super().form_valid(form)

    def get_success_url(self):
        return self.submission.urls.user_base


class SubmissionInviteAcceptView(LoggedInEventPageMixin, DetailView):
    template_name = "cfp/event/invitation.html"
    context_object_name = "submission"
    permission_required = "cfp.add_speakers"

    def get_object(self, queryset=None):
        return get_object_or_404(
            Submission,
            code__iexact=self.kwargs["code"],
            invitation_token__iexact=self.kwargs["invitation"],
        )

    def get_permission_object(self):
        return self.get_object()

    def post(self, request, *args, **kwargs):
        submission = self.get_object()
        submission.speakers.add(self.request.user)
        submission.log_action(
            "pretalx.submission.speakers.add", person=self.request.user
        )
        submission.save()
        messages.success(self.request, phrases.cfp.invite_accepted)
        return redirect("cfp:event.user.view", event=self.request.event.slug)


class MailListView(LoggedInEventPageMixin, TemplateView):
    template_name = "cfp/event/user_mails.html"

    @context
    def mails(self):
        return self.request.user.mails.filter(sent__isnull=False).order_by("-sent")
