import logging
from typing import Any, TYPE_CHECKING
from uuid import UUID
from django.conf import settings
from django.db.models import Manager, Q, QuerySet
from django.contrib.contenttypes.models import ContentTypeManager, ContentType
from django.db import DatabaseError
from django.utils.module_loading import import_string
from pghistory.models import EventQuerySet, EventsQuerySet

if TYPE_CHECKING:
    from .models import ConcordEvent, SyncableModel
    from concordia_tests.latchstring.models import User

from console_base.managers import LCBaseQuerySet
from .constants import PORTAL_RECORD_PK_KEY, SYNC_COMPLETED_KEY

logger = logging.getLogger(__name__)
try:
    SYNC_TABLES_Q = import_string(settings.CONCORD_SYNC_TABLES_QS)
except (AttributeError, ImportError):
    print('settings does not define CONCORD_SYNC_TABLES_QS')
    SYNC_TABLES_Q = Q()


# ---------------------------------------------------------------------------
def set_contenttype_kwargs(**kwargs) -> dict[str, Any]:  # type: ignore[no-untyped-def]
    """Lookup Generic values from schedule keyword if it was provided."""
    record = kwargs.pop('record', None)
    record_data: dict[str, Any] = kwargs.pop('record_data') or {}

    if record:
        try:
            kwargs['object_id'] = record_data.get(PORTAL_RECORD_PK_KEY, '') or record.id
            kwargs['table'] = ContentType.objects.get_for_model(record)
        except AttributeError:
            raise ValueError("'record' value must be a Model Object!")

    return kwargs


# ---------------------------------------------------------------------------
class ConcordiaContentTypeManager(ContentTypeManager):
    def get_queryset(self):
        if SYNC_TABLES_Q:
            return super().get_queryset().filter(SYNC_TABLES_Q).order_by('app_label')
        return super().get_queryset().order_by('app_label')


# ---------------------------------------------------------------------------
class BaseCanonicalIdTrackManager(Manager.from_queryset(LCBaseQuerySet)):  # type: ignore
    def create(self, **kwargs):
        return super().create(**set_contenttype_kwargs(**kwargs))

    def get_or_create(self, defaults=None, **kwargs):
        if defaults:
            defaults = set_contenttype_kwargs(**defaults)
        return super().get_or_create(defaults=defaults, **set_contenttype_kwargs(**kwargs))

    def update_or_create(self, defaults=None, **kwargs):
        if defaults:
            defaults = set_contenttype_kwargs(**defaults)
        return super().update_or_create(defaults=defaults, **set_contenttype_kwargs(**kwargs))


# ---------------------------------------------------------------------------
class CanonicalIdDeleteLogManager(BaseCanonicalIdTrackManager):
    model: 'SyncableModel'

    def record_deletion(
        self,
        record: 'SyncableModel',
        data: dict | None = None,
    ) -> QuerySet['SyncableModel'] | None:
        """
        Record a Canonical ID deletion instance.
        """
        try:
            delete = self.model.objects.create(
                record=record,
                record_data=data,
                name=data.get('name', 'N/A') if isinstance(data, dict) else str(record),
                cid=data['cid'] if isinstance(data, dict) else record.cid,
            )
            return delete
        except DatabaseError as e:
            logger.exception('Unable to record Canonical ID deletion: %e', e)
            pass


# ---------------------------------------------------------------------------
class CanonicalIdSwapManager(BaseCanonicalIdTrackManager):
    """Manager for CanonicalIdSwap model."""

    model: 'SyncableModel'

    def record_swap(
        self,
        record: 'SyncableModel',
        old: UUID,
        data: dict | None = None,
    ) -> QuerySet['SyncableModel'] | None:
        """
        Record a Canonical ID Swap instance.
        """
        try:
            swap = self.model.objects.create(
                record=record,
                record_data=data,
                name=data.get('name', 'N/A') if isinstance(data, dict) else str(record),
                cid=data['cid'] if isinstance(data, dict) else record.cid,
                old=old,
            )
            swap.apply()
            return swap
        except DatabaseError as e:
            logger.exception('Unable to record Canonical ID swap: %e', e)
            pass


# ---------------------------------------------------------------------------
class CanonicalIdMapManager(BaseCanonicalIdTrackManager):
    """Manager for CanonicalIdMap model."""

    model: 'SyncableModel'

    def record_map(
        self,
        record: 'SyncableModel',
        old: UUID,
        data: dict | None = None,
    ) -> QuerySet['SyncableModel'] | None:
        """
        Record a Canonical ID Map instance.
        """
        try:
            mapping = self.model.objects.create(
                record=record,
                record_data=data,
                name=data.get('name', 'N/A') if isinstance(data, dict) else str(record),
                cid=data['cid'] if isinstance(data, dict) else record.cid,
                old=old,
            )
            mapping.apply()
            return mapping
        except DatabaseError as e:
            logger.exception('Unable to record Canonical ID map: %e', e)
            pass


# ---------------------------------------------------------------------------
class SyncedEventMixin:
    def synced(self) -> QuerySet['ConcordEvent']:
        """
        Get all Console Events that have been synced with Publisher.
        """
        qs = self._clone()  # type: ignore[attr-defined]
        return qs.filter((f'pgh_context__{SYNC_COMPLETED_KEY}', True))

    def unsynced(self) -> QuerySet['ConcordEvent']:
        """
        Get all Console Events that have not been synced with Publisher.
        """
        qs = self._clone()  # type: ignore[attr-defined]

        return qs.filter(
            Q(pgh_context__has_key=SYNC_COMPLETED_KEY, _negated=True)
            | Q((f'pgh_context__{SYNC_COMPLETED_KEY}', False))
        )


# ---------------------------------------------------------------------------
class ConsoleEventQuerySet(SyncedEventMixin, EventQuerySet):
    pass


# ---------------------------------------------------------------------------
class ConcordEventsQuerySet(SyncedEventMixin, EventsQuerySet):
    pass


# ---------------------------------------------------------------------------
class SyncQuerySet(LCBaseQuerySet):
    """
    Used for type hints
    """

    def tenant(self, user: 'User') -> QuerySet:
        return self.all()
