# Tasks for Publishing messages to Concordia Message Queue
from celery import shared_task
from celery_singleton.exceptions import DuplicateTaskError
import logging

from django.conf import settings

from ..acls import skip_publication
from ..channels import ConcordTriggerChannel, ROOM_CHANNEL_REGISTRY
from ..choices import CONTEXT, CRUD, ORIGIN_SERVER
from ..constants import OPERATION_KEY, PORTAL_RECORD_PK_KEY
from ..events.operations import mark_events_synced, mq_payload_from_events
from ..mq.helpers import publish_concord_message

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
@shared_task
def sync_subscriber_publish_local_crud_event_to_concord_room(room_name: str, data: dict) -> None:
    """
    When ConcordEvents are made on Sync Subscribers, post message to
    the Sync Publisher Channel. The Sync Publisher will be responsible to
    ensure that the change is published to all relevant Subscribers.
    """
    logger.info(
        f'sync_subscriber_publish_local_crud_event_to_concord_room {room_name}. Data: {data}'
    )
    return
    channel = ROOM_CHANNEL_REGISTRY[room_name]
    record = channel.model.pgh_tracked_model.objects.filter(id=data[PORTAL_RECORD_PK_KEY]).first()

    # Recheck for publication status, now that we have a record object to inspect
    if skip_publication(record=record):
        return

    payload, record_events = mq_payload_from_events(record=record, serializer=channel.serializer)
    payload[CONTEXT.Origin] = data[CONTEXT.Event]

    # Include the Event Context ID(s) so that the Publisher can return then
    # in the Response back, and then we'll mark the ConcordEvents as synced.
    payload[CONTEXT.Origin][CONTEXT.Keys] = [re.pgh_context_id for re in record_events]

    rooms = channel.publish_room_names(record)
    result = publish_concord_message(rooms=rooms, payload=payload)

    published_status = [m.is_published() for m in result]
    logger.info(
        f'Concordia MQ Subscriber->Willow Event Room {rooms!r}. '
        f'Data: {payload!r}. Published: {published_status}'
    )


# ---------------------------------------------------------------------------
@shared_task
def sync_publisher_publish_local_crud_event_to_concord_room(room_name: str, data: dict) -> None:
    """
    When ConcordEvents are made on the Willow Sync Publisher,
    post message to all the Subscribers that need this record.
    """
    logger.info(
        f'sync_publisher_publish_local_crud_event_to_concord_room {room_name}. Data: {data}'
    )
    return
    channel = ROOM_CHANNEL_REGISTRY[room_name]
    record = channel.model.pgh_tracked_model.objects.filter(id=data[PORTAL_RECORD_PK_KEY]).first()

    # Recheck for publication status, now that we have a record object to inspect
    if skip_publication(record=record):
        return

    payload, record_events = mq_payload_from_events(record=record, serializer=channel.serializer)
    payload[CONTEXT.Origin] = data[CONTEXT.Event]
    context_ids = [re.pgh_context_id for re in record_events]

    try:
        # Appliance CID gets added in middleware, so it should always be present,
        # but don't crash if it isn't.
        origin_concord_mq = data[CONTEXT.Event][ORIGIN_SERVER.CID]
    except KeyError:
        logger.error(f'Unable to get origin Concord MQ CID from {payload}')
        origin_concord_mq = ''

    if subscriber_rooms := channel.publish_room_names(record, origin_concord_mq):
        result = publish_concord_message(rooms=subscriber_rooms, payload=payload)
        mark_synced = bool(result)
        msg = f'Published {subscriber_rooms!r}. Payload: {payload!r}. Result: {result}'
    else:
        mark_synced = True
        msg = f'No subscribers required payload: {payload} from {room_name}'

    logger.info(f'Concordia MQ Willow->Subscriber - {msg}')

    # Mark the events as synced to remove them from future sync operations.
    if mark_synced:
        mark_events_synced(channel.model.objects.filter(pgh_context_id__in=context_ids))


# ---------------------------------------------------------------------------
@shared_task
def publish_local_delete_event_to_concord_room(data: dict) -> None:
    """
    When local Concord Events deletion records are made, post message
    to Publisher Channel. A record that can be deleted on one Subscriber
    could still be relevant to other subscribers, so the Publisher
    will decide whether to send a message to the Subscribers.
    """
    logger.info(f'publish_local_delete_event_to_concord_room. Data: {data}')
    return
    # channel = ROOM_CHANNEL_REGISTRY[room_name]
    # payload = {
    #     'name': data.get('name', 'N/A'),
    #     'cid': data['cid'],
    #     CONTEXT.Origin: data[CONTEXT.Event],
    # }
    #
    # # Record the Canonical ID so it's never used again, unless specifically cleared.
    # if settings.SYNC_PUBLISHER:
    #     CanonicalIdDeleteLog.objects.record_deletion(record=channel.model, data=payload)
    #
    # room = channel.get_room_name()
    # result = publish_concord_message(rooms=[room], payload=payload)
    #
    # logger.info(
    #     f'Concordia MQ to Publisher Delete Room {room!r}. Data: {payload!r}. Result: {result}'
    # )


# ---------------------------------------------------------------------------
def schedule_publish_local_events_to_concord_room(
    channel: type[ConcordTriggerChannel],
    data: dict,
) -> None:
    """
    Wrapper to choose correct Publish task,
    handling DuplicateTaskError exception.
    """
    logger.info(f'schedule_publish_local_events_to_concord_room {channel}. Data: {data}')
    return
    if skip_publication(data):
        return

    # For task API consistency, ensure that data context is always dict,
    # since data payload may not always be generated from ConcordEvent.
    if (_ := data.get(CONTEXT.Event)) is None:
        data[CONTEXT.Event] = {}

    if data[CONTEXT.Event].get(OPERATION_KEY) == CRUD.Delete:
        task_function = publish_local_delete_event_to_concord_room
    else:
        if settings.SYNC_PUBLISHER:
            task_function = sync_publisher_publish_local_crud_event_to_concord_room
        else:
            task_function = sync_subscriber_publish_local_crud_event_to_concord_room

    try:
        result = task_function.apply_async(
            expires=settings.CONCORD_MQ_ROOM_POST_TASK_EXPIRATION,
            kwargs={
                'data': data,
            },
        )
        return True, result
    except DuplicateTaskError:
        msg = f'Sync task already scheduled for Channel: {channel}'
        logger.error(msg)
        return False, msg


__all__ = (
    'sync_subscriber_publish_local_crud_event_to_concord_room',
    'publish_local_delete_event_to_concord_room',
    'schedule_publish_local_events_to_concord_room',
)
