import graphene
import structlog
from graphene_django import DjangoObjectType

from vbv_lernwelt.core.utils import find_first_index
from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface
from vbv_lernwelt.learnpath.models import (
    Circle,
    LearningContentAssignment,
    LearningContentAttendanceCourse,
    LearningContentDocumentList,
    LearningContentEdoniqTest,
    LearningContentFeedbackUK,
    LearningContentFeedbackVV,
    LearningContentKnowledgeAssessment,
    LearningContentLearningModule,
    LearningContentMediaLibrary,
    LearningContentPlaceholder,
    LearningContentRichText,
    LearningContentVideo,
    LearningPath,
    LearningSequence,
    LearningUnit,
    Topic,
)

logger = structlog.get_logger(__name__)


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


class LearningContentInterface(CoursePageInterface):
    minutes = graphene.Int()
    description = graphene.String(required=True)
    content_url = graphene.String(required=True)
    can_user_self_toggle_course_completion = graphene.Boolean(required=True)
    circle = graphene.Field(CircleLightObjectType)

    @staticmethod
    def resolve_circle(root, info):
        return root.get_parent_circle()

    @classmethod
    def resolve_type(cls, instance, info):
        if isinstance(instance, LearningContentAssignment):
            return LearningContentAssignmentObjectType
        elif isinstance(instance, LearningContentAttendanceCourse):
            return LearningContentAttendanceCourseObjectType
        elif isinstance(instance, LearningContentFeedbackUK):
            return LearningContentFeedbackUKObjectType
        elif isinstance(instance, LearningContentFeedbackVV):
            return LearningContentFeedbackVVObjectType
        elif isinstance(instance, LearningContentLearningModule):
            return LearningContentLearningModuleObjectType
        elif isinstance(instance, LearningContentKnowledgeAssessment):
            return LearningContentKnowledgeAssessmentObjectType
        elif isinstance(instance, LearningContentMediaLibrary):
            return LearningContentMediaLibraryObjectType
        elif isinstance(instance, LearningContentPlaceholder):
            return LearningContentPlaceholderObjectType
        elif isinstance(instance, LearningContentRichText):
            return LearningContentRichTextObjectType
        elif isinstance(instance, LearningContentEdoniqTest):
            return LearningContentEdoniqTestObjectType
        elif isinstance(instance, LearningContentVideo):
            return LearningContentVideoObjectType
        elif isinstance(instance, LearningContentDocumentList):
            return LearningContentDocumentListObjectType
        else:
            logger.error(
                "Could not resolve type for LearningContentInterface",
                django_instance=instance,
            )
            return None


class LearningContentAttendanceCourseObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentAttendanceCourse
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningContentVideoObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentVideo
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningContentPlaceholderObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentPlaceholder
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningContentFeedbackUKObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentFeedbackUK
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningContentFeedbackVVObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentFeedbackVV
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningContentLearningModuleObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentLearningModule
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningContentKnowledgeAssessmentObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentKnowledgeAssessment
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningContentMediaLibraryObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentMediaLibrary
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningContentEdoniqTestObjectType(DjangoObjectType):
    competence_certificate = graphene.Field(
        "vbv_lernwelt.competence.graphql.types.CompetenceCertificateObjectType",
    )
    has_extended_time_test = graphene.Boolean(required=True)

    class Meta:
        model = LearningContentEdoniqTest
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = ["content_assignment", "checkbox_text", "has_extended_time_test"]

    def resolve_competence_certificate(self: LearningContentEdoniqTest, info):
        return self.content_assignment.competence_certificate

    def resolve_has_extended_time_test(self: LearningContentEdoniqTest, info):
        return self.has_extended_time_test


class LearningContentRichTextObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentRichText
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = ["text"]


class LearningContentAssignmentObjectType(DjangoObjectType):
    competence_certificate = graphene.Field(
        "vbv_lernwelt.competence.graphql.types.CompetenceCertificateObjectType",
    )

    class Meta:
        model = LearningContentAssignment
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = [
            "content_assignment",
            "assignment_type",
        ]

    def resolve_competence_certificate(self: LearningContentAssignment, info):
        return self.content_assignment.competence_certificate


class LearningContentDocumentListObjectType(DjangoObjectType):
    class Meta:
        model = LearningContentDocumentList
        interfaces = (
            CoursePageInterface,
            LearningContentInterface,
        )
        fields = []


class LearningUnitObjectType(DjangoObjectType):
    learning_contents = graphene.List(
        graphene.NonNull(LearningContentInterface), required=True
    )
    performance_criteria = graphene.List(
        graphene.NonNull(
            "vbv_lernwelt.competence.graphql.types.PerformanceCriteriaObjectType"
        ),
        required=True,
    )
    evaluate_url = graphene.String(required=True)

    class Meta:
        model = LearningUnit
        interfaces = (CoursePageInterface,)
        fields = ["evaluate_url", "title_hidden"]

    def resolve_evaluate_url(self: LearningUnit, info, **kwargs):
        return self.get_evaluate_url()

    def resolve_performance_criteria(self: LearningUnit, info, **kwargs):
        return self.performancecriteria_set.all()

    def resolve_learning_contents(self: LearningUnit, info, **kwargs):
        siblings = None
        if hasattr(info.context, "circle_descendants"):
            circle_descendants = info.context.circle_descendants
            index = circle_descendants.index(self)
            siblings = circle_descendants[index + 1 :]

        if not siblings:
            siblings = self.get_siblings().live().specific()

        learning_contents = []
        for sibling in siblings:
            if (
                sibling.specific_class == LearningUnit
                or sibling.specific_class == LearningSequence
            ):
                break
            else:
                learning_contents.append(sibling.specific)
        return learning_contents


class LearningSequenceObjectType(DjangoObjectType):
    learning_units = graphene.List(
        graphene.NonNull(LearningUnitObjectType), required=True
    )

    class Meta:
        model = LearningSequence
        interfaces = (CoursePageInterface,)
        fields = ["icon"]

    @staticmethod
    def resolve_learning_units(root: LearningSequence, info, **kwargs):
        siblings = None
        if hasattr(info.context, "circle_descendants"):
            circle_descendants = info.context.circle_descendants
            index = circle_descendants.index(root)
            siblings = circle_descendants[index + 1 :]

        if not siblings:
            siblings = root.get_siblings().live().specific()

        learning_units = []
        for sibling in siblings:
            if sibling.specific_class == LearningSequence:
                # finished with this sequence
                break
            if sibling.specific_class == LearningUnit:
                learning_units.append(sibling.specific)
        return learning_units


class CircleObjectType(DjangoObjectType):
    learning_sequences = graphene.List(
        graphene.NonNull(LearningSequenceObjectType), required=True
    )

    class Meta:
        model = Circle
        interfaces = (CoursePageInterface,)
        fields = [
            "description",
            "goals",
        ]

    def resolve_learning_sequences(self: Circle, info, **kwargs):
        circle_descendants = None

        if hasattr(info.context, "learning_path_descendants"):
            children = info.context.learning_path_descendants
            circle_start_index = children.index(self)
            next_children = children[circle_start_index + 1 :]
            next_circle_index = find_first_index(
                next_children,
                pred=lambda child: child.specific_class == Circle,
            )
            circle_descendants = next_children[0:next_circle_index]
            if circle_descendants[-1].specific_class == Topic:
                circle_descendants = circle_descendants[:-1]

        if not circle_descendants:
            circle_descendants = list(self.get_descendants().live().specific())

        # store flattened descendents to improve performance (no need for db queries)
        info.context.circle_descendants = list(circle_descendants)

        return [
            descendant.specific
            for descendant in circle_descendants
            if descendant.specific_class == LearningSequence
        ]


class TopicObjectType(DjangoObjectType):
    circles = graphene.List(graphene.NonNull(CircleObjectType), required=True)

    class Meta:
        model = Topic
        interfaces = (CoursePageInterface,)
        fields = [
            "is_visible",
        ]

    @staticmethod
    def resolve_circles(root: LearningPath, info, **kwargs):
        siblings = None

        if hasattr(info.context, "learning_path_descendants"):
            learning_path_descendants = info.context.learning_path_descendants
            index = learning_path_descendants.index(root)
            siblings = learning_path_descendants[index + 1 :]

        if not siblings:
            siblings = root.get_next_siblings().live().specific()

        circles = []
        for sibling in siblings:
            if sibling.specific_class == Topic:
                # finished with this topic
                break
            if sibling.specific_class == Circle:
                circles.append(sibling.specific)
        return circles


class LearningPathObjectType(DjangoObjectType):
    topics = graphene.List(graphene.NonNull(TopicObjectType), required=True)

    class Meta:
        model = LearningPath
        interfaces = (CoursePageInterface,)
        fields = []

    @staticmethod
    def resolve_topics(root: LearningPath, info, **kwargs):
        learning_path_descendants_qs = root.get_descendants().live().specific()

        # store flattened descendents to improve performance (no need for db queries)
        info.context.learning_path_descendants = list(learning_path_descendants_qs)
        return [
            descendant.specific
            for descendant in learning_path_descendants_qs
            if descendant.specific_class == Topic
        ]
