from typing import Type, Tuple

from django.db.models import Model


class DataGenerator:
    """
        Базовый класс для генератора, который возвращает поток из
        соответствующих данных объектов из базы и объектов из внешнего
        источника,
        а также ключа по которому они сравниваются.
        Так же генератор умеет по ключу получить объект из базы, для выполнения
        над ним операций.
    """

    model: Type[Model] = None
    queryset = None
    sync_fields: Tuple[str, ...] = ()
    diff_fields: Tuple[str, ...] = ()

    def __init__(self, external_datasource):
        self.value_fields = self.get_sync_fields() + self.get_diff_fields()
        self.external_datasource = external_datasource

    def get_model(self):
        return self.model or self.queryset.model

    def get_queryset(self):
        return (
            self.queryset.all()
            if self.queryset is not None
            else self.model.objects.all()
        )

    def set_related_object(self, obj, data):
        pass

    def get_object(self, sync_key):
        f = dict(zip(self.get_sync_fields(), sync_key))
        try:
            obj = self.get_queryset().get(**f)
        except self.get_model().DoesNotExist:
            obj = self.create_object(sync_key)
        return obj

    def create_object(self, sync_key):
        f = dict(zip(self.get_sync_fields(), sync_key))
        init_data = dict((k, v) for k, v in f.items() if '__' not in k)
        obj = self.get_model()(**init_data)
        try:
            self.set_related_object(obj, f)
        except Exception:
            return None
        return obj

    def build_sync_key(self, data):
        return tuple(data[f] for f in self.get_sync_fields())

    def get_int_objects(self):
        return {
            self.build_sync_key(int_data): int_data
            for int_data in self.get_queryset().values(*self.value_fields)
        }

    def ext_data_gen(self):
        raise NotImplementedError

    def get_sync_fields(self):
        return self.sync_fields

    def get_diff_fields(self):
        return self.diff_fields

    def __iter__(self):
        int_objects = self.get_int_objects()

        for ext_data in self.ext_data_gen():
            sync_key = self.build_sync_key(ext_data)
            int_data = int_objects.pop(sync_key, {})

            yield ext_data, int_data, sync_key

        for sync_key, int_data in int_objects.items():
            yield {}, int_data, sync_key
