import json

import graphene
from django.core.exceptions import ValidationError
from graphene import relay
from graphql_relay import from_global_id

from api.utils import get_object, get_errors
from books.models import ContentBlock, Chapter, SchoolClass
from books.schema.inputs import ContentBlockInput
from ..nodes import ContentBlockNode
from core.utils import set_hidden_for, set_visible_for
from .utils import handle_content_block, set_user_defined_block_type


class MutateContentBlock(relay.ClientIDMutation):
    class Input:
        id = graphene.ID(required=True)
        content_block = graphene.Argument(ContentBlockInput)

    content_block = graphene.Field(ContentBlockNode)

    @classmethod
    def mutate_and_get_payload(cls, root, info, **kwargs):
        try:
            id_param = kwargs['id']
            content_block_data = kwargs.get('content_block')
            # type_param = content_block_data.get('type')
            title = content_block_data.get('title', None)
            contents = content_block_data.get('contents', None)
            visibility_list = content_block_data.get('visibility', None)

            content_block = get_object(ContentBlock, id_param)

            block_type = content_block_data.get('type')
            if block_type is not None:  # only change the type if the user has set a type
                content_block.type = set_user_defined_block_type(block_type)

            module = content_block.get_parent().get_parent().specific

            if visibility_list is not None:
                if content_block.user_created:
                    set_visible_for(content_block, visibility_list)
                else:
                    set_hidden_for(content_block, visibility_list)

            if title is not None:
                content_block.title = title

            if contents is not None:
                content_block.contents = json.dumps([handle_content_block(c, info.context, module) for c in contents])

            content_block.save()

            return cls(content_block=content_block)

        except ValidationError as e:
            errors = get_errors(e)
            raise errors
        except Exception as e:
            errors = ['Error: {}'.format(e)]
            raise errors


class AddContentBlock(relay.ClientIDMutation):
    class Input:
        content_block = graphene.Argument(ContentBlockInput)
        parent = graphene.ID()  # ID of chapter node; new content block will be inserted at the start of it
        after = graphene.ID()  # ID of content block node; new content block will be inserted after this content block node

    new_content_block = graphene.Field(ContentBlockNode)
    errors = graphene.List(graphene.String)

    @classmethod
    def create_content_block(cls, content_block_data, parent=None, after=None, context=None):
        if after is None and parent is None:
            raise Exception('Define either a parent or a sibling id')

        title = content_block_data.get('title')
        contents = content_block_data.get('contents')
        block_type = set_user_defined_block_type(content_block_data.get('type', ContentBlock.NORMAL))

        new_content_block = ContentBlock(title=title, user_created=True, owner=context.user, type=block_type)

        if parent is not None:
            parent_chapter = get_object(Chapter, parent).specific
            first_sibling = parent_chapter.get_first_child()
            if first_sibling is not None:
                first_sibling.add_sibling(instance=new_content_block, pos='left')
            else:
                parent_chapter.add_child(instance=new_content_block)
        elif after is not None:
            sibling = get_object(ContentBlock, after).specific
            sibling.add_sibling(instance=new_content_block, pos='right')

        revision = new_content_block.save_revision()
        revision.publish()
        new_content_block.save()

        module = new_content_block.get_parent().get_parent().specific

        new_content_block.contents = json.dumps([handle_content_block(c, context, module) for c in
                                                 contents])  # can only do this after the content block has been saved
        new_content_block.save()

        return new_content_block

    @classmethod
    def mutate_and_get_payload(cls, root, info, **args):
        try:
            parent = args.get('parent', None)
            after = args.get('after', None)
            new_content_block = cls.create_content_block(content_block_data=args.get('content_block'),
                                                         parent=parent,
                                                         after=after,
                                                         context=info.context)

            return cls(new_content_block=new_content_block)

        except ValidationError as e:
            errors = get_errors(e)
        except Exception as e:
            errors = ['Error: {}'.format(e)]

        return cls(new_content_block=None, errors=errors)


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

    success = graphene.Boolean()
    errors = graphene.String()

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

        try:
            content_block = ContentBlock.objects.get(pk=id, owner=user)
            content_block.delete()
            return cls(success=True)
        except ContentBlock.DoesNotExist:
            return cls(success=False, errors='Content block not found')
