import datetime

from django.utils import timezone
from django.db import models, transaction
from django.contrib.postgres.fields import JSONField
from django.urls import reverse

from .constants import ASSIGNMENT_REFUSAL_REASONS, SUBMISSION_STATUS, ASSIGNMENT_NULLBOOL,\
                       SUBMISSION_TYPE, ED_COMM_CHOICES, REFEREE_QUALIFICATION, QUALITY_SPEC,\
                       RANKING_CHOICES, REPORT_REC, REPORT_REFUSAL_CHOICES,\
                       REPORT_STATUSES, STATUS_UNVETTED
from .managers import SubmissionManager, EditorialAssignmentManager, EICRecommendationManager

from scipost.behaviors import ArxivCallable
from scipost.constants import TITLE_CHOICES
from scipost.fields import ChoiceArrayField
from scipost.models import Contributor
from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS
from journals.constants import SCIPOST_JOURNALS_SUBMIT, SCIPOST_JOURNALS_DOMAINS
from journals.models import Publication


###############
# Submissions:
###############
class Submission(ArxivCallable, models.Model):
    # Main submission fields
    author_comments = models.TextField(blank=True, null=True)
    author_list = models.CharField(max_length=1000, verbose_name="author list")
    discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics')
    domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS)
    editor_in_charge = models.ForeignKey(Contributor, related_name='EIC', blank=True, null=True,
                                         on_delete=models.CASCADE)
    is_current = models.BooleanField(default=True)
    is_resubmission = models.BooleanField(default=False)
    list_of_changes = models.TextField(blank=True, null=True)
    open_for_commenting = models.BooleanField(default=False)
    open_for_reporting = models.BooleanField(default=False)
    referees_flagged = models.TextField(blank=True, null=True)
    referees_suggested = models.TextField(blank=True, null=True)
    remarks_for_editors = models.TextField(blank=True, null=True)
    reporting_deadline = models.DateTimeField(default=timezone.now)
    secondary_areas = ChoiceArrayField(
        models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS),
        blank=True, null=True)
    # Status set by Editors
    status = models.CharField(max_length=30, choices=SUBMISSION_STATUS, default='unassigned')
    subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS,
                                    verbose_name='Primary subject area', default='Phys:QP')
    submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE,
                                       blank=True, null=True, default=None)
    submitted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE)
    submitted_to_journal = models.CharField(max_length=30, choices=SCIPOST_JOURNALS_SUBMIT,
                                            verbose_name="Journal to be submitted to")
    title = models.CharField(max_length=300)

    # Authors which have been mapped to contributors:
    authors = models.ManyToManyField(Contributor, blank=True, related_name='authors_sub')
    authors_claims = models.ManyToManyField(Contributor, blank=True,
                                            related_name='authors_sub_claims')
    authors_false_claims = models.ManyToManyField(Contributor, blank=True,
                                                  related_name='authors_sub_false_claims')
    abstract = models.TextField()

    # Arxiv identifiers with/without version number
    arxiv_identifier_w_vn_nr = models.CharField(max_length=15, default='0000.00000v0')
    arxiv_identifier_wo_vn_nr = models.CharField(max_length=10, default='0000.00000')
    arxiv_vn_nr = models.PositiveSmallIntegerField(default=1)
    arxiv_link = models.URLField(verbose_name='arXiv link (including version nr)')

    # Metadata
    metadata = JSONField(default={}, blank=True, null=True)
    submission_date = models.DateField(verbose_name='submission date', default=timezone.now)
    latest_activity = models.DateTimeField(default=timezone.now)

    objects = SubmissionManager()

    class Meta:
        permissions = (
            ('can_take_editorial_actions', 'Can take editorial actions'),
            )

    def __str__(self):
        header = (self.arxiv_identifier_w_vn_nr + ', '
                  + self.title[:30] + ' by ' + self.author_list[:30])
        if self.is_current:
            header += ' (current version)'
        else:
            header += ' (deprecated version ' + str(self.arxiv_vn_nr) + ')'
        try:
            header += ' (published as ' + self.publication.citation() + ')'
        except Publication.DoesNotExist:
            pass
        return header

    def get_absolute_url(self):
        return reverse('submissions:submission', args=[self.arxiv_identifier_w_vn_nr])

    @property
    def reporting_deadline_has_passed(self):
        return timezone.now() > self.reporting_deadline

    @transaction.atomic
    def finish_submission(self):
        if self.is_resubmission:
            self.mark_other_versions_as_deprecated()
            self.copy_authors_from_previous_version()
            self.copy_EIC_from_previous_version()
            self.set_resubmission_defaults()
        else:
            self.authors.add(self.submitted_by)

        self.save()

    @classmethod
    def same_version_exists(self, identifier):
        return self.objects.filter(arxiv_identifier_w_vn_nr=identifier).exists()

    @classmethod
    def different_versions(self, identifier):
        return self.objects.filter(arxiv_identifier_wo_vn_nr=identifier).order_by('-arxiv_vn_nr')

    def make_assignment(self):
        assignment = EditorialAssignment(
            submission=self,
            to=self.editor_in_charge,
            accepted=True,
            date_created=timezone.now(),
            date_answered=timezone.now(),
        )
        assignment.save()

    def set_resubmission_defaults(self):
        self.open_for_reporting = True
        self.open_for_commenting = True
        if self.other_versions()[0].submitted_to_journal == 'SciPost Physics Lecture Notes':
            self.reporting_deadline = timezone.now() + datetime.timedelta(days=56)
        else:
            self.reporting_deadline = timezone.now() + datetime.timedelta(days=28)

    def copy_EIC_from_previous_version(self):
        last_version = self.other_versions()[0]
        self.editor_in_charge = last_version.editor_in_charge
        self.status = 'EICassigned'

    def copy_authors_from_previous_version(self):
        last_version = self.other_versions()[0]

        for author in last_version.authors.all():
            self.authors.add(author)
        for author in last_version.authors_claims.all():
            self.authors_claims.add(author)
        for author in last_version.authors_false_claims.all():
            self.authors_false_claims.add(author)

    def mark_other_versions_as_deprecated(self):
        for sub in self.other_versions():
            sub.is_current = False
            sub.open_for_reporting = False
            sub.status = 'resubmitted'
            sub.save()

    def other_versions(self):
        return Submission.objects.filter(
            arxiv_identifier_wo_vn_nr=self.arxiv_identifier_wo_vn_nr
        ).exclude(pk=self.id).order_by('-arxiv_vn_nr')

    def count_accepted_invitations(self):
        return self.refereeinvitation_set.filter(accepted=True).count()

    def count_declined_invitations(self):
        return self.refereeinvitation_set.filter(accepted=False).count()

    def count_pending_invitations(self):
        return self.refereeinvitation_set.filter(accepted=None).count()

    def count_invited_reports(self):
        return self.reports.filter(status=1, invited=True).count()

    def count_contrib_reports(self):
        return self.reports.filter(status=1, invited=False).count()

    def count_obtained_reports(self):
        return self.reports.filter(status=1, invited__isnull=False).count()

    def count_refused_resports(self):
        return self.reports.filter(status__lte=-1).count()

    def count_awaiting_vetting(self):
        return self.reports.filter(status=0).count()


######################
# Editorial workflow #
######################

class EditorialAssignment(models.Model):
    submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
    to = models.ForeignKey(Contributor, on_delete=models.CASCADE)
    accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None)
    # attribute `deprecated' becomes True if another Fellow becomes Editor-in-charge
    deprecated = models.BooleanField(default=False)
    completed = models.BooleanField(default=False)
    refusal_reason = models.CharField(max_length=3, choices=ASSIGNMENT_REFUSAL_REASONS,
                                      blank=True, null=True)
    date_created = models.DateTimeField(default=timezone.now)
    date_answered = models.DateTimeField(blank=True, null=True)

    objects = EditorialAssignmentManager()

    def __str__(self):
        return (self.to.user.first_name + ' ' + self.to.user.last_name + ' to become EIC of ' +
                self.submission.title[:30] + ' by ' + self.submission.author_list[:30] +
                ', requested on ' + self.date_created.strftime('%Y-%m-%d'))


class RefereeInvitation(models.Model):
    submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
    referee = models.ForeignKey(Contributor, related_name='referee', blank=True, null=True,
                                on_delete=models.CASCADE)
    title = models.CharField(max_length=4, choices=TITLE_CHOICES)
    first_name = models.CharField(max_length=30, default='')
    last_name = models.CharField(max_length=30, default='')
    email_address = models.EmailField()
    # if Contributor not found, person is invited to register
    invitation_key = models.CharField(max_length=40, default='')
    date_invited = models.DateTimeField(default=timezone.now)
    invited_by = models.ForeignKey(Contributor, related_name='referee_invited_by',
                                   blank=True, null=True, on_delete=models.CASCADE)
    nr_reminders = models.PositiveSmallIntegerField(default=0)
    date_last_reminded = models.DateTimeField(blank=True, null=True)
    accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None)
    date_responded = models.DateTimeField(blank=True, null=True)
    refusal_reason = models.CharField(max_length=3, choices=ASSIGNMENT_REFUSAL_REASONS,
                                      blank=True, null=True)
    fulfilled = models.BooleanField(default=False)  # True if a Report has been submitted
    cancelled = models.BooleanField(default=False)  # True if EIC has deactivated invitation

    def __str__(self):
        return (self.first_name + ' ' + self.last_name + ' to referee ' +
                self.submission.title[:30] + ' by ' + self.submission.author_list[:30] +
                ', invited on ' + self.date_invited.strftime('%Y-%m-%d'))


###########
# Reports:
###########

class Report(models.Model):
    """ Both types of reports, invited or contributed. """
    status = models.SmallIntegerField(choices=REPORT_STATUSES, default=STATUS_UNVETTED)
    submission = models.ForeignKey(Submission, related_name='reports', on_delete=models.CASCADE)
    vetted_by = models.ForeignKey(Contributor, related_name="report_vetted_by",
                                  blank=True, null=True, on_delete=models.CASCADE)
    # `invited' filled from RefereeInvitation objects at moment of report submission
    invited = models.BooleanField(default=False)
    # `flagged' if author of report has been flagged by submission authors (surname check only)
    flagged = models.BooleanField(default=False)
    date_submitted = models.DateTimeField('date submitted')
    author = models.ForeignKey(Contributor, on_delete=models.CASCADE)
    qualification = models.PositiveSmallIntegerField(
        choices=REFEREE_QUALIFICATION,
        verbose_name="Qualification to referee this: I am ")
    # Text-based reporting
    strengths = models.TextField()
    weaknesses = models.TextField()
    report = models.TextField()
    requested_changes = models.TextField(verbose_name="requested changes")
    # Qualities:
    validity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101)
    significance = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101)
    originality = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101)
    clarity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101)
    formatting = models.SmallIntegerField(choices=QUALITY_SPEC,
                                          verbose_name="Quality of paper formatting")
    grammar = models.SmallIntegerField(choices=QUALITY_SPEC,
                                       verbose_name="Quality of English grammar")
    #
    recommendation = models.SmallIntegerField(choices=REPORT_REC)
    remarks_for_editors = models.TextField(default='', blank=True,
                                           verbose_name='optional remarks for the Editors only')
    anonymous = models.BooleanField(default=True, verbose_name='Publish anonymously')

    def __str__(self):
        return (self.author.user.first_name + ' ' + self.author.user.last_name + ' on ' +
                self.submission.title[:50] + ' by ' + self.submission.author_list[:50])


##########################
# EditorialCommunication #
##########################

class EditorialCommunication(models.Model):
    """
    Each individual communication between Editor-in-charge
    to and from Referees and Authors becomes an instance of this class.
    """
    submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
    referee = models.ForeignKey(Contributor, related_name='referee_in_correspondence',
                                blank=True, null=True, on_delete=models.CASCADE)
    comtype = models.CharField(max_length=4, choices=ED_COMM_CHOICES)
    timestamp = models.DateTimeField(default=timezone.now)
    text = models.TextField()

    def __str__(self):
        output = self.comtype
        if self.referee is not None:
            output += ' ' + self.referee.user.first_name + ' ' + self.referee.user.last_name
        output += (' for submission ' + self.submission.title[:30] + ' by '
                   + self.submission.author_list[:30])
        return output


############################
# Editorial Recommendation #
############################

# From the Editor-in-charge of a Submission
class EICRecommendation(models.Model):
    submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
    date_submitted = models.DateTimeField('date submitted', default=timezone.now)
    remarks_for_authors = models.TextField(blank=True, null=True)
    requested_changes = models.TextField(verbose_name="requested changes", blank=True, null=True)
    remarks_for_editorial_college = models.TextField(
        default='', blank=True, null=True,
        verbose_name='optional remarks for the Editorial College')
    recommendation = models.SmallIntegerField(choices=REPORT_REC)
    # Editorial Fellows who have assessed this recommendation:
    eligible_to_vote = models.ManyToManyField(Contributor, blank=True,
                                              related_name='eligible_to_vote')
    voted_for = models.ManyToManyField(Contributor, blank=True, related_name='voted_for')
    voted_against = models.ManyToManyField(Contributor, blank=True, related_name='voted_against')
    voted_abstain = models.ManyToManyField(Contributor, blank=True, related_name='voted_abstain')
    voting_deadline = models.DateTimeField('date submitted', default=timezone.now)

    objects = EICRecommendationManager()

    def __str__(self):
        return (self.submission.title[:20] + ' by ' + self.submission.author_list[:30] +
                ', ' + self.get_recommendation_display())

    @property
    def nr_for(self):
        return self.voted_for.count()

    @property
    def nr_against(self):
        return self.voted_against.count()

    @property
    def nr_abstained(self):
        return self.voted_abstain.count()