import json
from functools import reduce
from operator import and_, or_

from django.db.models import Q
from django.utils import timezone

from question.models import Question
from question_bank.models import QuestionBank
from . import const
from .universal_question_content import UniversalQuestionContent


class BlueprintRetriever:
    def __init__(self, insert_type, duration, negative_score, positive_score, question_bank_id, count, tag_filters, contents, retrieved_at,
                 question_id):
        self.insert_type = insert_type
        self.duration = duration
        self.negative_score = negative_score
        self.positive_score = positive_score
        self.question_bank_id = question_bank_id
        self.question_id = question_id
        self.count = count
        self.tag_filters = tag_filters
        self.contents = contents
        self.retrieved_at = retrieved_at

    @property
    def content(self):
        if self.contents:
            return self.contents[0]
        else:
            return None

    @content.setter
    def content(self, input):
        self.contents = [input]

    @classmethod
    def from_rule_retriever(cls, input):
        retrieved_at = timezone.now()
        contents = []

        if input.insert_type == const.QUIZ_RULE_EXACT_QUESTION:
            contents = [UniversalQuestionContent.from_dict(
                input.exact_question_data)]
            count = 1
        elif input.insert_type == const.QUIZ_RULE_REFERENCED_QUESTION:

            question = Question.objects.get(id=input.question_id)

            contents = [
                UniversalQuestionContent.from_question_obj(question)]

            count = 1
        elif input.insert_type == const.QUIZ_RULE_QUERY_QUESTION:
            question_bank = QuestionBank.objects.get(
                id=input.question_bank_id)
            count = input.count
            questions_queryset = Question.objects.filter(
                question_bank=question_bank, archived=False).order_by('?')

            if input.tag_filters:
                query = reduce(and_, (reduce(or_, (Q(tags__contains=item)
                                                   for item in [{child["key"]: child["value"]} for child in group]))
                                      for group in input.tag_filters if group))
                questions_queryset = questions_queryset.filter(query)

            contents = [UniversalQuestionContent.from_question_obj(
                question) for question in questions_queryset[0:count]]

        return cls(
            insert_type=input.insert_type,
            duration=input.duration,
            negative_score=input.negative_score,
            positive_score=input.positive_score,
            question_bank_id=input.question_bank_id,
            count=input.count,
            question_id=input.question_id,
            tag_filters=input.tag_filters,
            contents=contents,
            retrieved_at=retrieved_at,
        )

    @classmethod
    def from_dict(cls, input):
        return cls(
            insert_type=input['insert_type'],
            duration=input['duration'],
            negative_score=input['negative_score'],
            positive_score=input['positive_score'],
            count=input['count'],
            contents=[UniversalQuestionContent.from_dict(
                c) for c in input['contents']],
            retrieved_at=input['retrieved_at'],
            question_bank_id=input.get('question_bank_id', None),
            question_id=input.get('question_id', None),
            tag_filters=input.get('tag_filters', None),
        )

    def to_dict(self):
        return {
            "insert_type": self.insert_type,
            "duration": self.duration,
            "negative_score": self.negative_score,
            "positive_score": self.positive_score,
            "contents": [c.to_dict() for c in self.contents],
            "retrieved_at": str(self.retrieved_at),
            "count": self.count,
        }

    def to_json(self, indent=0):
        return json.dumps(self.to_dict(), indent=indent)

    def questions_count(self):
        # There is one question if we do not have a question of type EXAM_RULE_QUERY_QUESTION
        return self.count or 1


class Blueprint:
    def __init__(self, retrievers, groups):
        self.retrievers = retrievers
        self.groups = groups

    def to_dict(self):
        return {
            "retrievers": [retriever.to_dict() for retriever in self.retrievers],
            "groups": [group.to_dict() for group in self.groups],
        }

    @property
    def contents(self):
        results = []
        for r in self.retrievers:
            results += r.contents
        return results

    @classmethod
    def from_dict(cls, input):
        retrievers = [BlueprintRetriever.from_dict(retriever) for retriever in input['retrievers']]
        groups = [BlueprintGroup.from_dict(group) for group in input['groups']]

        return cls(
            retrievers, groups
        )

    @classmethod
    def from_rule(cls, rule):
        retrievers = [BlueprintRetriever.from_rule_retriever(item) for item in rule.retrievers]
        groups = [BlueprintGroup.from_rule_group(item) for item in rule.groups]

        return cls(
            retrievers, groups
        )

    def questions_count(self):
        return sum([retriever.questions_count() for retriever in self.retrievers])


class BlueprintGroup:
    def __init__(self, name, start_at):
        self.name = name
        self.start_at = start_at

    @classmethod
    def from_rule_group(cls, input):
        return cls(
            name=input.name,
            start_at=input.start_at,
        )

    @classmethod
    def from_dict(cls, input):
        return cls(
            name=input['name'],
            start_at=input['start_at'],
        )

    def to_dict(self):
        return {
            "name": self.name,
            "start_at": self.start_at
        }
