# Tasks for responding to messages from Concordia Message Queue Subscriptions
from celery import shared_task
import logging
import pghistory
from typing import Any

from django.conf import settings
from django.db import DatabaseError

from ..channels import (
    ConcordTriggerChannel,
    ROOM_CHANNEL_REGISTRY,
)
from ..choices import CONTEXT
from ..constants import (
    SYNC_COMPLETED_KEY,
)
from ..events.operations import (
    mark_events_synced,
    prune_record_fields,
    persist_changed_fields,
)
from ..mq.helpers import load_record
from ..typehints import Status

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
@shared_task
def persist_concord_room_message_to_database(
    message_room: str,
    payload: dict,
) -> Status:
    """
    Take a ConcordiaMQ message and save it to local database.
    """
    channel = ROOM_CHANNEL_REGISTRY[message_room]

    # Include context from upstream server and attach it to this instance
    # to retain clearer picture of where the record change originated.
    context: dict[str, Any] = payload.pop(CONTEXT.Origin) or {}

    # If the Sync Publisher is receiving a message from a Subscriber, sync
    # can't be marked "completed" until messages are broadcast to Subscribers
    context[SYNC_COMPLETED_KEY] = not settings.SYNC_PUBLISHER

    room = channel.room_name()
    serializer = channel.serializer
    Model = channel.model.pgh_tracked_model

    cid = payload['cid']['value']
    record = load_record(Model, cid, context)
    if not record.permitted:
        msg = f'User not permitted to change record with CID: {cid!r} on Room: {room!r}'
        logger.error(msg)
        return Status(False, {'message': msg})

    if not record.obj:
        return Status(False, {'message': 'No record object found'})

    # Lookup any unsynced records so the incoming data can be merged with local unsynced data
    # event_data, record_events = mq_payload_from_events(record=record.obj, serializer=serializer)

    logger.info(f'Concordia MQ msg - {room!r} data: {payload!r} context: {context}')

    # Record actor was queried by CID; assign PK for
    # local FK relations to work on ConcordEvents model
    if record.editor:
        context['user'] = record.editor.pk

    with pghistory.context(**context):
        status = persist_changed_fields(
            record=record.obj,
            serializer=serializer,
            cleaned_data=prune_record_fields(record.obj, payload),
            user=record.editor,
        )

    return status


# ---------------------------------------------------------------------------
@shared_task
def persist_concord_room_deletion_message_to_database(
    message_room: str,
    payload: dict,
) -> Status:
    """
    Take a ConcordiaMQ deletion message and ensure it's deleted.
    """
    channel = ROOM_CHANNEL_REGISTRY[message_room]

    # Include context from upstream server and attach it to this instance
    # to retain clearer picture of where the record change originated.
    context = payload[CONTEXT.Origin]

    # If the Sync Publisher is receiving a message from a Subscriber, sync
    # can't be marked "completed" until messages are broadcast to Subscribers
    context[SYNC_COMPLETED_KEY] = not settings.SYNC_PUBLISHER

    cid = payload['cid']['value']
    record = load_record(channel.model.pgh_tracked_model, cid, context)
    if not record.obj:
        return Status(True, {'message': f'No record found for {cid}'})

    # Record actor was queried by CID; assign PK for
    # local FK relations to work on ConcordEvents model
    if record.editor:
        context['user'] = record.editor.pk

    with pghistory.context(**context):
        try:
            # Don't force deletion if this record is a Sync Publisher
            # since other records may depend on its existence.
            record.obj.delete(force=not settings.SYNC_PUBLISHER)
            status = Status(True, {'message': f'Record {record.obj} was deleted'})
        except DatabaseError as e:
            logger.error('Unable to delete %s - %s', (record.obj, e))
            status = Status(False, {'message': str(e)})

    return status


# ---------------------------------------------------------------------------
def confirm_event_synced(channel: type[ConcordTriggerChannel], data: dict) -> Status:
    """
    Confirm that the ConcordEvent record has been synchronized.
    """
    EventModel = channel.model
    events = EventModel.objects.unsynced().filter(pgh_context_id__in=data[CONTEXT.Keys])  # type: ignore[attr-defined]

    try:
        mark_events_synced(events)
        msg = f'Marked {len(events)} as synced in Topic: {channel.room_name()}'
        return Status(True, {'message': msg})
    except Exception as e:
        logger.exception('Unable to confirm synced events %s: %s', (channel, e))
        return Status(False, {'message': str(e)})


__all__ = (
    'persist_concord_room_message_to_database',
    'persist_concord_room_deletion_message_to_database',
    'confirm_event_synced',
)
