from datetime import datetime
from typing import Tuple

from graphene_django.utils import GraphQLTestCase

from vbv_lernwelt.assignment.models import Assignment, AssignmentType
from vbv_lernwelt.course.creators.test_utils import (
    add_course_session_group_supervisor,
    add_course_session_user,
    create_assignment,
    create_assignment_completion,
    create_assignment_learning_content,
    create_circle,
    create_competence_certificate,
    create_course,
    create_course_session,
    create_course_session_assignment,
    create_course_session_edoniq_test,
    create_course_session_group,
    create_user,
)
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.course_session.models import (
    CourseSessionAssignment,
    CourseSessionEdoniqTest,
)
from vbv_lernwelt.learnpath.models import Circle


class AssignmentTestCase(GraphQLTestCase):
    GRAPHQL_URL = "/server/graphql/"
    GRAPHQL_QUERY = f"""query($course_id: ID!) {{
                        course_statistics(course_id: $course_id) {{
                        assignments{{
                            summary{{
                                completed_count
                                average_passed
                            }}
                            records{{
                                course_session_id
                                course_session_assignment_id
                                circle_id
                                generation
                                assignment_title
                                assignment_type_translation_key
                                details_url
                                deadline
                                metrics {{
                                    passed_count
                                    failed_count
                                    unranked_count
                                    ranking_completed
                                    average_passed
                                }}
                            }}
                        }}
                    }}
                }}"""

    def setUp(self):
        self.course, self.course_page = create_course("Test Course")
        self.course_session = create_course_session(course=self.course, title=":)")
        self.circle, _ = create_circle(title="Circle", course_page=self.course_page)

        self.supervisor = create_user("supervisor")

        group = create_course_session_group(course_session=self.course_session)
        add_course_session_group_supervisor(group=group, user=self.supervisor)

        self.m1 = create_user("member_1")
        add_course_session_user(
            course_session=self.course_session,
            user=self.m1,
            role=CourseSessionUser.Role.MEMBER,
        )

        self.m2 = create_user("member_2")
        add_course_session_user(
            course_session=self.course_session,
            user=self.m2,
            role=CourseSessionUser.Role.MEMBER,
        )

        self.m3 = create_user("member_3")
        add_course_session_user(
            course_session=self.course_session,
            user=self.m3,
            role=CourseSessionUser.Role.MEMBER,
        )

        self.e1 = create_user("expert_1")
        add_course_session_user(
            course_session=self.course_session,
            user=self.e1,
            role=CourseSessionUser.Role.EXPERT,
        )

        self.client.force_login(self.supervisor)

    def test_dashboard_contains_casework(self):
        self._test_assignment_type_dashboard_details(
            assignment_type=AssignmentType.CASEWORK
        )

    def test_dashboard_contains_edoniq_tests(self):
        self._test_assignment_type_dashboard_details(
            assignment_type=AssignmentType.EDONIQ_TEST
        )

    def test_dashboard_not_contains_unsupported_types(self):
        """
        Since everything is mixed in the same table, we need to make sure
        that the dashboard only contains the supported types does not
        get confused by the unsupported ones.
        """

        irrelevant_types_for_dashboard = set(AssignmentType) - {
            AssignmentType.CASEWORK,
            AssignmentType.EDONIQ_TEST,
        }

        for assignment_type in irrelevant_types_for_dashboard:
            self._test_assignment_type_not_in_dashboard(assignment_type=assignment_type)

    def _test_assignment_type_dashboard_details(self, assignment_type: AssignmentType):
        # GIVEN
        assignment, csa = mix_assignment_cocktail(
            assignment_type=assignment_type,
            deadline_at=datetime(2000, 4, 1),
            course_session=self.course_session,
            circle=self.circle,
            add_competence_certificate=True,
        )

        create_assignment_completion(
            user=self.m1,
            assignment=assignment,
            course_session=self.course_session,
        )

        # WHEN
        response = self.query(
            self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)}
        )

        # THEN
        self.assertResponseNoErrors(response)
        dashboard = response.json()["data"]["course_statistics"]

        records = dashboard["assignments"]["records"]
        self.assertEqual(len(records), 1)

        record = records[0]

        if isinstance(csa, CourseSessionAssignment):
            due_date = csa.submission_deadline
        else:
            due_date = csa.deadline

        self.assertEqual(record["course_session_id"], str(self.course_session.id))
        self.assertEqual(record["course_session_assignment_id"], str(csa.id))
        self.assertEqual(record["generation"], str(self.course_session.generation))
        self.assertEqual(record["circle_id"], str(self.circle.id))
        self.assertEqual(record["details_url"], due_date.url_expert)
        self.assertEqual(datetime.fromisoformat(record["deadline"]), due_date.start)

        self.assertEqual(
            record["assignment_title"],
            csa.learning_content.content_assignment.title,
        )
        self.assertEqual(
            record["assignment_type_translation_key"],
            due_date.assignment_type_translation_key,
        )

    def _test_assignment_type_not_in_dashboard(self, assignment_type: AssignmentType):
        _, csa = mix_assignment_cocktail(
            assignment_type=assignment_type,
            course_session=self.course_session,
            circle=self.circle,
        )

        # WHEN
        response = self.query(
            self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)}
        )

        # THEN
        self.assertResponseNoErrors(response)
        dashboard = response.json()["data"]["course_statistics"]

        records = dashboard["assignments"]["records"]
        self.assertEqual(len(records), 0)

    def test_metrics_summary(self):
        # GIVEN
        assignment_1, _ = mix_assignment_cocktail(
            deadline_at=datetime(1990, 4, 1),
            assignment_type=AssignmentType.CASEWORK,
            course_session=self.course_session,
            circle=self.circle,
            add_competence_certificate=True,
        )

        assignment_2, _ = mix_assignment_cocktail(
            deadline_at=datetime(2000, 4, 1),
            assignment_type=AssignmentType.EDONIQ_TEST,
            course_session=self.course_session,
            circle=self.circle,
            add_competence_certificate=True,
        )

        assignment_3, _ = mix_assignment_cocktail(
            deadline_at=datetime(2010, 4, 1),
            assignment_type=AssignmentType.CASEWORK,
            course_session=self.course_session,
            circle=self.circle,
            add_competence_certificate=True,
        )

        # no completions for this assignment yet
        assignment_4, _ = mix_assignment_cocktail(
            deadline_at=datetime(2020, 4, 1),
            assignment_type=AssignmentType.EDONIQ_TEST,
            course_session=self.course_session,
            circle=self.circle,
            add_competence_certificate=False,
        )

        # assignment 1
        assigment_1_results = [
            (self.m1, True),  # passed
            (self.m2, False),  # failed
            (self.m3, None),  # unranked
        ]

        for user, has_passed in assigment_1_results:
            if has_passed is None:
                continue
            create_assignment_completion(
                user=user,
                assignment=assignment_1,
                course_session=self.course_session,
                has_passed=has_passed,
            )

        # assignment 2
        assignment_2_results = [
            (self.m1, True),  # passed
            (self.m2, True),  # passed
            (self.m3, False),  # failed
        ]

        for user, has_passed in assignment_2_results:
            create_assignment_completion(
                user=user,
                assignment=assignment_2,
                course_session=self.course_session,
                has_passed=has_passed,
            )

        # assignment 3
        assignment_3_results = [
            (self.m1, True),  # passed
            (self.m2, True),  # passed
            (self.m3, True),  # passed
        ]
        for user, has_passed in assignment_3_results:
            create_assignment_completion(
                user=user,
                assignment=assignment_3,
                course_session=self.course_session,
                has_passed=has_passed,
            )

        # WHEN
        response = self.query(
            self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)}
        )

        # THEN
        self.assertResponseNoErrors(response)
        dashboard = response.json()["data"]["course_statistics"]

        # 1 -> incomplete (not counted for average)
        # 2 -> complete 66% passed  ...
        # 3 -> complete 100% passed --> 67%
        # 4 -> not included in certificate
        summary = dashboard["assignments"]["summary"]
        self.assertEqual(summary["completed_count"], 3)
        self.assertEqual(summary["average_passed"], 67.0)

        records = dashboard["assignments"]["records"]
        self.assertEqual(len(records), 3)

        # 1 -> assigment_1_results (oldest)
        assignment_1_metrics = records[0]["metrics"]
        self.assertEqual(assignment_1_metrics["passed_count"], 1)
        self.assertEqual(assignment_1_metrics["failed_count"], 1)
        self.assertEqual(assignment_1_metrics["unranked_count"], 1)
        self.assertEqual(assignment_1_metrics["ranking_completed"], True)
        self.assertEqual(assignment_1_metrics["average_passed"], 34)

        # 2 -> assignment_2_results
        assignment_2_metrics = records[1]["metrics"]
        self.assertEqual(assignment_2_metrics["passed_count"], 2)
        self.assertEqual(assignment_2_metrics["failed_count"], 1)
        self.assertEqual(assignment_2_metrics["unranked_count"], 0)
        self.assertEqual(assignment_2_metrics["ranking_completed"], True)
        self.assertEqual(assignment_2_metrics["average_passed"], 67)

        # 3 -> assignment_3_results
        assignment_3_metrics = records[2]["metrics"]
        self.assertEqual(assignment_3_metrics["passed_count"], 3)
        self.assertEqual(assignment_3_metrics["failed_count"], 0)
        self.assertEqual(assignment_3_metrics["unranked_count"], 0)
        self.assertEqual(assignment_3_metrics["ranking_completed"], True)
        self.assertEqual(assignment_3_metrics["average_passed"], 100)


def mix_assignment_cocktail(
    assignment_type: AssignmentType,
    course_session: CourseSession,
    circle: Circle,
    add_competence_certificate: bool = False,
    deadline_at: datetime | None = None,
) -> Tuple[Assignment, CourseSessionAssignment | CourseSessionEdoniqTest]:
    """
    Little test helper to create a course session assignment or edoniq test based
    on the given assignment type.
    """

    assignment = create_assignment(
        course=course_session.course,
        assignment_type=assignment_type,
    )

    if add_competence_certificate:
        certificate = create_competence_certificate(course=course_session.course)
        assignment.competence_certificate = certificate
        assignment.save()

    if assignment_type == AssignmentType.EDONIQ_TEST:
        cset = create_course_session_edoniq_test(
            deadline_at=deadline_at,
            course_session=course_session,
            learning_content_edoniq_test=create_assignment_learning_content(
                circle=circle,
                assignment=assignment,
            ),
        )
        return assignment, cset
    else:
        csa = create_course_session_assignment(
            deadline_at=deadline_at,
            course_session=course_session,
            learning_content_assignment=create_assignment_learning_content(
                circle=circle,
                assignment=assignment,
            ),
        )
        return assignment, csa
