from django.apps import apps
from django.db import models
from django.db.models.signals import post_save, pre_delete
from typing import Type, Optional, List, cast, TYPE_CHECKING
from maestro.backends.django.settings import maestro_settings
from maestro.backends.django.contrib.factory import create_django_data_store
from maestro.backends.django.utils import model_to_entity_name
from maestro.core.metadata import Operation
from .middleware import _add_operation_to_queue
import copy

if TYPE_CHECKING:
    from maestro.backends.django import DjangoDataStore


def model_saved_signal(
    sender: "Type[models.Model]",
    instance: "models.Model",
    created: "bool",
    raw: "bool",
    using: "str",
    update_fields: "Optional[List[str]]",
    **kwargs,
):
    operation: "Operation"
    if created:
        operation = Operation.INSERT
    else:
        operation = Operation.UPDATE

    data_store: "DjangoDataStore" = create_django_data_store()
    entity_name = model_to_entity_name(instance)
    data_store.commit_item_change(
        operation=operation,
        entity_name=entity_name,
        item_id=str(instance.pk),
        item=copy.deepcopy(instance),
        execute_operation=False,
    )
    _add_operation_to_queue(operation=operation, item=copy.deepcopy(instance))


def model_pre_delete_signal(
    sender: "Type[models.Model]", instance: "models.Model", using: "str", **kwargs
):

    data_store: "DjangoDataStore" = create_django_data_store()
    entity_name = model_to_entity_name(instance)
    data_store.commit_item_change(
        operation=Operation.DELETE,
        entity_name=entity_name,
        item_id=str(instance.pk),
        item=copy.deepcopy(instance),
        execute_operation=False,
    )
    _add_operation_to_queue(operation=Operation.DELETE, item=copy.deepcopy(instance))


def _connect_signal(model: "models.Model"):
    full_label = (
        cast("str", model._meta.app_label) + "_" + cast("str", model._meta.model_name)
    )
    post_save.connect(
        receiver=model_saved_signal,
        sender=model,
        dispatch_uid=full_label + "_update_sync",
    )

    pre_delete.connect(
        receiver=model_pre_delete_signal,
        sender=model,
        dispatch_uid=full_label + "_delete_sync",
    )


def connect_signals():
    for app_model in maestro_settings.MODELS:
        model = apps.get_model(app_model)
        _connect_signal(model=model)


def _disconnect_signal(model: "models.Model"):
    full_label = (
        cast("str", model._meta.app_label) + "_" + cast("str", model._meta.model_name)
    )
    post_save.disconnect(
        receiver=model_saved_signal,
        sender=model,
        dispatch_uid=full_label + "_update_sync",
    )
    pre_delete.disconnect(
        receiver=model_pre_delete_signal,
        sender=model,
        dispatch_uid=full_label + "_delete_sync",
    )


class _DisableSignalsContext:
    def __init__(self, model: "Type[models.Model]"):
        self.model = model

    def __enter__(self):
        _disconnect_signal(model=self.model)

    def __exit__(self, type, value, traceback):
        label = self.model._meta.app_label + "." + self.model._meta.model_name
        enabled_models = [label.lower() for label in maestro_settings.MODELS]
        if label in enabled_models:
            _connect_signal(model=self.model)


def temporarily_disable_signals(model: "Type[models.Model]"):
    return _DisableSignalsContext(model=model)

[(31, 'maestro.backends.django.contrib.factory.create_django_data_store', 'create_django_data_store', False, 'from maestro.backends.django.contrib.factory import create_django_data_store\n'), (32, 'maestro.backends.django.utils.model_to_entity_name', 'model_to_entity_name', False, 'from maestro.backends.django.utils import model_to_entity_name\n'), (47, 'maestro.backends.django.contrib.factory.create_django_data_store', 'create_django_data_store', False, 'from maestro.backends.django.contrib.factory import create_django_data_store\n'), (48, 'maestro.backends.django.utils.model_to_entity_name', 'model_to_entity_name', False, 'from maestro.backends.django.utils import model_to_entity_name\n'), (63, 'django.db.models.signals.post_save.connect', 'post_save.connect', False, 'from django.db.models.signals import post_save, pre_delete\n'), (69, 'django.db.models.signals.pre_delete.connect', 'pre_delete.connect', False, 'from django.db.models.signals import post_save, pre_delete\n'), (86, 'django.db.models.signals.post_save.disconnect', 'post_save.disconnect', False, 'from django.db.models.signals import post_save, pre_delete\n'), (91, 'django.db.models.signals.pre_delete.disconnect', 'pre_delete.disconnect', False, 'from django.db.models.signals import post_save, pre_delete\n'), (61, 'typing.cast', 'cast', False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), (78, 'django.apps.apps.get_model', 'apps.get_model', False, 'from django.apps import apps\n'), (84, 'typing.cast', 'cast', False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), (37, 'copy.deepcopy', 'copy.deepcopy', False, 'import copy\n'), (40, 'copy.deepcopy', 'copy.deepcopy', False, 'import copy\n'), (53, 'copy.deepcopy', 'copy.deepcopy', False, 'import copy\n'), (56, 'copy.deepcopy', 'copy.deepcopy', False, 'import copy\n'), (61, 'typing.cast', 'cast', False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), (84, 'typing.cast', 'cast', False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n')]