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, 36, 31, 62), '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, 18, 32, 48), 'maestro.backends.django.utils.model_to_entity_name', 'model_to_entity_name', ({(32, 39, 32, 47): 'instance'}, {}), '(instance)', False, 'from maestro.backends.django.utils import model_to_entity_name\n'), ((47, 36, 47, 62), '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, 18, 48, 48), 'maestro.backends.django.utils.model_to_entity_name', 'model_to_entity_name', ({(48, 39, 48, 47): 'instance'}, {}), '(instance)', False, 'from maestro.backends.django.utils import model_to_entity_name\n'), ((63, 4, 67, 5), 'django.db.models.signals.post_save.connect', 'post_save.connect', (), '', False, 'from django.db.models.signals import post_save, pre_delete\n'), ((69, 4, 73, 5), 'django.db.models.signals.pre_delete.connect', 'pre_delete.connect', (), '', False, 'from django.db.models.signals import post_save, pre_delete\n'), ((86, 4, 90, 5), 'django.db.models.signals.post_save.disconnect', 'post_save.disconnect', (), '', False, 'from django.db.models.signals import post_save, pre_delete\n'), ((91, 4, 95, 5), 'django.db.models.signals.pre_delete.disconnect', 'pre_delete.disconnect', (), '', False, 'from django.db.models.signals import post_save, pre_delete\n'), ((61, 51, 61, 86), 'typing.cast', 'cast', ({(61, 56, 61, 61): '"""str"""', (61, 63, 61, 85): 'model._meta.model_name'}, {}), "('str', model._meta.model_name)", False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), ((78, 16, 78, 41), 'django.apps.apps.get_model', 'apps.get_model', ({(78, 31, 78, 40): 'app_model'}, {}), '(app_model)', False, 'from django.apps import apps\n'), ((84, 51, 84, 86), 'typing.cast', 'cast', ({(84, 56, 84, 61): '"""str"""', (84, 63, 84, 85): 'model._meta.model_name'}, {}), "('str', model._meta.model_name)", False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), ((37, 13, 37, 36), 'copy.deepcopy', 'copy.deepcopy', ({(37, 27, 37, 35): 'instance'}, {}), '(instance)', False, 'import copy\n'), ((40, 54, 40, 77), 'copy.deepcopy', 'copy.deepcopy', ({(40, 68, 40, 76): 'instance'}, {}), '(instance)', False, 'import copy\n'), ((53, 13, 53, 36), 'copy.deepcopy', 'copy.deepcopy', ({(53, 27, 53, 35): 'instance'}, {}), '(instance)', False, 'import copy\n'), ((56, 61, 56, 84), 'copy.deepcopy', 'copy.deepcopy', ({(56, 75, 56, 83): 'instance'}, {}), '(instance)', False, 'import copy\n'), ((61, 8, 61, 42), 'typing.cast', 'cast', ({(61, 13, 61, 18): '"""str"""', (61, 20, 61, 41): 'model._meta.app_label'}, {}), "('str', model._meta.app_label)", False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), ((84, 8, 84, 42), 'typing.cast', 'cast', ({(84, 13, 84, 18): '"""str"""', (84, 20, 84, 41): 'model._meta.app_label'}, {}), "('str', model._meta.app_label)", False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n')]