from typing import List, Type

import graphene
import structlog
from graphene import ObjectType
from graphene_django import DjangoObjectType
from graphql import GraphQLError
from rest_framework.exceptions import PermissionDenied

from vbv_lernwelt.competence.graphql.types import ActionCompetenceObjectType
from vbv_lernwelt.course.models import (
    CircleDocument,
    Course,
    CourseBasePage,
    CoursePage,
    CourseSession,
    CourseSessionUser,
)
from vbv_lernwelt.course_session.graphql.types import (
    CourseSessionAssignmentObjectType,
    CourseSessionAttendanceCourseObjectType,
    CourseSessionEdoniqTestObjectType,
)
from vbv_lernwelt.course_session.models import (
    CourseSessionAssignment,
    CourseSessionAttendanceCourse,
    CourseSessionEdoniqTest,
)
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.iam.permissions import has_course_access
from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType
from vbv_lernwelt.learnpath.models import Circle

logger = structlog.get_logger(__name__)


def resolve_course_page(
    page_model_class: Type[CourseBasePage],
    root,
    info,
    id=None,
    slug=None,
    course_id=None,
    course_slug=None,
):
    try:
        if id is None and slug is None and course_id is None and course_slug is None:
            raise GraphQLError(
                "Either 'id', 'slug', 'course_id' or 'course_slug' must be provided."
            )

        if id or slug:
            # try to fetch page directly
            page = None
            if id is not None:
                page = page_model_class.objects.get(pk=id)
            elif slug is not None:
                page = page_model_class.objects.get(slug=slug)

            if page and not has_course_access(
                info.context.user, page.specific.get_course().id
            ):
                raise PermissionDenied("You do not have access to this course.")
            return page

        if course_id or course_slug:
            # fetch first page of type `page_model_class` via course
            # makes sense for "Index" pages like "lernpfad" or "kompetenznachweise" etc
            course_page = None
            if course_id is not None:
                course_page = CoursePage.objects.get(pk=course_id)
            elif course_slug is not None:
                course_page = CoursePage.objects.get(slug=course_slug)

            page = course_page.get_descendants().type(page_model_class).first()
            if page and not has_course_access(
                info.context.user, page.specific.get_course().id
            ):
                raise PermissionDenied("You do not have access to this course.")
            return page.specific

    except PermissionDenied as e:
        raise e
    except Exception as e:
        logger.exception(e)
        raise e


class CourseObjectType(DjangoObjectType):
    learning_path = graphene.Field(LearningPathObjectType, required=True)
    action_competences = graphene.List(
        graphene.NonNull(ActionCompetenceObjectType), required=True
    )

    class Meta:
        model = Course
        fields = ("id", "title", "category_name", "slug", "enable_circle_documents")

    @staticmethod
    def resolve_learning_path(root: Course, info):
        return root.get_learning_path()

    @staticmethod
    def resolve_action_competences(root: Course, info):
        return root.get_action_competences()


class CourseSessionUserExpertCircleType(ObjectType):
    id = graphene.ID(required=True)
    title = graphene.String(required=True)
    slug = graphene.String(required=True)


class CourseSessionUserObjectsType(ObjectType):
    """
    WORKAROUND:
    Why is this no DjangoObjectType? It's because we have to "inject"
    the supervisor into the list of users. This is done in the resolve_users
    of the CourseSessionObjectType. And there we have to be able to construct
    a CourseSessionUserObjectsType with the CIRCLES of the supervisor!
    """

    id = graphene.ID(required=True)
    user_id = graphene.UUID(required=True)
    first_name = graphene.String(required=True)
    last_name = graphene.String(required=True)
    email = graphene.String(required=True)
    avatar_url = graphene.String(required=True)
    role = graphene.String(required=True)
    circles = graphene.List(
        graphene.NonNull(CourseSessionUserExpertCircleType), required=True
    )


class CircleDocumentObjectType(DjangoObjectType):
    file_name = graphene.String()
    url = graphene.String()

    class Meta:
        model = CircleDocument
        fields = [
            "id",
            "name",
            "file_name",
            "url",
            "course_session",
            "learning_sequence",
        ]

    def resolve_file_name(self, info):
        return self.file_name

    def resolve_url(self, info):
        return self.url


class CourseSessionObjectType(DjangoObjectType):
    attendance_courses = graphene.List(
        graphene.NonNull(CourseSessionAttendanceCourseObjectType), required=True
    )
    assignments = graphene.List(
        graphene.NonNull(CourseSessionAssignmentObjectType), required=True
    )
    edoniq_tests = graphene.List(
        graphene.NonNull(CourseSessionEdoniqTestObjectType), required=True
    )
    users = graphene.List(graphene.NonNull(CourseSessionUserObjectsType), required=True)

    class Meta:
        model = CourseSession
        fields = (
            "id",
            "created_at",
            "updated_at",
            "course",
            "title",
            "start_date",
            "end_date",
            "users",
        )

    def resolve_attendance_courses(self, info):
        return CourseSessionAttendanceCourse.objects.filter(course_session=self)

    def resolve_assignments(self, info):
        return CourseSessionAssignment.objects.filter(course_session=self)

    def resolve_edoniq_tests(self, info):
        return CourseSessionEdoniqTest.objects.filter(course_session=self)

    def resolve_users(self, info):
        course_session_users_resolved: List[CourseSessionUserObjectsType] = []

        # happy path, members and experts
        for course_session_user in CourseSessionUser.objects.filter(
            course_session_id=self.id
        ).distinct():
            course_session_users_resolved.append(
                CourseSessionUserObjectsType(
                    id=course_session_user.id,  # noqa
                    user_id=course_session_user.user.id,  # noqa
                    first_name=course_session_user.user.first_name,  # noqa
                    last_name=course_session_user.user.last_name,  # noqa
                    email=course_session_user.user.email,  # noqa
                    avatar_url=course_session_user.user.avatar_url,  # noqa
                    role=course_session_user.role,  # noqa
                    circles=[  # noqa
                        CourseSessionUserExpertCircleType(  # noqa
                            id=circle.id,  # noqa
                            title=circle.title,  # noqa
                            slug=circle.slug,  # noqa
                        )
                        for circle in course_session_user.expert.all()  # noqa
                    ],
                )
            )

        # workaround for supervisor
        # add supervisor to the list of users (as expert)
        course_session_id = self.id  # noqa
        user = info.context.user  # noqa

        if CourseSessionGroup.objects.filter(
            course_session=course_session_id, supervisor=user
        ).exists():
            if course_session := CourseSession.objects.filter(
                id=course_session_id
            ).first():
                circles = (
                    course_session.course.get_learning_path()
                    .get_descendants()
                    .live()
                    .specific()
                    .exact_type(Circle)
                )

                course_session_users_resolved.append(
                    CourseSessionUserObjectsType(
                        id=f"{user.id}-as-ephemeral-supervisor",  # noqa
                        user_id=user.id,  # noqa
                        first_name=user.first_name,  # noqa
                        last_name=user.last_name,  # noqa
                        email=user.email,  # noqa
                        avatar_url=user.avatar_url,  # noqa
                        role=CourseSessionUser.Role.EXPERT,  # noqa
                        circles=[  # noqa
                            CourseSessionUserExpertCircleType(  # noqa
                                id=circle.id,  # noqa
                                title=circle.title,  # noqa
                                slug=circle.slug,  # noqa
                            )
                            for circle in circles
                        ],
                    )
                )

        return course_session_users_resolved
