import graphene
from django.contrib.auth import update_session_auth_hash
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from graphene import relay
from graphql_relay import from_global_id

from api.utils import get_object
from core.logger import get_logger
from users.inputs import PasswordUpdateInput
from users.models import SchoolClass, SchoolClassMember, Team
from users.schema import SchoolClassNode, TeamNode
from users.serializers import PasswordSerialzer, AvatarUrlSerializer

logger = get_logger(__name__)


class CodeNotFoundException(Exception):
    pass


class FieldError(graphene.ObjectType):
    code = graphene.String()


class UpdateError(graphene.ObjectType):
    field = graphene.String()
    errors = graphene.List(FieldError)


class TeacherOnlyMutation(relay.ClientIDMutation):
    class Meta:
        abstract = True

    @classmethod
    def mutate(cls, root, info, input):
        user = info.context.user
        if 'users.can_manage_school_class_content' not in user.get_role_permissions():
            raise PermissionError('Permission denied')
        return super().mutate(root, info, input)


class UpdatePassword(relay.ClientIDMutation):
    class Input:
        password_input = graphene.Argument(PasswordUpdateInput)

    success = graphene.Boolean()
    errors = graphene.List(UpdateError)  # todo: change for consistency

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        user = info.context.user
        password_data = kwargs.get('password_input')

        serializer = PasswordSerialzer(data=password_data, context=user)

        if serializer.is_valid():
            user.set_password(password_data['new_password'])
            user.save()
            update_session_auth_hash(info.context, user)
            return cls(success=True)

        errors = []
        for key, value in serializer.errors.items():
            error = UpdateError(field=key, errors=[])
            for field_error in serializer.errors[key]:
                error.errors.append(FieldError(code=field_error.code))

            errors.append(error)

        return cls(success=False, errors=errors)


class UpdateAvatar(relay.ClientIDMutation):
    class Input:
        avatar_url = graphene.String()

    success = graphene.Boolean()
    errors = graphene.List(UpdateError)  # todo: change for consistency

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        user = info.context.user
        avatar_data = kwargs.get('avatar_url')

        serializer = AvatarUrlSerializer(data={'avatar_url': avatar_data})
        if serializer.is_valid():
            user.avatar_url = avatar_data
            user.save()
            return cls(success=True)

        errors = []
        for key, value in serializer.errors.items():

            error = UpdateError(field=key, errors=[])

            for field_error in serializer.errors[key]:
                error.errors.append(FieldError(code=field_error.code))

            errors.append(error)

        return cls(success=False, errors=errors)


class UpdateSetting(relay.ClientIDMutation):
    class Input:
        id = graphene.ID(required=True)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        class_id = kwargs.get('id')
        school_class = get_object(SchoolClass, class_id)
        user = info.context.user

        if school_class and school_class not in user.school_classes.all():
            raise PermissionDenied('Permission denied: Incorrect school class')

        user.set_selected_class(school_class)
        return cls(success=True)

    success = graphene.Boolean()
    errors = graphene.List(UpdateError)


class JoinClass(relay.ClientIDMutation):
    class Input:
        code = graphene.String(required=True)

    success = graphene.Boolean()
    school_class = graphene.Field(SchoolClassNode)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        user = info.context.user
        code = kwargs.get('code')
        try:
            school_class = SchoolClass.objects.get(Q(code__iexact=code))

            if user not in list(school_class.users.all()):
                SchoolClassMember.objects.create(
                    user=user,
                    school_class=school_class
                )
                user.set_selected_class(school_class)
            else:
                raise CodeNotFoundException('[CAJ] Schüler ist bereits in Klasse')  # CAJ = Class Already Joined

            return cls(success=True, school_class=school_class)
        except SchoolClass.DoesNotExist:
            raise CodeNotFoundException('[CNV] Code ist nicht gültig')  # CNV = Code Not Valid


class AddRemoveMember(relay.ClientIDMutation):
    class Input:
        member = graphene.ID(required=True)
        school_class = graphene.ID(required=True)
        active = graphene.Boolean(required=True)

    success = graphene.Boolean()

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        member_id = kwargs.get('member')
        school_class_id = kwargs.get('school_class')
        active = kwargs.get('active')
        user = info.context.user

        member_pk = from_global_id(member_id)[1]
        school_class = get_object(SchoolClass, school_class_id)

        if not user.is_teacher() or not school_class.users.filter(pk=user.pk).exists():
            raise PermissionError('Permission denied')

        school_class_member = SchoolClassMember.objects.get(user__pk=member_pk, school_class=school_class)
        school_class_member.active = active
        school_class_member.save()

        return cls(success=True)


class UpdateSchoolClass(TeacherOnlyMutation):
    class Input:
        id = graphene.ID(required=True)
        name = graphene.String()

    success = graphene.Boolean()
    school_class = graphene.Field(SchoolClassNode)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        id = kwargs.get('id')
        name = kwargs.get('name')
        user = info.context.user

        # todo: only allow to edit your own school class
        school_class = get_object(SchoolClass, id)
        school_class.name = name
        school_class.save()

        return cls(success=True, school_class=school_class)


class CreateSchoolClass(TeacherOnlyMutation):
    class Input:
        name = graphene.String()

    success = graphene.Boolean()
    school_class = graphene.Field(SchoolClassNode)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        name = kwargs.get('name')
        user = info.context.user

        school_class = SchoolClass.objects.create(name=name)
        SchoolClassMember.objects.create(school_class=school_class, user=user)
        user.set_selected_class(school_class)
        return cls(success=True, school_class=school_class)


class CreateTeam(TeacherOnlyMutation):
    class Input:
        name = graphene.String(required=True)

    success = graphene.Boolean()
    team = graphene.Field(TeamNode)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        name = kwargs.get('name')
        user = info.context.user

        team = Team.objects.create(name=name, creator=user)
        team.generate_code()
        user.team = team
        user.save()
        return cls(success=True, team=team)


class UpdateTeam(TeacherOnlyMutation):
    class Input:
        id = graphene.ID(required=True)
        name = graphene.String()

    success = graphene.Boolean()
    team = graphene.Field(TeamNode)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        id = kwargs.get('id')
        name = kwargs.get('name')
        user = info.context.user

        team = get_object(Team, id)
        if user not in team.members.all():
            logger.info('User not part of this team')
            raise PermissionError('Permission denied')
        team.name = name
        team.save()

        return cls(success=True, team=team)


class JoinTeam(TeacherOnlyMutation):
    class Input:
        code = graphene.String(required=True)

    success = graphene.Boolean()
    team = graphene.Field(TeamNode)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        user = info.context.user
        code = kwargs.get('code')
        try:
            team = Team.objects.get(Q(code__iexact=code))
            user.team = team
            user.save()

            return cls(success=True, team=team)
        except Team.DoesNotExist:
            raise CodeNotFoundException('[CNV] Code ist nicht gültig')  # CNV = Code Not Valid


class LeaveTeam(graphene.Mutation):
    success = graphene.Boolean()

    @classmethod
    def mutate(cls, root, info, **kwargs):
        user = info.context.user
        user.team = None
        user.save()
        return cls(success=True)


class UpdateOnboardingProgress(graphene.Mutation):
    success = graphene.Boolean()

    @classmethod
    def mutate(cls, root, info, **kwargs):
        user = info.context.user
        if not user.onboarding_visited:
            user.onboarding_visited = True
            user.save()

        return cls(success=True)


class ProfileMutations:
    update_password = UpdatePassword.Field()
    update_avatar = UpdateAvatar.Field()
    update_setting = UpdateSetting.Field()
    join_class = JoinClass.Field()
    add_remove_member = AddRemoveMember.Field()
    update_school_class = UpdateSchoolClass.Field()
    create_school_class = CreateSchoolClass.Field()
    update_onboarding_progress = UpdateOnboardingProgress.Field()
    create_team = CreateTeam.Field()
    join_team = JoinTeam.Field()
    update_team = UpdateTeam.Field()
    leave_team = LeaveTeam.Field()
