import json
import jsonschema

from django.db.models import Count
from django.http import Http404, HttpResponse
from django.core.paginator import EmptyPage

from polls.models import Question, Choice, Vote
from polls.resource import Action, Attribute, Resource, CollectionResource, SingleObjectMixin
from polls.features import can_create_question, can_delete_question, can_vote_choice


class RootResource(Resource):
    uri = '/'
    cache_max_age = 3600

    def get_relations(self):
        return {
            'questions': QuestionCollectionResource(),
        }

    def can_embed(self, relation):
        return False


class QuestionResource(Resource, SingleObjectMixin):
    model = Question

    def get_uri(self):
        return '/questions/{}'.format(self.get_object().pk)

    def get_attributes(self):
        question = self.get_object()

        return {
            'question': question.question_text,
            'published_at': question.published_at.isoformat(),
        }

    def get_relations(self):
        choices = self.get_object().choices.annotate(vote_count=Count('votes')).order_by('-vote_count', 'choice_text')

        def choice_resource(choice):
            resource = ChoiceResource()
            resource.obj = choice
            resource.request = getattr(self, 'request', None)
            return resource

        return {
            'choices': list(map(choice_resource, choices)),
        }

    def get_actions(self):
        actions = {}

        if can_delete_question(self.get_object(), self.request):
            actions['delete'] = Action(method='DELETE', attributes=None)

        return actions

    def get(self, *args, **kwargs):
        try:
            self.get_object()
        except self.model.DoesNotExist:
            raise Http404()

        return super(QuestionResource, self).get(*args, **kwargs)

    def delete(self, request, *args, **kwargs):
        if not can_delete_question(self.get_object(), request):
            return self.http_method_not_allowed(request)

        self.get_object().delete()
        return HttpResponse(status=204)


class ChoiceResource(Resource, SingleObjectMixin):
    model = Choice

    def get_uri(self):
        choice = self.get_object()
        return '/questions/{}/choices/{}'.format(choice.question.pk, choice.pk)

    def get_attributes(self):
        choice = self.get_object()

        if not hasattr(choice, 'vote_count'):
            choice.vote_count = choice.votes.count()

        return {
            'choice': choice.choice_text,
            'votes': choice.vote_count,
        }

    def get_actions(self):
        actions = {}

        if can_vote_choice(self.request):
            actions['vote'] = Action(method='POST', attributes=None)

        return actions

    def get(self, *args, **kwargs):
        try:
            self.get_object()
        except self.model.DoesNotExist:
            raise Http404()

        return super(ChoiceResource, self).get(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        if not can_vote_choice(self.request):
            return self.http_method_not_allowed(request)

        try:
            choice = self.get_object()
        except self.model.DoesNotExist:
            raise Http404('Choice does not exist')

        choice.vote()
        response = self.get(request)
        response.status_code = 201
        return response


class QuestionCollectionResource(CollectionResource):
    resource = QuestionResource
    model = Question
    relation = 'questions'
    uri = '/questions'

    request_body_schema = {
        'type': 'object',
        'properties': {
            'question': {
                'type': 'string'
            },
            'choices': {
                'type': 'array',
                'items': {
                    'type': 'string'
                },
                'minItems': 2
            }
        },
        'required': ['question', 'choices']
    }

    def get_actions(self):
        actions = {}

        if can_create_question(self.request):
            actions['create'] = Action(method='POST', attributes=(
                Attribute(name='question', category='text'),
                Attribute(name='choices', category='array[text]'),
            ))

        return actions

    def post(self, request):
        if not can_create_question(self.request):
            return self.http_method_not_allowed(request)

        try:
            body = json.loads(request.body)
        except ValueError:
            return HttpResponse(status=400)

        try:
            jsonschema.validate(body, self.request_body_schema)
        except jsonschema.ValidationError:
            return HttpResponse(status=400)

        question_text = body.get('question')
        choices = body.get('choices')

        question, created = self.get_or_create(question_text, choices)
        resource = self.resource()
        resource.obj = question
        resource.request = request
        response = resource.get(request)
        if created:
            response.status_code = 201
        response['Location'] = resource.get_uri()
        return response

    def create_question(self, question_text, choice_texts):
        question = Question(question_text=question_text)
        question.save()

        for choice_text in choice_texts:
            Choice(question=question, choice_text=choice_text).save()

        return question

    def get_or_create(self, question_text, choice_texts):
        try:
            question = Question.objects.filter(question_text=question_text).first()
        except Question.DoesNotExist:
            question = None

        if question:
            choices = list(map(lambda c: c.choice_text, question.choices.order_by('choice_text')))
            if choices == sorted(choice_texts):
                return (question, False)

        return (self.create_question(question_text, choice_texts), True)