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

from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.fields import HStoreField
from django.db import models
from django.db.models import Q
from model_utils import FieldTracker

from question_bank.models import QuestionBank

QUESTION_TYPE_DESCRIPTIVE = 'descriptive'
QUESTION_TYPE_MULTIPLE_CHOICE = 'multiple-choice'
QUESTION_TYPE_DATABASE_CHOICES = [
    (QUESTION_TYPE_DESCRIPTIVE, QUESTION_TYPE_DESCRIPTIVE), (QUESTION_TYPE_MULTIPLE_CHOICE, QUESTION_TYPE_MULTIPLE_CHOICE)]
QUESTION_TYPE_SERIALIZER_CHOICES = [
    QUESTION_TYPE_DESCRIPTIVE, QUESTION_TYPE_MULTIPLE_CHOICE]

QUESTION_FORMAT_MD_V1 = "markdownV1"
QUESTION_FORMAT_HTML_V1 = "htmlV1"
QUESTION_FORMAT_JSON_V1 = "jsonV1"
QUESTION_FORMAT_TEXT = "text"

QUESTION_FORMAT_DATABASE_CHOICES = [
    (QUESTION_FORMAT_MD_V1, QUESTION_FORMAT_MD_V1),
    (QUESTION_FORMAT_HTML_V1, QUESTION_FORMAT_HTML_V1),
    (QUESTION_FORMAT_JSON_V1, QUESTION_FORMAT_JSON_V1),
    (QUESTION_FORMAT_TEXT, QUESTION_FORMAT_TEXT),
]

QUESTION_FORMAT_SERIALIZER_CHOICES = [
    QUESTION_FORMAT_MD_V1,
    QUESTION_FORMAT_HTML_V1,
    QUESTION_FORMAT_JSON_V1
]


class Question(models.Model):
    id = models.UUIDField(primary_key=True, unique=True,
                          db_index=True, default=uuid.uuid4, editable=False)
    question_bank = models.ForeignKey(
        QuestionBank, on_delete=models.SET_NULL, db_index=True, null=True, blank=True)
    keywords = models.CharField(
        max_length=600, db_index=True, null=True, blank=True)
    popularity = models.PositiveBigIntegerField(default=0, db_index=True)
    question_text = models.TextField()

    format = models.CharField(max_length=20)
    # HStoreField and JSONField do not preserve dict key orders
    choices = HStoreField(null=True)
    # TODO
    # default_orders = ArrayField(models.CharField(max_length=10), null=True)
    correct_choices = ArrayField(models.CharField(max_length=10), null=True)
    input_rules = models.JSONField(null=True)
    solution = models.TextField(null=True)
    question_type = models.CharField(
        max_length=20, db_index=True, choices=QUESTION_TYPE_DATABASE_CHOICES)
    # deprecated
    answer_rules = models.JSONField(null=True)
    auto_correctable = models.BooleanField(db_index=True)
    tags = HStoreField(db_index=True, default=dict)
    archived = models.BooleanField(default=False, db_index=True)
    meta_data = models.JSONField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    modified_at = models.DateTimeField(auto_now=True, db_index=True)

    tracker = FieldTracker(fields=['tags'])

    def __str__(self):
        return str(self.keywords) or "-"


class ScopedTag(models.Model):
    key = models.CharField(max_length=200, db_index=True)
    value = models.CharField(max_length=200, db_index=True)
    question_bank = models.ForeignKey(
        QuestionBank, on_delete=models.CASCADE, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    modified_at = models.DateTimeField(auto_now=True, db_index=True)

    class Meta:
        constraints = [models.UniqueConstraint(
            fields=['key', 'value', 'question_bank'], name='key_and_value_and_qb_unique')]


question_bank_and_key_and_value_unique = models.UniqueConstraint(fields=['question_bank', 'key', 'value'],
                                                                 name='question_bank_and_key_and_value_unique')


def get_query_from_tag_filters_list(tag_filters):
    return reduce(and_, (
        get_query_from_tag_filter(tag_filter) for tag_filter in tag_filters
    ))


def get_query_from_tag_filter_value(key, tag_filter_value):
    if len(tag_filter_value['children']) != 0:
        return reduce(and_, (
            Q(tags__contains={key: tag_filter_value["value"]}),
            get_query_from_tag_filters_list(tag_filter_value['children'])
        ))
    else:
        return Q(tags__contains={key: tag_filter_value["value"]})


def get_query_from_tag_filter(tag_filter):
    return reduce(or_, (
        get_query_from_tag_filter_value(tag_filter["key"], tag_filter_value) for tag_filter_value in tag_filter['values']
    ))
