from datetime import datetime

import graphene
from django.db.models import Q
from django.utils.dateformat import format
from django_filters import FilterSet, OrderingFilter
from graphene import ObjectType, relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from graphql_relay import to_global_id

from basicknowledge.models import BasicKnowledge
from basicknowledge.queries import InstrumentNode
from books.models import Module
from books.schema.queries import ModuleNode
from users.models import SchoolClass, SchoolClassMember, Team, User


class SchoolClassNode(DjangoObjectType):
    pk = graphene.Int()
    members = graphene.List('users.schema.ClassMemberNode')
    code = graphene.String()
    read_only = graphene.Boolean()

    class Meta:
        model = SchoolClass
        only_fields = ['name', 'code', 'members', 'pk', 'read_only']
        filter_fields = ['name']
        interfaces = (relay.Node,)

    def resolve_pk(self, *args, **kwargs):
        return self.id

    def resolve_members(self, info, **kwargs):
        return SchoolClassMember.objects.filter(school_class=self)

    def resolve_code(self: SchoolClass, info, **kwargs):
        if not info.context.user.is_teacher():
            return None
        if self.code is None:
            self.generate_code()
        return self.code

    @staticmethod
    def resolve_read_only(root: SchoolClass, info, **kwargs):
        user = info.context.user
        member = SchoolClassMember.objects.get(user=user, school_class=root)
        return not member.active


class TeamNode(DjangoObjectType):
    class Meta:
        model = Team
        filter_fields = ['name']
        interfaces = (relay.Node,)

    pk = graphene.Int()
    members = graphene.List('users.schema.PublicUserNode')

    def resolve_pk(self, *args, **kwargs):
        return self.id

    def resolve_members(self, *args, **kwargs):
        return self.members.all()


class RecentModuleFilter(FilterSet):
    class Meta:
        model = Module
        fields = ('recent_modules',)

    order_by = OrderingFilter(
        fields=(
            ('recent_modules__visited', 'visited'),
        )
    )


class PublicUserNode(DjangoObjectType):
    full_name = graphene.String(required=True)
    is_me = graphene.Boolean()

    class Meta:
        model = User
        only_fields = ['full_name', 'first_name', 'last_name', 'avatar_url']
        interfaces = (relay.Node,)

    @staticmethod
    def resolve_is_me(parent: User, info, **kwargs):
        return info.context.user.pk == parent.pk


class PrivateUserNode(DjangoObjectType):
    class Meta:
        model = User
        filter_fields = ['username', 'email']
        only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
                       'last_topic', 'avatar_url',
                       'selected_class', 'expiry_date', 'onboarding_visited', 'team', 'read_only']
        interfaces = (relay.Node,)

    pk = graphene.Int()
    permissions = graphene.List(graphene.String)
    selected_class = graphene.Field(SchoolClassNode)
    expiry_date = graphene.String()
    is_teacher = graphene.Boolean()
    old_classes = DjangoFilterConnectionField(SchoolClassNode)
    recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
    team = graphene.Field(TeamNode)
    read_only = graphene.Boolean()

    def resolve_pk(self, info, **kwargs):
        return self.id

    def resolve_permissions(self, info):
        return self.get_all_permissions()

    @staticmethod
    def resolve_selected_class(root: User, info):
        return root.selected_class

    @staticmethod
    def resolve_expiry_date(root: User, info):
        if not root.hep_id:  # concerns users that already have an (old) account
            return format(datetime(2020, 7, 31), 'U')  # just set some expiry date
        else:
            return root.license_expiry_date

    def resolve_is_teacher(root: User, info):
        return root.is_teacher()

    @staticmethod
    def resolve_school_classes(root: User, info):
        if root.selected_class is None:  # then we don't have any class to return
            return SchoolClass.objects.none()
        return SchoolClass.objects.filter(
            Q(schoolclassmember__active=True, schoolclassmember__user=root) | Q(pk=root.selected_class.pk)).distinct()

    def resolve_old_classes(self: User, info):
        return SchoolClass.objects.filter(schoolclassmember__active=False, schoolclassmember__user=self)

    def resolve_recent_modules(self, info, **kwargs):
        # see https://docs.graphene-python.org/projects/django/en/latest/filtering/
        return RecentModuleFilter(kwargs).qs.filter(recent_modules__user=self)

    def resolve_team(self, info, **kwargs):
        return self.team


class ClassMemberNode(ObjectType):
    """
    We need to build this ourselves, because we want the active property on the node, because providing it on the
    Connection or Edge for a UserNodeConnection is difficult.
    """
    user = graphene.Field('users.schema.PublicUserNode')
    active = graphene.Boolean()
    first_name = graphene.String()
    last_name = graphene.String()
    is_teacher = graphene.Boolean()
    id = graphene.ID()
    is_me = graphene.Boolean()

    def resolve_id(self, *args):
        return to_global_id('PublicUserNode', self.user.pk)

    def resolve_active(self, *args):
        return self.active

    def resolve_first_name(self, *args):
        return self.user.first_name

    def resolve_last_name(self, *args):
        return self.user.last_name

    def resolve_user(self, info, **kwargs):
        return self.user

    def resolve_is_teacher(self, *args):
        return self.user.is_teacher()

    @staticmethod
    def resolve_is_me(parent, info, **kwargs):
        return info.context.user.pk == parent.user.pk


class UsersQuery(object):
    me = graphene.Field(PrivateUserNode)
    all_users = DjangoFilterConnectionField(PrivateUserNode)
    my_activity = DjangoFilterConnectionField(ModuleNode)
    my_instrument_activity = DjangoFilterConnectionField(InstrumentNode)

    def resolve_me(self, info, **kwargs):
        return info.context.user

    def resolve_all_users(self, info, **kwargs):
        if not info.context.user.is_superuser:
            return User.objects.none()
        else:
            return User.objects.all()

    def resolve_my_activity(self, info, **kwargs):
        return Module.objects.all()

    def resolve_my_instrument_activity(self, info, **kwargs):
        return BasicKnowledge.objects.all()


class AllUsersQuery(object):
    me = graphene.Field(PrivateUserNode)
    all_users = DjangoFilterConnectionField(PrivateUserNode)

    def resolve_all_users(self, info, **kwargs):
        if not info.context.user.is_superuser:
            return User.objects.none()
        else:
            return User.objects.all()
