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)

[((928, 954), 'maestro.backends.django.contrib.factory.create_django_data_store', 'create_django_data_store', ([], {}), '()\n', (952, 954), False, 'from maestro.backends.django.contrib.factory import create_django_data_store\n'), ((973, 1003), 'maestro.backends.django.utils.model_to_entity_name', 'model_to_entity_name', (['instance'], {}), '(instance)\n', (993, 1003), False, 'from maestro.backends.django.utils import model_to_entity_name\n'), ((1445, 1471), 'maestro.backends.django.contrib.factory.create_django_data_store', 'create_django_data_store', ([], {}), '()\n', (1469, 1471), False, 'from maestro.backends.django.contrib.factory import create_django_data_store\n'), ((1490, 1520), 'maestro.backends.django.utils.model_to_entity_name', 'model_to_entity_name', (['instance'], {}), '(instance)\n', (1510, 1520), False, 'from maestro.backends.django.utils import model_to_entity_name\n'), ((1984, 2091), 'django.db.models.signals.post_save.connect', 'post_save.connect', ([], {'receiver': 'model_saved_signal', 'sender': 'model', 'dispatch_uid': "(full_label + '_update_sync')"}), "(receiver=model_saved_signal, sender=model, dispatch_uid=\n    full_label + '_update_sync')\n", (2001, 2091), False, 'from django.db.models.signals import post_save, pre_delete\n'), ((2123, 2235), 'django.db.models.signals.pre_delete.connect', 'pre_delete.connect', ([], {'receiver': 'model_pre_delete_signal', 'sender': 'model', 'dispatch_uid': "(full_label + '_delete_sync')"}), "(receiver=model_pre_delete_signal, sender=model,\n    dispatch_uid=full_label + '_delete_sync')\n", (2141, 2235), False, 'from django.db.models.signals import post_save, pre_delete\n'), ((2578, 2687), 'django.db.models.signals.post_save.disconnect', 'post_save.disconnect', ([], {'receiver': 'model_saved_signal', 'sender': 'model', 'dispatch_uid': "(full_label + '_update_sync')"}), "(receiver=model_saved_signal, sender=model,\n    dispatch_uid=full_label + '_update_sync')\n", (2598, 2687), False, 'from django.db.models.signals import post_save, pre_delete\n'), ((2719, 2834), 'django.db.models.signals.pre_delete.disconnect', 'pre_delete.disconnect', ([], {'receiver': 'model_pre_delete_signal', 'sender': 'model', 'dispatch_uid': "(full_label + '_delete_sync')"}), "(receiver=model_pre_delete_signal, sender=model,\n    dispatch_uid=full_label + '_delete_sync')\n", (2740, 2834), False, 'from django.db.models.signals import post_save, pre_delete\n'), ((1938, 1973), 'typing.cast', 'cast', (['"""str"""', 'model._meta.model_name'], {}), "('str', model._meta.model_name)\n", (1942, 1973), False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), ((2350, 2375), 'django.apps.apps.get_model', 'apps.get_model', (['app_model'], {}), '(app_model)\n', (2364, 2375), False, 'from django.apps import apps\n'), ((2532, 2567), 'typing.cast', 'cast', (['"""str"""', 'model._meta.model_name'], {}), "('str', model._meta.model_name)\n", (2536, 2567), False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), ((1148, 1171), 'copy.deepcopy', 'copy.deepcopy', (['instance'], {}), '(instance)\n', (1161, 1171), False, 'import copy\n'), ((1266, 1289), 'copy.deepcopy', 'copy.deepcopy', (['instance'], {}), '(instance)\n', (1279, 1289), False, 'import copy\n'), ((1672, 1695), 'copy.deepcopy', 'copy.deepcopy', (['instance'], {}), '(instance)\n', (1685, 1695), False, 'import copy\n'), ((1797, 1820), 'copy.deepcopy', 'copy.deepcopy', (['instance'], {}), '(instance)\n', (1810, 1820), False, 'import copy\n'), ((1895, 1929), 'typing.cast', 'cast', (['"""str"""', 'model._meta.app_label'], {}), "('str', model._meta.app_label)\n", (1899, 1929), False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n'), ((2489, 2523), 'typing.cast', 'cast', (['"""str"""', 'model._meta.app_label'], {}), "('str', model._meta.app_label)\n", (2493, 2523), False, 'from typing import Type, Optional, List, cast, TYPE_CHECKING\n')]