import math
from typing import List

import graphene

import vbv_lernwelt.assignment.models
from vbv_lernwelt.assignment.models import (
    AssignmentCompletion,
    AssignmentCompletionStatus,
    AssignmentType,
)
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.course_session.models import (
    CourseSessionAssignment,
    CourseSessionEdoniqTest,
)


class AssignmentCompletionMetricsType(graphene.ObjectType):
    _id = graphene.ID(required=True)
    passed_count = graphene.Int(required=True)
    failed_count = graphene.Int(required=True)
    unranked_count = graphene.Int(required=True)
    ranking_completed = graphene.Boolean(required=True)
    average_passed = graphene.Float(required=True)


class AssignmentStatisticsRecordType(graphene.ObjectType):
    _id = graphene.ID(required=True)
    course_session_id = graphene.ID(required=True)
    course_session_assignment_id = graphene.ID(required=True)
    circle_id = graphene.ID(required=True)
    generation = graphene.String(required=True)
    assignment_type_translation_key = graphene.String(required=True)
    assignment_title = graphene.String(required=True)
    deadline = graphene.DateTime(required=True)
    metrics = graphene.Field(AssignmentCompletionMetricsType, required=True)
    details_url = graphene.String(required=True)


class AssignmentStatisticsSummaryType(graphene.ObjectType):
    _id = graphene.ID(required=True)
    completed_count = graphene.Int(required=True)
    average_passed = graphene.Float(required=True)


class AssignmentsStatisticsType(graphene.ObjectType):
    _id = graphene.ID(required=True)
    records = graphene.List(
        graphene.NonNull(AssignmentStatisticsRecordType), required=True
    )
    summary = graphene.Field(AssignmentStatisticsSummaryType, required=True)


def create_assignment_summary(course_id, metrics) -> AssignmentStatisticsSummaryType:
    completed_metrics = [m for m in metrics if m.ranking_completed]

    if not completed_metrics:
        return AssignmentStatisticsSummaryType(
            _id=course_id, completed_count=0, average_passed=0  # noqa
        )

    completed_count = len(completed_metrics)

    average_passed_completed = (
        sum([m.average_passed for m in completed_metrics]) / completed_count
    )

    return AssignmentStatisticsSummaryType(
        _id=course_id,  # noqa
        completed_count=completed_count,  # noqa
        average_passed=average_passed_completed,  # noqa
    )


def get_assignment_completion_metrics(
    course_session: CourseSession, assignment: vbv_lernwelt.assignment.models.Assignment
) -> AssignmentCompletionMetricsType:
    course_session_users = CourseSessionUser.objects.filter(
        course_session=course_session,
        role=CourseSessionUser.Role.MEMBER,
    ).values_list("user", flat=True)

    evaluation_results = AssignmentCompletion.objects.filter(
        completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
        assignment_user__in=course_session_users,
        course_session=course_session,
        assignment=assignment,
    ).values_list("evaluation_passed", flat=True)

    passed_count = len([passed for passed in evaluation_results if passed])
    failed_count = len(evaluation_results) - passed_count

    participants_count = len(course_session_users)
    unranked_count = participants_count - passed_count - failed_count

    if participants_count == 0:
        average_passed = 0
    else:
        average_passed = math.ceil(passed_count / participants_count * 100)

    return AssignmentCompletionMetricsType(
        _id=f"{course_session.id}-{assignment.id}",  # noqa
        passed_count=passed_count,  # noqa
        failed_count=failed_count,  # noqa
        unranked_count=unranked_count,  # noqa
        ranking_completed=(passed_count > 0 or failed_count > 0),  # noqa
        average_passed=average_passed,  # noqa
    )


def create_record(
    course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest,
) -> AssignmentStatisticsRecordType:
    if isinstance(course_session_assignment, CourseSessionAssignment):
        due_date = course_session_assignment.submission_deadline
    else:
        due_date = course_session_assignment.deadline

    learning_content = course_session_assignment.learning_content

    return AssignmentStatisticsRecordType(
        # make sure it's unique, across all types of assignments!
        _id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}",
        # noqa
        course_session_id=str(course_session_assignment.course_session.id),  # noqa
        circle_id=learning_content.get_circle().id,  # noqa
        course_session_assignment_id=str(course_session_assignment.id),  # noqa
        generation=course_session_assignment.course_session.generation,  # noqa
        assignment_type_translation_key=due_date.assignment_type_translation_key,
        # noqa
        assignment_title=learning_content.content_assignment.title,  # noqa
        metrics=get_assignment_completion_metrics(  # noqa
            course_session=course_session_assignment.course_session,  # noqa
            assignment=learning_content.content_assignment,  # noqa
        ),
        details_url=due_date.url_expert,  # noqa
        deadline=due_date.start,  # noqa
    )


def assignments(
    course_id: graphene.ID(required=True),
    course_session_selection_ids: graphene.List(graphene.ID),
) -> AssignmentsStatisticsType:
    course_sessions = CourseSession.objects.filter(
        id__in=course_session_selection_ids,
    )
    records: List[AssignmentStatisticsRecordType] = []

    for course_session in course_sessions:
        for csa in CourseSessionAssignment.objects.filter(
            course_session=course_session,
            learning_content__content_assignment__assignment_type__in=[
                AssignmentType.CASEWORK.value,
            ],
            learning_content__content_assignment__competence_certificate__isnull=False,
        ):
            record = create_record(course_session_assignment=csa)
            records.append(record)

        for cset in CourseSessionEdoniqTest.objects.filter(
            course_session=course_session,
            learning_content__content_assignment__competence_certificate__isnull=False,
        ):
            record = create_record(course_session_assignment=cset)
            records.append(record)

    return AssignmentsStatisticsType(
        _id=course_id,  # noqa
        records=sorted(records, key=lambda r: r.deadline),  # noqa
        summary=create_assignment_summary(  # noqa
            course_id=course_id, metrics=[r.metrics for r in records]  # noqa
        ),
    )
