# SPDX-FileCopyrightText: 2017-present Tobias Kunze
# SPDX-License-Identifier: AGPL-3.0-only WITH LicenseRef-Pretalx-AGPL-3.0-Terms

from django.contrib import messages
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from django.views.generic import FormView, ListView, TemplateView, View
from django_context_decorator import context

from pretalx.common.exceptions import SendMailException
from pretalx.common.language import language
from pretalx.common.mail import TolerantDict
from pretalx.common.text.phrases import phrases
from pretalx.common.ui import Button, LinkButton, delete_link, send_button
from pretalx.common.views.generic import (
    CreateOrUpdateView,
    OrgaCRUDView,
    OrgaTableMixin,
)
from pretalx.common.views.mixins import (
    ActionConfirmMixin,
    EventPermissionRequired,
    Filterable,
    PermissionRequired,
)
from pretalx.mail.models import MailTemplate, QueuedMail, get_prefixed_subject
from pretalx.mail.signals import request_pre_send
from pretalx.orga.forms.mails import (
    DraftRemindersForm,
    MailDetailForm,
    MailTemplateForm,
    QueuedMailFilterForm,
    WriteSessionMailForm,
    WriteTeamsMailForm,
)
from pretalx.orga.tables.mail import MailTemplateTable, OutboxMailTable, SentMailTable


def get_send_mail_exceptions(request):
    exceptions = [
        result[1]
        for result in request_pre_send.send_robust(
            sender=request.event, request=request
        )
        if len(result) == 2 and isinstance(result[1], SendMailException)
    ]
    if exceptions:
        errors = [str(e) for e in exceptions]
        return errors or [_("You cannot send emails at this time.")]


class OutboxList(EventPermissionRequired, Filterable, OrgaTableMixin, ListView):
    model = QueuedMail
    table_class = OutboxMailTable
    context_object_name = "mails"
    template_name = "orga/mails/outbox_list.html"
    default_filters = (
        "to__icontains",
        "subject__icontains",
        "to_users__name__icontains",
        "to_users__email__icontains",
    )
    permission_required = "mail.list_queuedmail"

    def get_queryset(self):
        qs = (
            self.request.event.queued_mails.prefetch_related(
                "to_users", "submissions", "submissions__track", "submissions__event"
            )
            .select_related("template")
            .filter(sent__isnull=True)
            .order_by("-id")
        )
        qs = self.filter_queryset(qs)
        return qs

    @context
    @cached_property
    def show_tracks(self):
        return self.request.event.get_feature_flag("use_tracks")

    @context
    @cached_property
    def is_filtered(self):
        return self.get_queryset().count() != self.request.event.pending_mails

    def get_filter_form(self):
        return QueuedMailFilterForm(
            self.request.GET, event=self.request.event, sent=False
        )

    def get_table_kwargs(self):
        kwargs = super().get_table_kwargs()
        permission = self.request.user.has_perm(
            "mail.send_queuedmail", self.request.event
        )
        kwargs["has_update_permission"] = permission
        kwargs["has_delete_permission"] = permission
        return kwargs


class SentMail(EventPermissionRequired, Filterable, OrgaTableMixin, ListView):
    model = QueuedMail
    table_class = SentMailTable
    context_object_name = "mails"
    template_name = "orga/mails/sent_list.html"
    default_filters = (
        "to__icontains",
        "subject__icontains",
        "to_users__name__icontains",
        "to_users__email__icontains",
    )
    permission_required = "mail.list_queuedmail"

    def get_filter_form(self):
        return QueuedMailFilterForm(
            self.request.GET, event=self.request.event, sent=True
        )

    def get_queryset(self):
        qs = (
            self.request.event.queued_mails.prefetch_related(
                "to_users", "submissions", "submissions__track", "submissions__event"
            )
            .select_related("template")
            .filter(sent__isnull=False)
            .order_by("-sent")
        )
        qs = self.filter_queryset(qs)
        return qs

    @context
    @cached_property
    def show_tracks(self):
        return self.request.event.get_feature_flag("use_tracks")


class OutboxSend(ActionConfirmMixin, OutboxList):
    permission_required = "mail.send_queuedmail"
    action_object_name = ""
    action_confirm_label = phrases.base.send
    action_confirm_color = "success"
    action_confirm_icon = "envelope"

    @context
    def question(self):
        return _("Do you really want to send {count} mails?").format(
            count=self.queryset.count()
        )

    def action_title(self):
        return _("Send emails")

    def action_text(self):
        return self.question()

    @property
    def action_back_url(self):
        return self.request.event.orga_urls.outbox

    def dispatch(self, request, *args, **kwargs):
        if "pk" in self.kwargs:
            try:
                mail = self.request.event.queued_mails.get(pk=self.kwargs.get("pk"))
            except QueuedMail.DoesNotExist:
                messages.error(
                    request,
                    _(
                        "This mail either does not exist or cannot be sent because it was sent already."
                    ),
                )
                return redirect(self.request.event.orga_urls.outbox)
            if mail.sent:
                messages.error(request, _("This mail had been sent already."))
            else:
                errors = get_send_mail_exceptions(request)
                if errors:
                    for error in errors:
                        messages.error(request, error)
                    return redirect(self.request.event.orga_urls.outbox)
                mail.send(requestor=self.request.user)
                messages.success(request, _("The mail has been sent."))
            return redirect(self.request.event.orga_urls.outbox)
        return super().dispatch(request, *args, **kwargs)

    @cached_property
    def queryset(self):
        pks = self.request.GET.get("pks") or ""
        if pks:
            return self.request.event.queued_mails.filter(sent__isnull=True).filter(
                pk__in=pks.split(",")
            )
        return self.get_queryset()

    def post(self, request, *args, **kwargs):
        mails = self.queryset
        errors = get_send_mail_exceptions(request)
        if errors:
            for error in errors:
                messages.error(request, error)
            return redirect(self.request.event.orga_urls.outbox)
        count = mails.count()
        for mail in mails:
            mail.send(requestor=self.request.user)
        messages.success(
            request, _("{count} mails have been sent.").format(count=count)
        )
        return redirect(self.request.event.orga_urls.outbox)


class MailDelete(PermissionRequired, ActionConfirmMixin, TemplateView):
    permission_required = "mail.delete_queuedmail"
    action_object_name = ""

    def get_permission_object(self):
        return self.request.event

    @cached_property
    def queryset(self):
        mail = self.request.event.queued_mails.filter(
            sent__isnull=True, pk=self.kwargs.get("pk")
        )
        if "all" in self.request.GET and mail:
            return self.request.event.queued_mails.filter(
                sent__isnull=True, template=mail.first().template
            )
        return mail

    def action_text(self):
        return self.question()

    @property
    def action_back_url(self):
        return self.request.event.orga_urls.outbox

    @context
    def question(self):
        count = len(self.queryset)
        return str(
            ngettext_lazy(
                "Do you really want to delete this mail?",
                "Do you really want to purge {count} mails?",
                count,
            )
        ).format(count=count)

    def post(self, request, *args, **kwargs):
        mails = self.queryset
        mail_count = len(mails)
        if not mails:
            messages.error(
                request,
                _(
                    "This mail either does not exist or cannot be discarded because it was sent already."
                ),
            )
            return redirect(self.request.event.orga_urls.outbox)
        for mail in mails:
            mail.log_action("pretalx.mail.delete", person=self.request.user, orga=True)
            mail.delete()

        messages.success(
            request,
            str(
                ngettext_lazy(
                    "The mail has been discarded.",
                    "{count} mails have been discarded.",
                    mail_count,
                )
            ).format(count=mail_count),
        )

        return redirect(request.event.orga_urls.outbox)


class OutboxPurge(ActionConfirmMixin, OutboxList):
    permission_required = "mail.delete_queuedmail"
    action_object_name = ""

    @context
    def question(self):
        return _("Do you really want to purge {count} mails?").format(
            count=self.queryset.count()
        )

    def action_text(self):
        return self.question()

    @property
    def action_back_url(self):
        return self.request.event.orga_urls.outbox

    @cached_property
    def queryset(self):
        return self.get_queryset()

    def post(self, request, *args, **kwargs):
        qs = self.queryset
        count = qs.count()
        qs.delete()
        messages.success(
            request, _("{count} mails have been purged.").format(count=count)
        )
        return redirect(self.request.event.orga_urls.outbox)


class MailDetail(PermissionRequired, CreateOrUpdateView):
    model = QueuedMail
    form_class = MailDetailForm
    template_name = "orga/mails/outbox_form.html"
    write_permission_required = "mail.update_queuedmail"
    permission_required = "mail.view_queuedmail"
    extra_forms_signal = "pretalx.orga.signals.mail_form"
    messages = {}

    def get_object(self, queryset=None) -> QueuedMail:
        return self.request.event.queued_mails.filter(pk=self.kwargs.get("pk")).first()

    def get_success_url(self):
        return self.object.event.orga_urls.outbox

    def form_valid(self, form):
        form.instance.event = self.request.event
        result = super().form_valid(form)
        action = form.data.get("form", "save")
        if action == "send":
            errors = get_send_mail_exceptions(self.request)
            if errors:
                for error in errors:
                    messages.error(self.request, error)
                return redirect(self.get_success_url())
            form.instance.send()
            messages.success(self.request, _("The email has been sent."))
        else:  # action == 'save'
            messages.success(
                self.request,
                _(
                    "The email has been saved. When you send it, the updated text will be used."
                ),
            )
        return result

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.permission_action == "edit":
            context["submit_buttons"] = [
                Button(),
                Button(
                    name="form",
                    value="send",
                    color="info",
                    label=_("Save and send"),
                    icon="envelope",
                ),
            ]
            context["submit_buttons_extra"] = [
                delete_link(
                    color="danger", href=self.object.urls.delete, label=_("Discard")
                ),
                delete_link(
                    href=f"{self.object.urls.delete}?all",
                    label=_("Discard all from this template"),
                ),
            ]
        elif self.object.sent:
            if pk := self.object.template_id:
                href = f"{self.request.event.orga_urls.compose_mails_sessions}?template={pk}"
            else:
                href = self.object.urls.copy
            context["submit_buttons"] = [
                LinkButton(href=href, label=_("Copy to draft"))
            ]
        return context


class MailCopy(PermissionRequired, View):
    permission_required = "mail.send_queuedmail"

    def get_object(self) -> QueuedMail:
        return get_object_or_404(
            self.request.event.queued_mails, pk=self.kwargs.get("pk")
        )

    def dispatch(self, request, *args, **kwargs):
        mail = self.get_object()
        new_mail = mail.copy_to_draft()
        messages.success(request, _("The mail has been copied, you can edit it now."))
        return redirect(new_mail.urls.edit)


class MailPreview(PermissionRequired, View):
    permission_required = "mail.send_queuedmail"

    def get_object(self) -> QueuedMail:
        return get_object_or_404(
            self.request.event.queued_mails, pk=self.kwargs.get("pk")
        )

    def get(self, request, *args, **kwargs):
        mail = self.get_object()
        return HttpResponse(mail.make_html())


class ComposeMailChoice(EventPermissionRequired, TemplateView):
    template_name = "orga/mails/compose_choice.html"
    permission_required = "mail.send_queuedmail"


class ComposeMailBaseView(EventPermissionRequired, FormView):
    permission_required = "mail.send_queuedmail"
    write_permission_required = "mail.send_queuedmail"

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["event"] = self.request.event
        initial = kwargs.get("initial", {})
        if "template" in self.request.GET:
            template = MailTemplate.objects.filter(
                pk=self.request.GET.get("template")
            ).first()
            if template:
                initial["subject"] = template.subject
                initial["text"] = template.text
                initial["reply_to"] = template.reply_to
                initial["bcc"] = template.bcc
        for key in self.form_class.base_fields.keys():
            if key in self.request.GET:
                initial[key] = self.request.GET.get(key)
        kwargs["initial"] = initial

        errors = get_send_mail_exceptions(self.request)
        kwargs["may_skip_queue"] = not bool(errors)
        return kwargs

    def get_success_url(self):
        return getattr(self, "success_url", self.request.event.orga_urls.outbox)

    def get_context_data(self, *args, **kwargs):
        ctx = super().get_context_data(*args, **kwargs)
        ctx["output"] = getattr(self, "output", None)
        ctx["mail_count"] = getattr(self, "mail_count", None) or 0
        ctx["submit_buttons"] = [
            Button(
                color="outline-info",
                name="action",
                value="preview",
                label=_("Preview email"),
                icon=None,
            )
        ]
        if self.request.method == "POST" and ctx["form"].is_valid():
            ctx["submit_buttons"].append(Button(label=_("Send to outbox")))
        return ctx

    def form_valid(self, form):
        preview = self.request.POST.get("action") == "preview"
        if preview:
            self.output = {}
            # Only approximate, good enough. Doesn't run deduplication, so it doesn't have to
            # run rendering for all placeholders for all people, either.
            result = form.get_recipients()
            if not result:
                messages.error(
                    self.request,
                    _("There are no recipients matching this selection."),
                )
                return self.get(self.request, *self.args, **self.kwargs)

            import bleach

            from pretalx.common.templatetags.rich_text import render_markdown_abslinks

            for locale in self.request.event.locales:
                with language(locale):
                    context_dict = TolerantDict()
                    title = _(
                        "This value will be replaced based on dynamic parameters."
                    )
                    for key, value in form.get_valid_placeholders().items():
                        content = escape(value.render_sample(self.request.event))
                        context_dict[key] = (
                            f'<span class="placeholder" title="{title}">{content}</span>'
                        )

                    subject = bleach.clean(
                        form.cleaned_data["subject"].localize(locale), tags={}
                    )
                    preview_subject = get_prefixed_subject(
                        self.request.event, subject.format_map(context_dict)
                    )
                    message = form.cleaned_data["text"].localize(locale)
                    preview_text = render_markdown_abslinks(
                        message.format_map(context_dict)
                    )
                    self.output[locale] = {
                        "subject": _("Subject: {subject}").format(
                            subject=preview_subject
                        ),
                        "html": preview_text,
                    }
                    # Very rough method to deduplicate recipients, but good enough for a preview
                    self.mail_count = len({str(res) for res in result})
            return self.get(self.request, *self.args, **self.kwargs)

        result = form.save()
        if len(result) and result[0].sent:
            self.success_url = self.request.event.orga_urls.sent_mails
            messages.success(
                self.request,
                _("{count} emails have been sent.").format(count=len(result)),
            )
        else:
            self.success_url = self.request.event.orga_urls.outbox
            messages.success(
                self.request,
                phrases.orga.mails_in_outbox.format(count=len(result)),
            )
        return super().form_valid(form)


class ComposeTeamsMail(ComposeMailBaseView):
    form_class = WriteTeamsMailForm
    template_name = "orga/mails/compose_reviewer_mail_form.html"
    permission_required = "event.update_team"

    def dispatch(self, request, *args, **kwargs):
        # Gotta handle errors directly here, as these emails are always sent directly
        errors = get_send_mail_exceptions(request)
        if errors:
            for error in errors:
                messages.error(request, error)
            return redirect(self.request.event.orga_urls.outbox)
        return super().dispatch(request, *args, **kwargs)

    def get_success_url(self):
        return self.request.event.orga_urls.outbox


class ComposeSessionMail(ComposeMailBaseView):
    form_class = WriteSessionMailForm
    template_name = "orga/mails/compose_session_mail_form.html"

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        initial = kwargs.get("initial", {})
        if "submissions" in self.request.GET:
            initial["submissions"] = list(
                self.request.event.submissions.filter(
                    code__in=self.request.GET.get("submissions").split(",")
                ).values_list("code", flat=True)
            )
        if "speakers" in self.request.GET:
            initial["speakers"] = self.request.event.submitters.filter(
                code__in=self.request.GET.get("speakers").split(",")
            )
        kwargs["initial"] = initial
        return kwargs


class ComposeDraftReminders(EventPermissionRequired, FormView):
    form_class = DraftRemindersForm
    template_name = "orga/mails/send_draft_reminders.html"
    permission_required = "mail.send_queuedmail"

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["event"] = self.request.event
        return kwargs

    @context
    def submit_buttons(self):
        return [send_button()]

    def get_success_url(self):
        return self.request.event.orga_urls.base

    def form_valid(self, form):
        result = form.save()
        messages.success(
            self.request,
            _("{count} emails have been sent.").format(count=result),
        )
        return super().form_valid(form)


class MailTemplateView(OrgaCRUDView):
    model = MailTemplate
    form_class = MailTemplateForm
    table_class = MailTemplateTable
    template_namespace = "orga/mails"
    messages = {
        "create": phrases.base.saved,
        "update": _(
            "The template has been saved - note that already pending emails that are based on this template will not be changed!"
        ),
        "delete": phrases.base.deleted,
    }
    create_button_label = _("New custom template")

    def get_queryset(self):
        return (
            self.request.event.mail_templates.all()
            .exclude(is_auto_created=True)
            .order_by("role")
        )

    def get_generic_title(self, instance=None):
        if instance:
            if not instance.role:
                return _("Email template") + f": {instance.subject}"
            return _("Email template") + f": {instance.get_role_display()}"
        if self.action == "create":
            return _("New email template")
        return _("Email templates")
