"""SP2 cross-app receivers — respond to onboarding revoke signals
(FRSME-OBR029 cascade).

The REP course-completion → SME-Hub ``ProgressRecord`` receiver was removed with
the REP LMS; a Moodle-completion feed can repopulate progress via MEL later.

FRSME-INC036, FRSME-OBR029.
"""
from __future__ import annotations

import logging

from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.utils import timezone

from apps.smehub.incubation import signals as inc_signals
from apps.smehub.incubation.models import (
    Application,
    CohortMember,
    JudgeScore,
    ProgressRecord,
)
from apps.smehub.onboarding import signals as onb_signals

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# JudgeScore → cached Application.computed_score (FRSME-INC020 perf)
# ---------------------------------------------------------------------------


@receiver(post_save, sender=JudgeScore, dispatch_uid="smehub.incubation.judgescore_cache_save")
@receiver(post_delete, sender=JudgeScore, dispatch_uid="smehub.incubation.judgescore_cache_delete")
def _refresh_application_computed_score(sender, instance, **kwargs):
    """Keep ``Application.computed_score`` in sync whenever a judge score is
    saved or deleted. Best-effort: failures are logged, never propagated, so a
    bug in the cache update can never block scoring or rubric edits.
    """
    try:
        from apps.smehub.incubation.services import compute_weighted_average

        compute_weighted_average(instance.application)
    except Exception:
        logger.exception(
            "computed_score refresh failed for application %s",
            getattr(instance, "application_id", None),
        )


# ---------------------------------------------------------------------------
# OBR029 revoke cascade — SP1 business revocation withdraws active SP2 work
# ---------------------------------------------------------------------------

@receiver(onb_signals.smehub_business_revoked, dispatch_uid="smehub.incubation.revoke_cascade")
def _on_business_revoked(sender, business=None, by_user=None, reason: str = "", **kwargs):
    """When a verified business is revoked (FRSME-OBR029), withdraw every
    active SP2 application and cohort membership tied to it. The audit
    flagged that SP2 records survived a revoke, leaving the entrepreneur
    with stale links into closed-down activity.

    Email per affected record so the entrepreneur understands why each
    item disappeared.
    """
    if business is None:
        return

    from apps.core.notifications.models import Notification
    from apps.smehub._shared.notifications import notify_smehub

    Verb = Notification.Verb
    actor = by_user

    open_apps = Application.objects.filter(
        business=business,
        status__in=[
            Application.Status.DRAFT,
            Application.Status.SUBMITTED,
            Application.Status.UNDER_REVIEW,
            Application.Status.RETURNED_FOR_INFO,
        ],
    ).select_related("entrepreneur__user", "programme")
    for app in open_apps:
        try:
            app.withdraw()
            app.save(update_fields=["status"])
        except Exception:
            logger.exception("SP2 cascade withdraw failed for application %s", app.pk)
            continue
        try:
            notify_smehub(
                app.entrepreneur.user,
                message=(
                    f"Your application {app.application_id} to "
                    f"\"{app.programme.title}\" was withdrawn because the "
                    f"underlying business profile was revoked."
                    + (f" Reason: {reason}" if reason else "")
                ),
                verb=Verb.SMEHUB_APPLICATION_RETURNED,
                target=app,
            )
        except Exception:
            logger.exception("Cascade notification failed for application %s", app.pk)

    open_members = CohortMember.objects.filter(
        business=business, status=CohortMember.Status.ACTIVE,
    ).select_related("entrepreneur__user", "cohort__programme")
    for member in open_members:
        try:
            member.withdraw(reason=f"Business revoked. {reason}".strip())
            member.save(update_fields=["status", "withdrawn_reason"])
        except Exception:
            logger.exception("SP2 cascade withdraw failed for cohort member %s", member.pk)
            continue
        try:
            notify_smehub(
                member.entrepreneur.user,
                message=(
                    f"You have been withdrawn from cohort "
                    f"\"{member.cohort.programme.title}\" because the "
                    f"underlying business profile was revoked."
                ),
                verb=Verb.SMEHUB_COHORT_REJECTED,
                target=member,
            )
        except Exception:
            logger.exception("Cascade notification failed for cohort member %s", member.pk)
