import os
import hashlib
import sform
from datetime import datetime

from django.db.models import Q
from django.conf import settings

from pymongo.write_concern import WriteConcern

from staff.lib.decorators import custom_dumps
from staff.lib.mongodb import mongo

from ._base import lib, OPERATORS

__all__ = 'FilterCtl',

modules = sorted({
    f.rsplit('.', 1)[0]
    for f in os.listdir(os.path.split(__file__)[0])
    if f.startswith('f_')
})

for module in modules:
    __import__('.'.join([__name__, module]))

MONGO_COLLECTION = 'new_filters'


class FilterForm(sform.SForm):
    operator = sform.ChoiceField(
        choices=OPERATORS,
        state=sform.REQUIRED,
        default=OPERATORS.AND,
    )
    field = sform.ChoiceField(
        choices=[[f.__name__] * 2 for f in lib],
        state=sform.REQUIRED,
        default='DepartmentFilter',
    )

    def data_as_dict(self, prefix=''):
        data = super(FilterForm, self).data_as_dict(prefix)

        field = lib[self.initial['field']]
        condition_form = field.get_form_cls()(initial=self.initial)
        data.update(condition_form.data_as_dict(prefix))

        condition = field.conditions[self.initial['condition']]
        query_form = condition.form_cls(initial=self.initial)
        data.update(query_form.data_as_dict(prefix))

        return data

    def clean(self):
        field = lib[self.cleaned_data['field']]
        condition_form = field.get_form_cls()(self.data)
        errors = None

        if condition_form.is_valid():
            condition = field.conditions[
                condition_form.cleaned_data['condition']
            ]
            query_form = condition.form_cls(self.data)
            if query_form.is_valid():
                self.cleaned_data['query'] = query_form.cleaned_data
                self.cleaned_data['condition'] = condition(
                    cleaned_data=query_form.cleaned_data
                )
            else:
                errors = query_form._get_errors()
        else:
            errors = condition_form._get_errors()

        if errors:
            self._errors.update(errors)

        return self.cleaned_data


class FiltersForm(sform.SForm):
    filters = sform.GridField(
        sform.FieldsetField(FilterForm),
        state=sform.REQUIRED,
        # default=[{
        #     'operator': OPERATORS.AND,
        #     'field': 'DepartmentFilter',
        #     'condition': 'InHierarchy',
        # }],
    )


class FilterNotFound(Exception):
    pass


class FilterCtl(object):

    """Принимает список словарей вида:
    {'filters':[
        {
            'operator': 'and',
            'field': 'OfficeFilter',
            'condition': 'Equal',
            'office': 1,
        },
        {
            'operator': 'and',
            'field': 'ChiefFilter',
            'condition': 'Equal',
            'value': True,
        },
    ]}
    operator - оператор, который будет применен между фильтрами
    field - имя фильтра, котоорое определяет класс фильтра
    condition - класс условия для фильтра
    остальные поля - параметры для условия
    """

    def __init__(self, data=None, filter_id=None):
        self.filter_id = filter_id

        self.data = self.load_data(filter_id) if data is None else data
        if filter_id and self.data is None:
            raise FilterNotFound('filter %s not found', filter_id)

        self._form = None

    @classmethod
    def as_meta_dict(cls, initial=None):
        result = FiltersForm(initial=initial).as_dict()
        result['conditions'] = {f.__name__: f.as_meta_dict() for f in lib}
        return result

    def as_dict(self):
        return self.as_meta_dict(initial=self.data)

    @classmethod
    def get_by_data(cls, data):
        return FilterCtl(data)

    @classmethod
    def get_by_id(cls, filter_id):
        return FilterCtl(filter_id=filter_id)

    def is_valid(self):
        if self._form is None:
            self._form = FiltersForm(self.data)
        return self._form.is_valid()

    def get_errors(self):
        return self._form.errors

    def create_id(self):
        assert self.is_valid()

        data = []
        for row_data in self._form.cleaned_data['filters']:
            data.append([
                row_data['operator'],
                row_data['field'],
                row_data['condition'].name,
            ])
            data[-1].extend([
                row_data['condition'].cleaned_data[f]
                for f in row_data['condition'].form_cls().fields
            ])

        data = custom_dumps(data)
        filter_id = hashlib.sha1(data.encode('utf-8')).hexdigest()

        self.save_data(filter_id)

        return filter_id

    def save_data(self, filter_id):
        collection = mongo.db.get_collection(
            MONGO_COLLECTION,
            write_concern=WriteConcern(settings.MONGO_WRITE_CONCERN_W),
        )
        if not self.load_data(filter_id):
            collection.insert_one({
                'filter_id': filter_id,
                'creation_time': datetime.now(),
                'data': self.data,
            })

    def load_data(self, filter_id):
        collection = mongo.db.get_collection(MONGO_COLLECTION)
        saved = collection.find_one({'filter_id': filter_id})
        if saved:
            return saved['data']

    def get_query(self):
        q_stack = [Q()]
        for row_data in self._form.cleaned_data['filters']:
            operator = row_data['operator']
            condition = row_data['condition']
            f_query = condition.q()

            # Если or, то откладываем операцию
            # иначе выполняю ее с верхним элементом стека
            if 'or' in operator:
                q_stack.append(f_query)
            else:
                q_stack[-1] &= f_query

        q = Q()
        # Вторым проходом выполняю все or
        for f_query in q_stack:
            q |= f_query

        return q
