from collections import defaultdict
from datetime import datetime
import logging
from typing import Any, Literal, TypedDict, TYPE_CHECKING, Union

from django.apps import apps
from django.conf import settings
from django.db.models import Model, Q
from django.urls import reverse, NoReverseMatch

from ..models import ConcordEvents, ConcordEvent

if TYPE_CHECKING:
    from console_base.models import BaseIntegerPKModel, BaseUUIDPKModel

logger = logging.getLogger(__name__)
OPERATIONS = Literal['insert', 'update', 'delete']


class RecordEventData(TypedDict):
    model_name: str
    css_icon: str
    record: ConcordEvent
    record_url: str
    record_name: str


class OperationEventData(TypedDict):
    pk: int
    date: datetime
    model_name: str
    css_icon: str
    operation: OPERATIONS
    context: dict[str, Any]
    record_url: str
    record_name: str


# ---------------------------------------------------------------------------
class ConcordEventsTrail:
    """
    Handle retrieval and display of Record Events in an Audit Trail view.
    """

    def __init__(self, date: datetime | None = None):
        self.date = date
        self._event_models: dict[str, list[type[Model]]] = {}

    def event_models(self) -> dict:
        """
        Return all models in the App that are Record Event tracking models.
        """
        if not self._event_models:
            event_models = defaultdict(list)
            for app, models in apps.all_models.items():
                if app not in settings.AUDITABLE_APPS:
                    continue
                for name, model_class in models.items():
                    if name.endswith('event'):
                        event_models[app].append(model_class)
            self._event_models = dict(event_models)
        return self._event_models

    def unviewed_events_exist(self) -> bool:
        """
        Check to see if any events have occurred since the last date specified.
        """
        for app, models in self.event_models().items():
            for EventModel in models:
                if EventModel.objects.filter(pgh_created_at__gte=self.date).exists():
                    return True
        return False

    def unviewed_events(self) -> list[RecordEventData]:
        """
        Return all Events that occurred after the specified date for display to the User.
        """
        all_events = []

        for app, Models in self.event_models().items():
            for EventModel in Models:
                event_models = (
                    EventModel.objects.filter(
                        pgh_created_at__gte=self.date,
                    )
                    .select_related('pgh_obj')
                    .distinct('pgh_obj')
                )

                for event_model in event_models:
                    console_record = event_model.pgh_obj
                    console_record_url = console_record.get_absolute_url()

                    events = (
                        ConcordEvents.objects.tracks(console_record)
                        .filter(
                            pgh_created_at__gte=self.date,
                        )
                        .select_related(
                            'user',
                        )
                        .order_by('-pgh_created_at')
                    )

                    for event in events:
                        all_events.append(
                            RecordEventData(
                                model_name=console_record.__class__.__name__,
                                css_icon=console_record.css_icon,
                                record=event,
                                record_url=console_record_url,
                                record_name=str(console_record),
                            )
                        )

        all_events.sort(key=lambda e: e['record'].pgh_created_at, reverse=True)

        return all_events

    def record_events(
        self, target: Union['BaseIntegerPKModel', 'BaseUUIDPKModel']
    ) -> list[RecordEventData]:
        """
        Return all Events for the specified target record.
        """
        all_events = []
        url = target.get_absolute_url()

        events = (
            ConcordEvents.objects.tracks(target)
            .filter(
                pgh_created_at__gte=self.date,
            )
            .select_related(
                'user',
            )
            .order_by('-pgh_created_at')
        )

        for event in events:
            all_events.append(
                RecordEventData(
                    model_name=target.__class__.__name__,
                    css_icon=target.css_icon,
                    record=event,
                    record_url=url,
                    record_name=str(target),
                )
            )

        return all_events

    def model_events(
        self,
        event_model: ConcordEvent,
        operations: tuple[OPERATIONS] = ('delete',),
        query_params: Q | None = None,
    ) -> list[OperationEventData]:
        """
        Return Create / Delete Events for the specified model.
        """
        all_events: list[OperationEventData] = []
        if not (pgh_tracked_model := event_model.pgh_tracked_model):
            return all_events

        pgh_labels = operations or ['delete']
        event_qp: list[tuple[str, datetime | str]] = [('pgh_label__in', pgh_labels)]  # type: ignore[list-item]
        if self.date:
            event_qp.append(('pgh_created_at__gte', self.date))

        tracked_model = pgh_tracked_model()
        url_name = tracked_model.get_url_base_name
        lookup_fields = [
            'pk',
            'pgh_created_at',
            'pgh_obj_id',
            'pgh_label',
            'pgh_context',
        ]
        if 'name' in tracked_model.get_model_fields():
            lookup_fields.append('name')

        filter_params = query_params & Q(*event_qp) if query_params else Q(*event_qp)
        events = (
            event_model.objects.filter(
                filter_params,
            )
            .order_by('-pgh_created_at')
            .values_list(*lookup_fields, named=True)
        )

        for event in events:
            context: dict[str, Any] = event.pgh_context or {}
            record_name = getattr(event, 'name', '') or context.get('record_name', 'N/A')
            all_events.append(
                OperationEventData(
                    pk=event.pk,
                    date=event.pgh_created_at,
                    model_name=tracked_model.__class__.__name__,
                    operation=event.pgh_label,
                    context=context,
                    css_icon=tracked_model.css_icon,
                    record_url=get_record_url(event, url_name),
                    record_name=record_name,
                )
            )

        return all_events


def get_record_url(event: ConcordEvent, url_name: str) -> str:
    if event.pgh_label == 'delete':
        return '#'

    try:
        return reverse(url_name, kwargs={'pk': event.pgh_obj_id})  # type: ignore[attr-defined]
    except NoReverseMatch:
        return '#'
