"""
Workflow models are intended to track which step the student is in during the
assessment process. The submission state is not explicitly tracked because
the assessment workflow only begins after a submission has been created.

NOTE: We've switched to migrations, so if you make any edits to this file, you
need to then generate a matching migration for it using:

    ./manage.py schemamigration openassessment.workflow --auto

"""
from django.db import models
from django_extensions.db.fields import UUIDField
from model_utils import Choices
from model_utils.models import StatusModel, TimeStampedModel

from submissions import api as sub_api


class AssessmentWorkflow(TimeStampedModel, StatusModel):
    """Tracks the open-ended assessment status of a student submission.

    It's important to note that although we track the status as an explicit
    field here, it is not the canonical status. This is because the
    determination of what we need to do in order to be "done" is specified by
    the OpenAssessmentBlock problem definition and can change. So every time
    we are asked where the student is, we have to query the peer, self, and
    later other assessment APIs with the latest requirements (e.g. "number of
    submissions you have to assess = 5"). The "status" field on this model is
    an after the fact recording of the last known state of that information so
    we can search easily.
    """
    STATUS = Choices(  # implicit "status" field
        "peer",  # User needs to assess peer submissions
        "self",  # User needs to assess themselves
        "waiting",  # User has done all necessary assessment but hasn't been
                    # graded yet -- we're waiting for assessments of their
                    # submission by others.
        "done",  # Complete
    )

    submission_uuid = models.CharField(max_length=36, db_index=True, unique=True)
    uuid = UUIDField(version=1, db_index=True, unique=True)

    class Meta:
        ordering = ["-created"]
        # TODO: In migration, need a non-unique index on (course_id, item_id, status)

    @property
    def score(self):
        """Latest score for the submission we're tracking.

        Note that while it is usually the case that we're setting the score,
        that may not always be the case. We may have some course staff override.
        """
        return sub_api.get_latest_score_for_submission(self.submission_uuid)

    def status_details(self, assessment_requirements):
        return {
            "peer": {
                "complete": self._is_peer_complete(assessment_requirements),
            },
            "self": {
                "complete": self._is_self_complete(),
            },
        }

    def _is_peer_complete(self, assessment_requirements):
        from openassessment.assessment import peer_api
        peer_requirements = assessment_requirements["peer"]
        return peer_api.is_complete(self.submission_uuid, peer_requirements)

    def _is_self_complete(self):
        from openassessment.assessment import self_api
        return self_api.is_complete(self.submission_uuid)

    def update_from_assessments(self, assessment_requirements):
        """Query self and peer APIs and change our status if appropriate.

        If the status is done, we do nothing. Once something is done, we never
        move back to any other status.

        By default, an `AssessmentWorkflow` starts with status `peer`.

        If the peer API says that our submitter's requirements are met -- that
        the submitter of the submission we're tracking has assessed the required
        number of other submissions -- then the status will move to `self`.

        If the self API says that the person who created the submission we're
        tracking has assessed themselves, then we move to `waiting`.

        If we're in the `waiting` status, and the peer API says it can score
        this submission (meaning other students have created enough assessments
        of it), then we record the score in the submissions API and move our
        `status` to `done`.

        Args:
            assessment_requirements (dict): Dictionary that currently looks like:
                `{"peer": {"must_grade": <int>, "must_be_graded_by": <int>}}`
                `must_grade` is the number of assessments a student must complete.
                `must_be_graded_by` is the number of assessments a submission must
                receive to be scored. `must_grade` should be greater than
                `must_be_graded_by` to ensure that everyone will get scored.
                The intention is to eventually pass in more assessment sequence
                specific requirements in this dict.

        """
        from openassessment.assessment import peer_api

        # If we're done, we're done -- it doesn't matter if requirements have
        # changed because we've already written a score.
        if self.status == self.STATUS.done:
            return

        # Have they completed the peer and self steps?
        peer_complete = self._is_peer_complete(assessment_requirements)
        self_complete = self._is_self_complete()

        if peer_complete and self_complete:
            # If they've completed both, they're at least waiting, possibly done
            new_status = self.STATUS.waiting
        elif peer_complete:
            # If they haven't done self assessment yet, that's their status
            new_status = self.STATUS.self
        else:
            # Default starting status is peer
            new_status = self.STATUS.peer

        # If we're at least waiting, let's check if we have a peer score and
        # can move all the way to done
        if new_status == self.STATUS.waiting:
            score = peer_api.get_score(
                self.submission_uuid, assessment_requirements["peer"]
            )
            if score:
                sub_api.set_score(
                    self.submission_uuid,
                    score["points_earned"],
                    score["points_possible"]
                )
                new_status = self.STATUS.done

        # Finally save our changes if the status has changed
        if self.status != new_status:
            self.status = new_status
            self.save()



# Just here to record thoughts for later:
#
# class AssessmentWorkflowEvent(models.Model):
#     workflow = models.ForeignKey(AssessmentWorkflow, related_name="events")
#     app = models.CharField(max_length=50)
#     event_type = models.CharField(max_length=255)
#     event_data = models.TextField()
#     description = models.TextField()
#     created_at = models.DateTimeField(default=now, db_index=True)