"""Wire upstream signals into M&EL writes.

Kept intentionally thin — each handler delegates to ``apps.mel.tracking.services``
or ``apps.mel.indicators.services`` so the write path is shared with manual
entry views, API endpoints, and outbox drainers.
"""
from __future__ import annotations

import logging

from django.dispatch import receiver

from apps.mel.tracking.signals import (
    mel_activity_delayed,
    mel_data_point_recorded,
    mel_indicator_threshold_crossed,
)

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Repository signals (custom dispatch)
# ---------------------------------------------------------------------------

try:
    from apps.repository.documents.signals import (
        access_granted as _access_granted,
        document_published as _document_published,
    )
except ImportError:  # pragma: no cover
    _access_granted = None
    _document_published = None


if _document_published is not None:
    @receiver(_document_published, dispatch_uid="mel.document_published")
    def on_document_published(sender, document=None, **kwargs):
        if document is None:
            return
        from apps.mel.tracking.services import record_document_published

        record_document_published(document)


if _access_granted is not None:
    @receiver(_access_granted, dispatch_uid="mel.access_granted")
    def on_access_granted(sender, access=None, **kwargs):
        if access is None:
            return
        from apps.mel.tracking.services import record_document_access_granted

        record_document_access_granted(access)


# ---------------------------------------------------------------------------
# RIMS signals are handled from within the upstream signal handlers so they
# share the same transaction as award / graduation creation. See
# apps/rims/grants/signals.py and apps/rims/scholarships/signals.py.
# ---------------------------------------------------------------------------


# ---------------------------------------------------------------------------
# SME-Hub signals — Sub-Phase 6.6 longitudinal tracking (FRSME-MEI001 → MEI007)
# ---------------------------------------------------------------------------

try:
    from apps.mel.tracking.smehub_receivers import wire_smehub_receivers as _wire_smehub
except ImportError:  # pragma: no cover — partial-install state
    _wire_smehub = None

if _wire_smehub is not None:
    _wire_smehub()


# ---------------------------------------------------------------------------
# Internal MEL signals — re-score RAG and dispatch alerts.
# ---------------------------------------------------------------------------


@receiver(mel_data_point_recorded, dispatch_uid="mel.datapoint.rescore")
def on_data_point_recorded(sender, indicator=None, data_point=None, period_label=None, **kwargs):
    """When a new data point lands, re-score the indicator and emit a
    threshold-crossed signal for listeners (compliance alerts, etc.)."""
    if indicator is None or not period_label:
        return
    from apps.mel.indicators.services import calculate_progress

    progress = calculate_progress(indicator, period_label)
    rag = progress.get("rag")
    mel_indicator_threshold_crossed.send(
        sender=type(indicator),
        indicator=indicator,
        period_label=period_label,
        rag=rag,
        progress=progress,
    )


@receiver(mel_indicator_threshold_crossed, dispatch_uid="mel.threshold.dispatch_alert")
def on_threshold_crossed(sender, indicator=None, period_label=None, rag=None, progress=None, **kwargs):
    """Dispatch MEL_INDICATOR_OFF_TRACK notifications on RED crossings."""
    if indicator is None or progress is None:
        return
    from apps.mel.indicators.models import RagStatus
    from apps.mel.indicators.services import dispatch_indicator_alert

    if rag != RagStatus.RED:
        return
    dispatch_indicator_alert(indicator, progress)


@receiver(mel_activity_delayed, dispatch_uid="mel.activity.delayed_alert")
def on_activity_delayed(sender, activity=None, **kwargs):
    """Notify the responsible owner + logframe owner when an Activity slips."""
    if activity is None:
        return
    from apps.core.notifications.models import Notification
    from apps.core.notifications.services import send_notification

    seen: set[int] = set()
    recipients = []
    if activity.responsible_id:
        recipients.append(activity.responsible)
        seen.add(activity.responsible_id)
    lf = getattr(activity.logframe_row, "logframe", None)
    owner = getattr(lf, "owner", None)
    if owner and owner.pk not in seen:
        recipients.append(owner)
        seen.add(owner.pk)

    if not recipients:
        logger.info("mel.activity.delayed no recipients pk=%s", activity.pk)
        return

    message = (
        f"Activity '{activity.name}' is delayed — scheduled end {activity.scheduled_end} "
        f"has passed."
    )
    for user in recipients:
        send_notification(user, message, verb=Notification.Verb.MEL_ACTIVITY_DELAYED)
