__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"


import datetime

from django import forms
from django.contrib.auth import get_user_model
from django.db.models import Max
from django.db.models.functions import Greatest

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Div, Field, Submit
from crispy_bootstrap5.bootstrap5 import FloatingField

from journals.models import Journal
from markup.widgets import TextareaWithPreview
from proceedings.models import Proceedings
from scipost.fields import UserModelChoiceField

from . import constants
from .models import (
    ProductionUser,
    ProductionStream,
    ProductionEvent,
    Proofs,
    ProductionEventAttachment,
)


today = datetime.datetime.today()


class ProductionEventForm(forms.ModelForm):
    class Meta:
        model = ProductionEvent
        fields = (
            "stream",
            "comments",
            "noted_by",
        )
        widgets = {
            "comments": TextareaWithPreview(attrs={"rows": 4}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Field("stream", type="hidden"),
            Field("comments"),
            Field("noted_by", type="hidden"),
            Submit("submit", "Submit"),
        )


class ProductionEventForm_deprec(forms.ModelForm):
    class Meta:
        model = ProductionEvent
        fields = ("comments",)
        widgets = {
            "comments": forms.Textarea(attrs={"rows": 4}),
        }


class AssignOfficerForm(forms.ModelForm):
    class Meta:
        model = ProductionStream
        fields = ("officer",)

    def save(self, commit=True):
        stream = super().save(False)
        if commit:
            if stream.status == constants.PRODUCTION_STREAM_INITIATED:
                stream.status = constants.PROOFS_TASKED
            stream.save()
        return stream


class AssignInvitationsOfficerForm(forms.ModelForm):
    class Meta:
        model = ProductionStream
        fields = ("invitations_officer",)


class AssignSupervisorForm(forms.ModelForm):
    class Meta:
        model = ProductionStream
        fields = ("supervisor",)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["supervisor"].queryset = self.fields["supervisor"].queryset.filter(
            user__groups__name="Production Supervisor"
        )


class StreamStatusForm(forms.ModelForm):
    class Meta:
        model = ProductionStream
        fields = ("status",)

    def __init__(self, *args, **kwargs):
        self.current_production_user = kwargs.pop("production_user")
        super().__init__(*args, **kwargs)
        self.fields["status"].choices = self.get_available_statuses()

    def get_available_statuses(self):
        if self.instance.status in [
            constants.PRODUCTION_STREAM_INITIATED,
            constants.PRODUCTION_STREAM_COMPLETED,
            constants.PROOFS_ACCEPTED,
            constants.PROOFS_CITED,
        ]:
            # No status change can be made by User
            return ()
        elif self.instance.status == constants.PROOFS_TASKED:
            return ((constants.PROOFS_PRODUCED, "Proofs have been produced"),)
        elif self.instance.status == constants.PROOFS_PRODUCED:
            return (
                (constants.PROOFS_CHECKED, "Proofs have been checked by Supervisor"),
                (constants.PROOFS_SENT, "Proofs sent to Authors"),
            )
        elif self.instance.status == constants.PROOFS_CHECKED:
            return (
                (constants.PROOFS_SENT, "Proofs sent to Authors"),
                (constants.PROOFS_CORRECTED, "Corrections implemented"),
            )
        elif self.instance.status == constants.PROOFS_SENT:
            return (
                (constants.PROOFS_RETURNED, "Proofs returned by Authors"),
                (constants.PROOFS_ACCEPTED, "Authors have accepted proofs"),
            )
        elif self.instance.status == constants.PROOFS_RETURNED:
            return (
                (constants.PROOFS_CHECKED, "Proofs have been checked by Supervisor"),
                (constants.PROOFS_SENT, "Proofs sent to Authors"),
                (constants.PROOFS_CORRECTED, "Corrections implemented"),
                (constants.PROOFS_ACCEPTED, "Authors have accepted proofs"),
            )
        elif self.instance.status == constants.PROOFS_CORRECTED:
            return (
                (constants.PROOFS_CHECKED, "Proofs have been checked by Supervisor"),
                (constants.PROOFS_SENT, "Proofs sent to Authors"),
                (constants.PROOFS_ACCEPTED, "Authors have accepted proofs"),
            )
        elif self.instance.status == constants.PROOFS_PUBLISHED:
            return (
                (
                    constants.PROOFS_CITED,
                    "Cited people have been notified/invited to SciPost",
                ),
            )
        return ()

    def save(self, commit=True):
        stream = super().save(commit)
        if commit:
            event = ProductionEvent(
                stream=stream,
                event="status",
                comments="Stream changed status to: {status}".format(
                    status=stream.get_status_display()
                ),
                noted_by=self.current_production_user,
            )
            event.save()
        return stream


class UserToOfficerForm(forms.ModelForm):
    user = UserModelChoiceField(
        queryset=get_user_model()
        .objects.filter(production_user__isnull=True)
        .order_by("last_name")
    )

    class Meta:
        model = ProductionUser
        fields = ("user",)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["user"].queryset = (
            self.fields["user"]
            .queryset.filter(production_user__isnull=True)
            .order_by("last_name")
        )


class ProofsUploadForm(forms.ModelForm):
    class Meta:
        model = Proofs
        fields = ("attachment",)


class ProofsDecisionForm(forms.ModelForm):
    decision = forms.ChoiceField(
        choices=[
            (True, "Accept Proofs for publication"),
            (False, "Decline Proofs for publication"),
        ]
    )
    feedback = forms.CharField(required=False, widget=forms.Textarea)
    feedback_attachment = forms.FileField(required=False)

    class Meta:
        model = Proofs
        fields = ()

    def save(self, commit=True):
        proofs = self.instance
        decision = self.cleaned_data["decision"]
        comments = self.cleaned_data["feedback"]

        if decision in ["True", True]:
            proofs.status = constants.PROOFS_ACCEPTED
            if proofs.stream.status in [
                constants.PROOFS_PRODUCED,
                constants.PROOFS_CHECKED,
                constants.PROOFS_SENT,
                constants.PROOFS_CORRECTED,
            ]:
                # Force status change on Stream if appropriate
                proofs.stream.status = constants.PROOFS_ACCEPTED
        else:
            proofs.status = constants.PROOFS_DECLINED
            proofs.stream.status = constants.PROOFS_RETURNED

        if commit:
            proofs.save()
            proofs.stream.save()

            prodevent = ProductionEvent(
                stream=proofs.stream,
                event="status",
                comments="<em>Received feedback from the authors:</em><br>{comments}".format(
                    comments=comments
                ),
                noted_by=proofs.stream.supervisor,
            )
            prodevent.save()
            if self.cleaned_data.get("feedback_attachment"):
                attachment = ProductionEventAttachment(
                    attachment=self.cleaned_data["feedback_attachment"],
                    production_event=prodevent,
                )
                attachment.save()
        return proofs


class ProductionStreamSearchForm(forms.Form):
    accepted_in = forms.ModelMultipleChoiceField(
        queryset=Journal.objects.active(),
        required=False,
    )
    proceedings = forms.ModelChoiceField(
        queryset=Proceedings.objects.order_by("-submissions_close"),
        required=False,
    )
    author = forms.CharField(max_length=100, required=False, label="Author(s)")
    title = forms.CharField(max_length=512, required=False)
    identifier = forms.CharField(max_length=128, required=False)
    officer = forms.ModelChoiceField(
        queryset=ProductionUser.objects.active(),
        required=False,
        empty_label="Any",
    )
    supervisor = forms.ModelChoiceField(
        queryset=ProductionUser.objects.active(),
        required=False,
        empty_label="Any",
    )
    status = forms.MultipleChoiceField(
        choices=constants.PRODUCTION_STREAM_STATUS,
        required=False,
    )
    orderby = forms.ChoiceField(
        label="Order by",
        choices=(
            ("submission__editorial_decision__taken_on", "Date accepted"),
            ("latest_activity_annot", "Latest activity"),
            (
                "status,submission__editorial_decision__taken_on",
                "Status + Date accepted",
            ),
            ("status,latest_activity_annot", "Status + Latest activity"),
        ),
        required=False,
    )
    ordering = forms.ChoiceField(
        label="Ordering",
        choices=(
            # FIXME: Emperically, the ordering appers to be reversed for dates?
            ("-", "Ascending"),
            ("+", "Descending"),
        ),
        required=False,
    )

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop("user")
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Div(
                Div(FloatingField("identifier"), css_class="col-md-3 col-4"),
                Div(FloatingField("author"), css_class="col-md-3 col-8"),
                Div(FloatingField("title"), css_class="col-md-6"),
                css_class="row mb-0 mt-2",
            ),
            Div(
                Div(
                    Div(
                        Div(Field("accepted_in", size=3), css_class="col-12 col-sm-7"),
                        Div(Field("proceedings", size=3), css_class="col-12 col-sm-5"),
                        css_class="row mb-0",
                    ),
                    Div(
                        Div(Field("supervisor"), css_class="col-6"),
                        Div(Field("officer"), css_class="col-6"),
                        css_class="row mb-0",
                    ),
                    Div(
                        Div(Field("orderby"), css_class="col-6"),
                        Div(Field("ordering"), css_class="col-6"),
                        css_class="row mb-0",
                    ),
                    css_class="col-md-7",
                ),
                Div(
                    Field("status", size=len(constants.PRODUCTION_STREAM_STATUS)),
                    css_class="col-md-5",
                ),
                css_class="row mb-0",
            ),
        )

    def search_results(self):
        streams = ProductionStream.objects.ongoing()

        streams = streams.annotate(
            latest_activity_annot=Greatest(Max("events__noted_on"), "opened", "closed")
        )

        if self.cleaned_data.get("accepted_in"):
            streams = streams.filter(
                submission__editorialdecision__for_journal=self.cleaned_data.get(
                    "accepted_in"
                ),
            )
        if self.cleaned_data.get("proceedings"):
            streams = streams.filter(
                submission__proceedings=self.cleaned_data.get("proceedings"),
            )
        if self.cleaned_data.get("identifier"):
            streams = streams.filter(
                submission__preprint__identifier_w_vn_nr__icontains=self.cleaned_data.get(
                    "identifier"
                ),
            )
        if self.cleaned_data.get("author"):
            streams = streams.filter(
                submission__author_list__icontains=self.cleaned_data.get("author"),
            )
        if self.cleaned_data.get("title"):
            streams = streams.filter(
                submission__title__icontains=self.cleaned_data.get("title"),
            )
        if self.cleaned_data.get("officer"):
            streams = streams.filter(officer=self.cleaned_data.get("officer"))
        if self.cleaned_data.get("supervisor"):
            streams = streams.filter(supervisor=self.cleaned_data.get("supervisor"))
        if self.cleaned_data.get("status"):
            streams = streams.filter(status__in=self.cleaned_data.get("status"))

        if not self.user.has_perm("scipost.can_view_all_production_streams"):
            # Restrict stream queryset if user is not supervisor
            streams = streams.filter_for_user(self.user.production_user)

        # Ordering of streams
        # Only order if both fields are set
        if (orderby_value := self.cleaned_data.get("orderby")) and (
            ordering_value := self.cleaned_data.get("ordering")
        ):
            # Remove the + from the ordering value, causes a Django error
            ordering_value = ordering_value.replace("+", "")

            # Ordering string is built by the ordering (+/-), and the field name
            # from the orderby field split by "," and joined together
            streams = streams.order_by(
                *[
                    ordering_value + order_part
                    for order_part in orderby_value.split(",")
                ]
            )

        return streams