import json
from datetime import date

from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils import timezone
from graphene_django.utils import GraphQLTestCase

from vbv_lernwelt.assignment.models import (
    Assignment,
    AssignmentCompletion,
    AssignmentCompletionAuditLog,
)
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.creators.test_course import create_test_course
from vbv_lernwelt.course.models import CourseSession
from vbv_lernwelt.files.models import UploadFile
from vbv_lernwelt.notify.models import Notification


class AttendanceCourseUserMutationTestCase(GraphQLTestCase):
    GRAPHQL_URL = "/server/graphql/"

    def setUp(self):
        create_default_users()
        create_test_course(include_vv=False, with_sessions=True)
        self.course_session = CourseSession.objects.get(title="Test Bern 2022 a")
        self.trainer = User.objects.get(username="test-trainer1@example.com")
        self.student = User.objects.get(username="test-student1@example.com")

        self.assignment = Assignment.objects.get(
            slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
        )
        self.assignment_subtasks = self.assignment.filter_user_subtasks()
        self.assignment_task_ids = [t.id for t in self.assignment.tasks]

        # self.client.force_login(self.trainer)

    def test_student_can_upsertAssignmentCompletion(self):
        self.client.force_login(self.student)
        user_text_input = find_first(
            self.assignment_subtasks, pred=lambda x: x["type"] == "user_text_input"
        )

        task_id = self.assignment_task_ids[0]

        # Create file
        uploaded_file = SimpleUploadedFile("file.txt", b"file_content")
        file = UploadFile(
            original_file_name="file.txt",
            file_name="file.txt",
            file_type="text/plain",
            uploaded_by=self.student,
            file=uploaded_file,
        )
        file.full_clean()
        file.save()

        file_id = str(file.id)
        file_url = file.url

        completion_data_string = json.dumps(
            {
                user_text_input["id"]: {"user_data": {"text": "Hallo via API"}},
                task_id: {"user_data": {"fileId": file_id}},
            }
        ).replace('"', '\\"')

        query = f"""
        mutation {{
            upsert_assignment_completion(
                assignment_id: {self.assignment.id}
                course_session_id: {self.course_session.id}
                completion_status: IN_PROGRESS
                completion_data_string: "{completion_data_string}"
            ) {{
                assignment_completion {{
                    id
                    completion_status
                    completion_data
                    task_completion_data
                    assignment_user {{ id }}
                    assignment {{ id }}
              }}
            }}
        }}
        """

        response = self.query(query)
        self.assertResponseNoErrors(response)

        data = json.loads(response.content)["data"]["upsert_assignment_completion"][
            "assignment_completion"
        ]

        self.assertEqual(data["assignment_user"]["id"], str(self.student.id))
        self.assertEqual(data["assignment"]["id"], str(self.assignment.id))
        self.assertEqual(data["completion_status"], "IN_PROGRESS")
        self.assertDictEqual(
            data["completion_data"],
            {
                user_text_input["id"]: {"user_data": {"text": "Hallo via API"}},
                task_id: {"user_data": {"fileId": file_id}},
            },
        )

        task_data = data["task_completion_data"][task_id]
        self.maxDiff = None
        self.assertEqual(task_data["user_data"]["fileId"], file_id)
        self.assertEqual(task_data["user_data"]["fileInfo"]["id"], file_id)
        self.assertEqual(task_data["user_data"]["fileInfo"]["name"], "file.txt")
        self.assertTrue(
            task_data["user_data"]["fileInfo"]["url"].startswith(
                "https://s3.eu-central-1.amazonaws.com/myvbv-dev.iterativ.ch"
            )
        )

        # check DB data
        db_entry = AssignmentCompletion.objects.get(
            assignment_user=self.student,
            course_session_id=self.course_session.id,
            assignment_id=self.assignment.id,
        )
        self.assertEqual(db_entry.completion_status, "IN_PROGRESS")
        self.assertDictEqual(
            db_entry.completion_data,
            {
                user_text_input["id"]: {"user_data": {"text": "Hallo via API"}},
                task_id: {"user_data": {"fileId": file_id}},
            },
        )

        # submit the response
        completion_data_string = json.dumps(
            {
                user_text_input["id"]: {"user_data": {"text": "Hallo via API 2"}},
            }
        ).replace('"', '\\"')

        query = f"""
        mutation {{
            upsert_assignment_completion(
                assignment_id: {self.assignment.id}
                course_session_id: {self.course_session.id}
                completion_status: SUBMITTED
                completion_data_string: "{completion_data_string}"
            ) {{
                assignment_completion {{
                    id
                    completion_status
                    completion_data
                    assignment_user {{ id }}
                    assignment {{ id }}
              }}
            }}
        }}
        """

        response = self.query(query)
        self.assertResponseNoErrors(response)

        data = json.loads(response.content)["data"]["upsert_assignment_completion"][
            "assignment_completion"
        ]

        self.assertEqual(data["assignment_user"]["id"], str(self.student.id))
        self.assertEqual(data["assignment"]["id"], str(self.assignment.id))
        self.assertEqual(data["completion_status"], "SUBMITTED")
        self.assertDictEqual(
            data["completion_data"],
            {
                user_text_input["id"]: {"user_data": {"text": "Hallo via API 2"}},
                task_id: {"user_data": {"fileId": file_id}},
            },
        )

        # check DB data
        db_entry = AssignmentCompletion.objects.get(
            assignment_user=self.student,
            course_session_id=self.course_session.id,
            assignment_id=self.assignment.id,
        )
        self.assertEqual(db_entry.completion_status, "SUBMITTED")
        self.assertEqual(db_entry.submitted_at.date(), date.today())
        self.assertDictEqual(
            db_entry.completion_data,
            {
                user_text_input["id"]: {"user_data": {"text": "Hallo via API 2"}},
                task_id: {"user_data": {"fileId": file_id}},
            },
        )

        # check notification
        self.assertEqual(Notification.objects.count(), 1)
        notification = Notification.objects.first()
        self.assertEqual(
            "Test Student1 hat die geleitete Fallarbeit «Überprüfen einer Motorfahrzeugs-Versicherungspolice» abgegeben.",
            notification.verb,
        )
        self.assertEqual(
            "test-trainer1@example.com",
            notification.recipient.email,
        )
        self.assertEqual(
            "test-student1@example.com",
            notification.actor.email,
        )
        self.assertEqual(
            "USER_INTERACTION",
            notification.notification_category,
        )
        self.assertEqual(
            "CASEWORK_SUBMITTED",
            notification.notification_trigger,
        )
        self.assertEqual(
            notification.action_object,
            db_entry,
        )
        self.assertEqual(
            notification.course_session,
            self.course_session,
        )
        self.assertTrue(
            f"/course/test-lehrgang/cockpit/assignment" in notification.target_url
        )

        # second submit will fail
        completion_data_string = json.dumps(
            {
                user_text_input["id"]: {"user_data": {"text": "Hallo via API 3"}},
            }
        ).replace('"', '\\"')

        query = f"""
        mutation {{
            upsert_assignment_completion(
                assignment_id: {self.assignment.id}
                course_session_id: {self.course_session.id}
                completion_status: SUBMITTED
                completion_data_string: "{completion_data_string}"
            ) {{
                assignment_completion {{
                    id
                    completion_status
                    completion_data
                    assignment_user {{ id }}
                    assignment {{ id }}
              }}
            }}
        }}
        """

        response = self.query(query)
        self.assertResponseHasErrors(response)
        self.assertTrue("Cannot update completion status" in str(response.json()))

    def test_expert_can_gradeAssignmentCompletion(self):
        # setup AssignmentCompletion
        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.student,
            assignment=self.assignment,
            learning_content_page=self.assignment.find_attached_learning_content(),
            course_session=self.course_session,
            completion_status="SUBMITTED",
            submitted_at=timezone.now(),
            completion_data={
                user_text_input["id"]: {
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."}
                },
            },
        )

        self.client.force_login(self.trainer)

        completion_data_string = json.dumps(
            {
                user_text_input["id"]: {
                    "expert_data": {"points": 1, "comment": "Gut gemacht!"}
                },
            }
        ).replace('"', '\\"')

        query = f"""
        mutation {{
            upsert_assignment_completion(
                assignment_id: {self.assignment.id}
                assignment_user_id: "{self.student.id}"
                course_session_id: {self.course_session.id}
                completion_status: EVALUATION_IN_PROGRESS
                completion_data_string: "{completion_data_string}"
            ) {{
                assignment_completion {{
                    id
                    completion_status
                    completion_data
                    assignment_user {{ id }}
                    assignment {{ id }}
              }}
            }}
        }}
        """

        print(query)
        response = self.query(query)
        self.assertResponseNoErrors(response)

        data = json.loads(response.content)["data"]["upsert_assignment_completion"][
            "assignment_completion"
        ]

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

        db_entry = AssignmentCompletion.objects.get(
            assignment_user=self.student,
            course_session_id=self.course_session.id,
            assignment_id=self.assignment.id,
        )
        self.assertEqual(db_entry.completion_status, "EVALUATION_IN_PROGRESS")
        self.assertDictEqual(
            db_entry.completion_data,
            {
                user_text_input["id"]: {
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."},
                    "expert_data": {"points": 1, "comment": "Gut gemacht!"},
                },
            },
        )

        # finish grading
        completion_data_string = json.dumps(
            {
                user_text_input["id"]: {
                    "expert_data": {"points": 1, "comment": "Gut gemacht!"}
                },
            }
        ).replace('"', '\\"')

        query = f"""
        mutation {{
            upsert_assignment_completion(
                assignment_id: {self.assignment.id}
                assignment_user_id: "{self.student.id}"
                course_session_id: {self.course_session.id}
                completion_status: EVALUATION_SUBMITTED
                completion_data_string: "{completion_data_string}"
                evaluation_points: 16,
            ) {{
                assignment_completion {{
                    id
                    completion_status
                    completion_data
                    assignment_user {{ id }}
                    assignment {{ id }}
              }}
            }}
        }}
        """

        response = self.query(query)
        self.assertResponseNoErrors(response)

        data = json.loads(response.content)["data"]["upsert_assignment_completion"][
            "assignment_completion"
        ]

        self.assertEqual(data["assignment_user"]["id"], str(self.student.id))
        self.assertEqual(data["assignment"]["id"], str(self.assignment.id))
        self.assertEqual(data["completion_status"], "EVALUATION_SUBMITTED")
        self.assertDictEqual(
            data["completion_data"],
            {
                user_text_input["id"]: {
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."},
                    "expert_data": {"points": 1, "comment": "Gut gemacht!"},
                },
            },
        )

        db_entry = AssignmentCompletion.objects.get(
            assignment_user=self.student,
            course_session_id=self.course_session.id,
            assignment_id=self.assignment.id,
        )
        self.assertEqual(db_entry.completion_status, "EVALUATION_SUBMITTED")
        self.assertEqual(db_entry.evaluation_points, 16)
        self.assertEqual(db_entry.evaluation_max_points, 24)
        self.assertTrue(db_entry.evaluation_passed)
        self.assertDictEqual(
            db_entry.completion_data,
            {
                user_text_input["id"]: {
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."},
                    "expert_data": {"points": 1, "comment": "Gut gemacht!"},
                },
            },
        )

        # check notification
        self.assertEqual(Notification.objects.count(), 1)
        notification = Notification.objects.first()
        self.assertEqual(
            "Test Trainer1 hat die geleitete Fallarbeit «Überprüfen einer Motorfahrzeugs-Versicherungspolice» bewertet.",
            notification.verb,
        )
        self.assertEqual(
            "test-student1@example.com",
            notification.recipient.email,
        )
        self.assertEqual(
            "test-trainer1@example.com",
            notification.actor.email,
        )
        self.assertEqual(
            "USER_INTERACTION",
            notification.notification_category,
        )
        self.assertEqual(
            "CASEWORK_EVALUATED",
            notification.notification_trigger,
        )
        self.assertEqual(
            notification.action_object,
            db_entry,
        )
        self.assertEqual(
            notification.course_session,
            self.course_session,
        )
        self.assertEqual(
            notification.target_url,
            "/course/test-lehrgang/learn/fahrzeug/überprüfen-einer-motorfahrzeug-versicherungspolice",
        )

        # `EVALUATION_SUBMITTED` will create a new AssignmentCompletionAuditLog
        acl = AssignmentCompletionAuditLog.objects.get(
            assignment_user=self.student,
            course_session_id=self.course_session.id,
            assignment_id=self.assignment.id,
            completion_status="EVALUATION_SUBMITTED",
        )
        self.maxDiff = None
        self.assertDictEqual(
            acl.completion_data,
            {
                user_text_input["id"]: {
                    "id": user_text_input["id"],
                    "type": "user_text_input",
                    "value": {
                        "text": "Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest? Begründe deine Empfehlung",
                    },
                    "user_data": {"text": "Ich würde nichts weiteres empfehlen."},
                    "expert_data": {"points": 1, "comment": "Gut gemacht!"},
                },
            },
        )
        self.assertEqual(acl.evaluation_points, 16)
        self.assertEqual(acl.evaluation_max_points, 24)
        self.assertTrue(acl.evaluation_passed)

    def test_student_can_upsert_reflection(self):
        """
        when upserting a reflection, it will store also the page_id of the learning content
        """
        reflection = Assignment.objects.get(slug="test-lehrgang-assignment-reflexion")
        reflection_subtasks = reflection.filter_user_subtasks()
        reflection_learning_content = reflection.find_attached_learning_content()

        self.client.force_login(self.student)
        user_text_input = find_first(
            reflection_subtasks, pred=lambda x: x["type"] == "user_text_input"
        )

        completion_data_string = json.dumps(
            {
                user_text_input["id"]: {
                    "user_data": {"text": "Hallo via Reflection API"}
                },
            }
        ).replace('"', '\\"')

        query = f"""
        mutation {{
            upsert_assignment_completion(
                assignment_id: {reflection.id}
                course_session_id: {self.course_session.id}
                learning_content_page_id: {reflection_learning_content.id}
                completion_status: IN_PROGRESS
                completion_data_string: "{completion_data_string}"
            ) {{
                assignment_completion {{
                    id
                    completion_status
                    completion_data
                    assignment_user {{ id }}
                    assignment {{ id }}
                    learning_content_page_id
              }}
            }}
        }}
        """

        response = self.query(query)
        self.assertResponseNoErrors(response)

        data = json.loads(response.content)["data"]["upsert_assignment_completion"][
            "assignment_completion"
        ]
        print(data)
        self.assertEqual(data["assignment_user"]["id"], str(self.student.id))
        self.assertEqual(data["assignment"]["id"], str(reflection.id))
        self.assertEqual(data["completion_status"], "IN_PROGRESS")
        self.assertEqual(
            data["learning_content_page_id"], str(reflection_learning_content.id)
        )
        self.assertDictEqual(
            data["completion_data"],
            {
                user_text_input["id"]: {
                    "user_data": {"text": "Hallo via Reflection API"}
                },
            },
        )

        # check DB data
        db_entry = AssignmentCompletion.objects.get(
            assignment_user=self.student,
            course_session_id=self.course_session.id,
            assignment_id=reflection.id,
            learning_content_page_id=reflection_learning_content.id,
        )
        self.assertEqual(db_entry.completion_status, "IN_PROGRESS")
        self.assertDictEqual(
            db_entry.completion_data,
            {
                user_text_input["id"]: {
                    "user_data": {"text": "Hallo via Reflection API"}
                },
            },
        )
