"""Signal receivers for the Repository documents app.

Loaded by ``apps.repository.documents.apps.DocumentsConfig.ready`` so the
wiring is in place as soon as Django finishes app loading.
"""

from __future__ import annotations

import logging

from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver

from apps.core.notifications.models import Notification
from apps.core.notifications.services import send_notification
from apps.repository.documents import signals as repo_signals
from apps.repository.documents.models import Document, DocumentBookmark, IntegrationOutbox

logger = logging.getLogger(__name__)


@receiver(pre_save, sender=Document)
def cache_previous_status(sender, instance: Document, **kwargs):
    if not instance.pk:
        instance._repo_prev_status = None
        return
    try:
        prev = Document.objects.only("status").get(pk=instance.pk)
        instance._repo_prev_status = prev.status
    except Document.DoesNotExist:
        instance._repo_prev_status = None


@receiver(post_save, sender=Document)
def reindex_search_on_publish(sender, instance: Document, created, **kwargs):
    """When a document moves into PUBLISHED, refresh its search vector."""
    prev = getattr(instance, "_repo_prev_status", None)
    if instance.status == Document.Status.PUBLISHED and prev != Document.Status.PUBLISHED:
        from apps.repository.documents.tasks import index_document_for_search

        index_document_for_search.delay(instance.pk)


# ── custom signal receivers ─────────────────────────────────────────────────


@receiver(repo_signals.document_qa_assigned)
def notify_qa_reviewer(sender, document: Document, qa_record, **kwargs):
    if not qa_record.reviewer_id:
        return
    send_notification(
        qa_record.reviewer,
        f'A document is awaiting your QA review: "{document.title[:80]}".',
        verb=Notification.Verb.REPO_QA_ASSIGNED,
    )


@receiver(repo_signals.document_published)
def on_document_published(sender, document: Document, **kwargs):
    """Push to MEL outbox + queue embedding for RAG (analytics consumes the same signal)."""
    # FRREP-QA011 — the M&EL feed needs each author's institution (not just
    # name/ORCID) to report on institutional reach; omitting it forces MEL to
    # do its own author lookups.
    authors = [
        {
            "full_name": a.full_name,
            "orcid": a.orcid or "",
            "institution": a.institution.name if a.institution_id else "",
        }
        for a in document.authors.all()
    ]
    payload = {
        "document_id": document.pk,
        "public_document_id": document.public_document_id,
        "title": document.title,
        "collection": document.collection.slug,
        "document_type": document.document_type,
        "license_type": document.license_type,
        "open_access": document.is_open_access,
        "grant_reference": document.grant_reference,
        "published_at": document.published_at.isoformat() if document.published_at else None,
        "authors": authors,
    }
    IntegrationOutbox.objects.create(
        target=IntegrationOutbox.Target.MEL,
        event_type="document_published",
        payload=payload,
    )


@receiver(repo_signals.document_withdrawn)
def on_document_withdrawn(sender, document: Document, reason: str = "", **kwargs):
    """Notify REP that any OER link to this document is now broken."""
    payload = {
        "document_id": document.pk,
        "public_document_id": document.public_document_id,
        "document_uid": str(document.document_uid),
        "reason": reason,
    }
    IntegrationOutbox.objects.create(
        target=IntegrationOutbox.Target.REP,
        event_type="document_withdrawn",
        payload=payload,
    )
    if document.submitted_by_id:
        send_notification(
            document.submitted_by,
            f'Your document "{document.title[:80]}" has been withdrawn.',
            verb=Notification.Verb.REPO_DOC_WITHDRAWN,
        )
    # FRREP-VL007 — anyone who bookmarked this document also gets notified,
    # not just the submitter (excluded here to avoid double-notifying them).
    bookmarks = (
        DocumentBookmark.objects.filter(document=document)
        .exclude(user_id=document.submitted_by_id)
        .select_related("user")
    )
    for bookmark in bookmarks:
        send_notification(
            bookmark.user,
            f'A document you saved, "{document.title[:80]}", has been withdrawn.',
            verb=Notification.Verb.REPO_SAVED_DOC_WITHDRAWN,
        )


@receiver(repo_signals.document_reinstated)
def on_document_reinstated(sender, document: Document, **kwargs):
    """Notify REP that OER links to this document are no longer broken."""
    payload = {
        "document_id": document.pk,
        "public_document_id": document.public_document_id,
        "document_uid": str(document.document_uid),
    }
    IntegrationOutbox.objects.create(
        target=IntegrationOutbox.Target.REP,
        event_type="document_reinstated",
        payload=payload,
    )


@receiver(repo_signals.access_requested)
def on_access_requested(sender, document: Document, access_request, **kwargs):
    for manager in document.collection.managed_by.filter(is_active=True):
        send_notification(
            manager,
            f'{access_request.requester.get_full_name() or access_request.requester.email} requested access to "{document.title[:80]}".',
            verb=Notification.Verb.REPO_ACCESS_REQUESTED,
        )


@receiver(repo_signals.access_denied)
def on_access_denied(sender, document: Document, access_request, **kwargs):
    """FRREP-AC006 — a denial must reach the requester, mirroring the
    ``on_access_requested`` / grant-notification pattern (the grant path
    already notifies inline in ``services.decide_access_request``)."""
    send_notification(
        access_request.requester,
        f'Your access request for "{document.title[:80]}" was declined.',
        verb=Notification.Verb.REPO_ACCESS_DENIED,
    )


@receiver(repo_signals.version_added)
def on_version_added(sender, document: Document, version, **kwargs):
    from apps.repository.documents.tasks import (
        extract_text_from_file,
        index_document_for_search,
    )

    extract_text_from_file.delay(document.pk)
    index_document_for_search.delay(document.pk)
