from datetime import date

from django.test import TestCase
from django.utils import timezone
from rest_framework import serializers

from vbv_lernwelt.assignment.models import (
    Assignment,
    AssignmentCompletion,
    AssignmentCompletionAuditLog,
)
from vbv_lernwelt.assignment.services import update_assignment_completion
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.utils import find_first
from vbv_lernwelt.course.consts import COURSE_TEST_ID
from vbv_lernwelt.course.creators.test_course import create_test_course
from vbv_lernwelt.course.models import CourseSession
from vbv_lernwelt.learnpath.models import LearningContentAssignment


class UpdateAssignmentCompletionTestCase(TestCase):
    def setUp(self):
        create_default_users()
        self.course = create_test_course(include_vv=False)
        self.assignment = (
            self.course.coursepage.get_descendants()
            .exact_type(Assignment)
            .filter(assignment__assignment_type="CASEWORK")
            .first()
            .specific
        )
        self.course_session = CourseSession.objects.create(
            course_id=COURSE_TEST_ID,
            title="Bern 2022 a",
        )
        self.user = User.objects.get(username="student")
        self.trainer = User.objects.get(username="admin")

    def test_can_store_new_user_input(self):
        subtasks = self.assignment.filter_user_subtasks()
        user_text_input = find_first(
            subtasks, pred=lambda x: x["type"] == "user_text_input"
        )
        user_confirmation = find_first(
            subtasks, pred=lambda x: x["type"] == "user_confirmation"
        )

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                user_text_input["id"]: {
                    "user_data": {"text": "Viel habe ich nicht zu sagen..."}
                },
                user_confirmation["id"]: {"user_data": {"confirmation": True}},
            },
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )
        self.assertDictEqual(
            ac.completion_data,
            {
                user_text_input["id"]: {
                    "user_data": {"text": "Viel habe ich nicht zu sagen..."}
                },
                user_confirmation["id"]: {"user_data": {"confirmation": True}},
            },
        )

    def test_can_update_user_input(self):
        subtasks = self.assignment.filter_user_subtasks(
            subtask_types=["user_text_input"]
        )
        user_text_input0 = subtasks[0]
        user_text_input1 = subtasks[1]

        ac = AssignmentCompletion.objects.create(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                user_text_input0["id"]: {
                    "user_data": {"text": "Am Anfang war das Wort... 0"}
                },
                user_text_input1["id"]: {
                    "user_data": {"text": "Am Anfang war das Wort... 1"}
                },
            },
        )

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                user_text_input1["id"]: {
                    "user_data": {"text": "Viel mehr gibt es nicht zu sagen."}
                }
            },
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )
        user_input0 = ac.completion_data[user_text_input0["id"]]["user_data"]["text"]
        user_input1 = ac.completion_data[user_text_input1["id"]]["user_data"]["text"]
        self.assertEqual(user_input0, "Am Anfang war das Wort... 0")
        self.assertEqual(user_input1, "Viel mehr gibt es nicht zu sagen.")

    def test_will_not_store_unknown_user_tasks(self):
        random_uuid = "7b60903b-d2a5-4798-b6cd-5b51e63e98ab"

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                random_uuid: {
                    "user_data": {"text": "Viel mehr gibt es nicht zu sagen."}
                }
            },
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )
        self.assertEqual(ac.completion_data, {})

    def test_completion_status_submitted(self):
        subtasks = self.assignment.filter_user_subtasks(
            subtask_types=["user_text_input"]
        )
        user_text_input0 = subtasks[0]
        user_text_input1 = subtasks[1]

        ac = AssignmentCompletion.objects.create(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                user_text_input0["id"]: {
                    "user_data": {"text": "Am Anfang war das Wort... 0"}
                },
                user_text_input1["id"]: {
                    "user_data": {"text": "Am Anfang war das Wort... 1"}
                },
            },
        )

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="SUBMITTED",
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )

        self.assertEqual(ac.completion_status, "SUBMITTED")
        self.assertEqual(ac.submitted_at.date(), date.today())

        # will create AssignmentCompletionAuditLog entry
        acl = AssignmentCompletionAuditLog.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="SUBMITTED",
        )
        self.assertEqual(acl.created_at.date(), date.today())
        self.assertEqual(acl.assignment_user_email, "student")
        self.assertEqual(
            acl.assignment_slug,
            "test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice",
        )

        # AssignmentCompletionAuditLog entry will remain event after deletion of foreign keys
        ac.delete()
        self.user.delete()
        self.course.coursepage.get_descendants().exact_type(
            LearningContentAssignment
        ).delete()
        self.assignment.delete()
        acl = AssignmentCompletionAuditLog.objects.get(id=acl.id)
        self.assertEqual(acl.created_at.date(), date.today())
        self.assertEqual(acl.assignment_user_email, "student")
        self.assertEqual(
            acl.assignment_slug,
            "test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice",
        )
        self.assertIsNone(acl.assignment_user)
        self.assertIsNone(acl.assignment)

    def test_completion_status_submitted_cannot_submit_twice(self):
        subtasks = self.assignment.filter_user_subtasks(
            subtask_types=["user_text_input"]
        )
        user_text_input0 = subtasks[0]
        user_text_input1 = subtasks[1]

        ac = AssignmentCompletion.objects.create(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            submitted_at=timezone.now(),
            completion_status="SUBMITTED",
            completion_data={
                user_text_input0["id"]: {
                    "user_data": {"text": "Am Anfang war das Wort... 0"}
                },
                user_text_input1["id"]: {
                    "user_data": {"text": "Am Anfang war das Wort... 1"}
                },
            },
        )

        with self.assertRaises(serializers.ValidationError) as error:
            update_assignment_completion(
                assignment_user=self.user,
                assignment=self.assignment,
                course_session=self.course_session,
                completion_status="SUBMITTED",
            )

        # can submit twice with flag
        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="SUBMITTED",
            validate_completion_status_change=False,
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )

        self.assertEqual(ac.completion_status, "SUBMITTED")
        self.assertEqual(ac.submitted_at.date(), date.today())

    def test_copy_task_data(self):
        subtasks = self.assignment.filter_user_subtasks(
            subtask_types=["user_text_input"]
        )
        user_text_input = find_first(
            subtasks,
            pred=lambda x: (value := x.get("value"))
            and value.get("text", "").startswith(
                "Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
            ),
        )

        ac = AssignmentCompletion.objects.create(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                user_text_input["id"]: {
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."}
                },
            },
        )

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="SUBMITTED",
            copy_task_data=True,
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )

        self.assertEqual(ac.completion_status, "SUBMITTED")
        user_input = ac.completion_data[user_text_input["id"]]
        self.assertEqual(
            user_input["user_data"]["text"], "Ich würde nichts weiteres empfehlen."
        )
        self.assertTrue(
            user_input["value"]["text"].startswith(
                "Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
            )
        )

    def test_can_evaluate_with_evaluation_tasks(self):
        ac = AssignmentCompletion.objects.create(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="SUBMITTED",
        )

        evaluation_task = self.assignment.get_evaluation_tasks()[0]

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                evaluation_task["id"]: {
                    "expert_data": {"points": 2, "text": "Gut gemacht!"}
                },
            },
            completion_status="EVALUATION_IN_PROGRESS",
            evaluation_user=self.trainer,
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )

        self.assertEqual(ac.completion_status, "EVALUATION_IN_PROGRESS")
        trainer_input = ac.completion_data[evaluation_task["id"]]
        self.assertDictEqual(
            trainer_input["expert_data"], {"points": 2, "text": "Gut gemacht!"}
        )

    def test_can_add_evaluation_data_without_loosing_user_input_data(self):
        subtasks = self.assignment.filter_user_subtasks(
            subtask_types=["user_text_input"]
        )
        user_text_input = find_first(
            subtasks,
            pred=lambda x: (value := x.get("value"))
            and value.get("text", "").startswith(
                "Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
            ),
        )

        ac = AssignmentCompletion.objects.create(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="SUBMITTED",
            completion_data={
                user_text_input["id"]: {
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."}
                },
            },
        )

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                user_text_input["id"]: {
                    "expert_data": {"points": 1, "comment": "Gut gemacht!"}
                },
            },
            completion_status="EVALUATION_IN_PROGRESS",
            evaluation_user=self.trainer,
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )

        self.assertEqual(ac.completion_status, "EVALUATION_IN_PROGRESS")
        user_input = ac.completion_data[user_text_input["id"]]
        self.assertDictEqual(
            user_input["expert_data"], {"points": 1, "comment": "Gut gemacht!"}
        )
        self.assertEqual(
            user_input["user_data"]["text"], "Ich würde nichts weiteres empfehlen."
        )

    def test_cannot_evaluate_data_without_evaluation_user(self):
        subtasks = self.assignment.filter_user_subtasks(
            subtask_types=["user_text_input"]
        )
        user_text_input = find_first(
            subtasks,
            pred=lambda x: (value := x.get("value"))
            and value.get("text", "").startswith(
                "Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
            ),
        )

        ac = AssignmentCompletion.objects.create(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="IN_PROGRESS",
            completion_data={
                user_text_input["id"]: {
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."}
                },
            },
        )

        with self.assertRaises(serializers.ValidationError) as error:
            update_assignment_completion(
                assignment_user=self.user,
                assignment=self.assignment,
                course_session=self.course_session,
                completion_status="EVALUATION_IN_PROGRESS",
                completion_data={
                    user_text_input["id"]: {
                        "expert_data": {"points": 1, "comment": "Gut gemacht!"}
                    },
                },
            )

        self.assertTrue(
            "evaluation_user" in error.exception.detail,
        )

    def test_can_submit_evaluation(self):
        subtasks = self.assignment.filter_user_subtasks(
            subtask_types=["user_text_input"]
        )
        user_text_input = find_first(
            subtasks,
            pred=lambda x: (value := x.get("value"))
            and value.get("text", "").startswith(
                "Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
            ),
        )

        ac = AssignmentCompletion.objects.create(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="SUBMITTED",
            completion_data={
                user_text_input["id"]: {
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."}
                },
            },
        )

        evaluation_task = self.assignment.get_evaluation_tasks()[0]

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={
                evaluation_task["id"]: {
                    "expert_data": {"points": 2, "text": "Gut gemacht!"}
                },
            },
            completion_status="EVALUATION_IN_PROGRESS",
            evaluation_user=self.trainer,
        )

        with self.assertRaises(serializers.ValidationError) as error:
            # not setting grade will raise an error
            update_assignment_completion(
                assignment_user=self.user,
                assignment=self.assignment,
                course_session=self.course_session,
                completion_data={},
                completion_status="EVALUATION_SUBMITTED",
                evaluation_user=self.trainer,
                evaluation_grade=None,
                evaluation_points=None,
            )

        update_assignment_completion(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_data={},
            completion_status="EVALUATION_SUBMITTED",
            evaluation_user=self.trainer,
            evaluation_grade=4.5,
            evaluation_points=16,
        )

        ac = AssignmentCompletion.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
        )

        self.assertEqual(ac.completion_status, "EVALUATION_SUBMITTED")
        self.assertEqual(ac.evaluation_grade, 4.5)
        self.assertEqual(ac.evaluation_points, 16)
        trainer_input = ac.completion_data[evaluation_task["id"]]
        self.assertDictEqual(
            trainer_input["expert_data"], {"points": 2, "text": "Gut gemacht!"}
        )
        user_input = ac.completion_data[user_text_input["id"]]
        self.assertDictEqual(
            user_input["user_data"], {"text": "Ich würde nichts weiteres empfehlen."}
        )

        # will create AssignmentCompletionAuditLog entry
        acl = AssignmentCompletionAuditLog.objects.get(
            assignment_user=self.user,
            assignment=self.assignment,
            course_session=self.course_session,
            completion_status="EVALUATION_SUBMITTED",
        )
        self.assertEqual(acl.created_at.date(), date.today())
        self.assertEqual(acl.assignment_user_email, "student")
        self.assertEqual(
            acl.assignment_slug,
            "test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice",
        )
        trainer_input = acl.completion_data[evaluation_task["id"]]
        self.assertDictEqual(
            trainer_input["expert_data"], {"points": 2, "text": "Gut gemacht!"}
        )
        user_input = acl.completion_data[user_text_input["id"]]
        self.assertDictEqual(
            user_input["user_data"], {"text": "Ich würde nichts weiteres empfehlen."}
        )

        # AssignmentCompletionAuditLog entry will remain event after deletion of foreign keys
        ac.delete()
        self.user.delete()
        self.course.coursepage.get_descendants().exact_type(
            LearningContentAssignment
        ).delete()
        self.assignment.delete()
        acl = AssignmentCompletionAuditLog.objects.get(id=acl.id)
        self.assertEqual(acl.created_at.date(), date.today())
        self.assertEqual(acl.assignment_user_email, "student")
        self.assertEqual(
            acl.assignment_slug,
            "test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice",
        )
        self.assertIsNone(acl.assignment_user)
        self.assertIsNone(acl.assignment)
