from django import forms
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.views.generic import FormView

from activities.models import (
    Post,
    PostAttachment,
    PostAttachmentStates,
    PostStates,
    TimelineEvent,
)
from core.files import blurhash_image, resize_image
from core.html import html_to_plaintext
from core.models import Config
from users.decorators import identity_required


@method_decorator(identity_required, name="dispatch")
class Compose(FormView):

    template_name = "activities/compose.html"

    class form_class(forms.Form):
        text = forms.CharField(
            widget=forms.Textarea(
                attrs={
                    "autofocus": "autofocus",
                    "maxlength": Config.lazy_system_value("post_length"),
                    "placeholder": "What's on your mind?",
                },
            )
        )
        visibility = forms.ChoiceField(
            choices=[
                (Post.Visibilities.public, "Public"),
                (Post.Visibilities.local_only, "Local Only"),
                (Post.Visibilities.unlisted, "Unlisted"),
                (Post.Visibilities.followers, "Followers & Mentioned Only"),
                (Post.Visibilities.mentioned, "Mentioned Only"),
            ],
        )
        content_warning = forms.CharField(
            required=False,
            label=Config.lazy_system_value("content_warning_text"),
            widget=forms.TextInput(
                attrs={
                    "placeholder": Config.lazy_system_value("content_warning_text"),
                },
            ),
            help_text="Optional - Post will be hidden behind this text until clicked",
        )
        reply_to = forms.CharField(widget=forms.HiddenInput(), required=False)

        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.fields["text"].widget.attrs[
                "_"
            ] = f"""
                on load or input
                set characters to my.value.trim().length
                put {Config.system.post_length} - characters into #character-counter

                if characters > {Config.system.post_length} then
                    set #character-counter's style.color to 'var(--color-text-error)'
                    add [@disabled=] to #post-button
                else
                    set #character-counter's style.color to ''
                    remove @disabled from #post-button
                end
            """

        def clean_text(self):
            text = self.cleaned_data.get("text")
            if not text:
                return text
            length = len(text)
            if length > Config.system.post_length:
                raise forms.ValidationError(
                    f"Maximum post length is {Config.system.post_length} characters (you have {length})"
                )
            return text

    def get_initial(self):
        initial = super().get_initial()
        if self.post_obj:
            initial.update(
                {
                    "reply_to": self.reply_to.pk if self.reply_to else "",
                    "visibility": self.post_obj.visibility,
                    "text": html_to_plaintext(self.post_obj.content),
                    "content_warning": self.post_obj.summary,
                }
            )
        else:
            initial[
                "visibility"
            ] = self.request.identity.config_identity.default_post_visibility
            if self.reply_to:
                initial["reply_to"] = self.reply_to.pk
                if self.reply_to.visibility == Post.Visibilities.public:
                    initial["visibility"] = Post.Visibilities.unlisted
                else:
                    initial["visibility"] = self.reply_to.visibility
                # Build a set of mentions for the content to start as
                mentioned = {self.reply_to.author}
                mentioned.update(self.reply_to.mentions.all())
                mentioned.discard(self.request.identity)
                initial["text"] = "".join(
                    f"@{identity.handle} " for identity in mentioned
                )
        return initial

    def form_valid(self, form):
        # Gather any attachment objects now, they're not in the form proper
        attachments = []
        if "attachment" in self.request.POST:
            attachments = PostAttachment.objects.filter(
                pk__in=self.request.POST.getlist("attachment", [])
            )
        # Dispatch based on edit or not
        if self.post_obj:
            self.post_obj.edit_local(
                content=form.cleaned_data["text"],
                summary=form.cleaned_data.get("content_warning"),
                visibility=form.cleaned_data["visibility"],
                attachments=attachments,
            )
            self.post_obj.transition_perform(PostStates.edited)
        else:
            post = Post.create_local(
                author=self.request.identity,
                content=form.cleaned_data["text"],
                summary=form.cleaned_data.get("content_warning"),
                visibility=form.cleaned_data["visibility"],
                reply_to=self.reply_to,
                attachments=attachments,
            )
            # Add their own timeline event for immediate visibility
            TimelineEvent.add_post(self.request.identity, post)
        return redirect("/")

    def dispatch(self, request, handle=None, post_id=None, *args, **kwargs):
        self.post_obj = None
        if handle and post_id:
            # Make sure the request identity owns the post!
            if handle != request.identity.handle:
                raise PermissionDenied("Post author is not requestor")

            self.post_obj = get_object_or_404(request.identity.posts, pk=post_id)

        # Grab the reply-to post info now
        self.reply_to = None
        reply_to_id = request.POST.get("reply_to") or request.GET.get("reply_to")
        if reply_to_id:
            try:
                self.reply_to = Post.objects.get(pk=reply_to_id)
            except Post.DoesNotExist:
                pass
        # Keep going with normal rendering
        return super().dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["reply_to"] = self.reply_to
        if self.post_obj:
            context["post"] = self.post_obj
        return context


@method_decorator(identity_required, name="dispatch")
class ImageUpload(FormView):
    """
    Handles image upload - returns a new input type hidden to embed in
    the main form that references an orphaned PostAttachment
    """

    template_name = "activities/_image_upload.html"

    class form_class(forms.Form):
        image = forms.ImageField()
        description = forms.CharField(required=False)

        def clean_image(self):
            value = self.cleaned_data["image"]
            max_mb = settings.SETUP.MEDIA_MAX_IMAGE_FILESIZE_MB
            max_bytes = max_mb * 1024 * 1024
            if value.size > max_bytes:
                # Erase the file from our data to stop trying to show it again
                self.files = {}
                raise forms.ValidationError(f"File must be {max_mb}MB or less")
            return value

    def form_invalid(self, form):
        return super().form_invalid(form)

    def form_valid(self, form):
        # Make a PostAttachment
        main_file = resize_image(
            form.cleaned_data["image"],
            size=(2000, 2000),
            cover=False,
        )
        thumbnail_file = resize_image(
            form.cleaned_data["image"],
            size=(400, 225),
            cover=True,
        )
        attachment = PostAttachment.objects.create(
            blurhash=blurhash_image(thumbnail_file),
            mimetype="image/webp",
            width=main_file.image.width,
            height=main_file.image.height,
            name=form.cleaned_data.get("description"),
            state=PostAttachmentStates.fetched,
        )

        attachment.file.save(
            main_file.name,
            main_file,
        )
        attachment.thumbnail.save(
            thumbnail_file.name,
            thumbnail_file,
        )
        attachment.save()
        # Return the response, with a hidden input plus a note
        return render(
            self.request, "activities/_image_uploaded.html", {"attachment": attachment}
        )
