"""SME-Hub beneficiary survey dispatch (FRSME-MEI017 → MEI019).

Each milestone-driven receiver in :mod:`apps.mel.tracking.smehub_receivers`
calls one of the ``dispatch_*`` helpers below to:

  • create a per-beneficiary :class:`apps.mel.feedback.models.FeedbackChannel`
    if it doesn't exist yet,
  • notify the entrepreneur with a tokenised public link,
  • record a :class:`apps.mel.tracking.models.SMEHubBeneficiaryFeedback` row
    so M&EL can roll up survey completion rates.

Idempotent: every helper guards on ``(tracking_record, stage, trigger_event)``
so replays of the same upstream signal don't enqueue duplicate surveys.
"""
from __future__ import annotations

import logging
from datetime import timedelta

from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.urls import NoReverseMatch, reverse
from django.utils import timezone

logger = logging.getLogger(__name__)


def _response_window_days() -> int:
    """FRSME-MEI019 — configurable response window."""
    return int(getattr(settings, "SMEHUB_FEEDBACK_RESPONSE_WINDOW_DAYS", 14))


def _reminder_after_days() -> int:
    """Days after dispatch before sending the first reminder (must be < window)."""
    return int(getattr(settings, "SMEHUB_FEEDBACK_REMINDER_AFTER_DAYS", 7))


def _beneficiary_label(record) -> str:
    """Public-safe label for a feedback channel title.

    FeedbackChannel names surface on the anonymous submit page, so never leak
    the entrepreneur's ``__str__`` (which exposes their email). Prefer the
    business name; fall back to a neutral label.
    """
    business = getattr(record, "business", None)
    name = getattr(business, "name", "") if business else ""
    return name.strip() or "your enterprise"


# ---------------------------------------------------------------------------
# Channel creation + notification
# ---------------------------------------------------------------------------


def _ensure_channel(*, tracking_record, stage: str, subject_obj, channel_name: str):
    """Get-or-create a per-beneficiary FeedbackChannel scoped to the trigger."""
    from apps.mel.feedback.models import FeedbackChannel, FeedbackChannelType

    subject_ct = ContentType.objects.get_for_model(subject_obj.__class__)
    slug = f"smehub-{stage}-record-{tracking_record.pk}-{subject_obj.pk}"
    channel, _ = FeedbackChannel.objects.get_or_create(
        slug=slug,
        defaults={
            "type": FeedbackChannelType.BENEFICIARY,
            "name": channel_name,
            "subject_type": subject_ct,
            "subject_id": subject_obj.pk,
            "token": FeedbackChannel.generate_token(),
            "is_public": True,
        },
    )
    return channel


def _notify_entrepreneur(*, tracking_record, channel, prompt: str) -> None:
    """Notify the entrepreneur with a public feedback link."""
    from apps.core.notifications.models import Notification
    from apps.core.notifications.services import send_notification

    user = getattr(tracking_record.entrepreneur, "user", None)
    if user is None:
        return
    try:
        link = reverse("mel_feedback:public_submit", kwargs={"token": channel.token})
    except NoReverseMatch:  # pragma: no cover — defensive
        link = ""
    message = f"{prompt} {link}".strip()
    send_notification(
        user,
        message,
        verb=Notification.Verb.SMEHUB_FEEDBACK_SURVEY,
        email_context={"feedback_link": link, "prompt": prompt},
    )


def _record_dispatch(*, tracking_record, stage: str, trigger_event: str, channel) -> None:
    """Persist the SMEHubBeneficiaryFeedback row — idempotent on the trigger."""
    from apps.mel.tracking.models import SMEHubBeneficiaryFeedback

    now = timezone.now()
    SMEHubBeneficiaryFeedback.objects.get_or_create(
        tracking_record=tracking_record,
        stage=stage,
        trigger_event=trigger_event,
        defaults={
            "feedback_channel": channel,
            "dispatched_at": now,
            "due_at": now + timedelta(days=_response_window_days()),
            "status": SMEHubBeneficiaryFeedback.Status.SENT,
        },
    )


# ---------------------------------------------------------------------------
# Stage-specific dispatchers
# ---------------------------------------------------------------------------


def dispatch_post_incubation_survey(*, record, member) -> None:
    """FRSME-MEI017 — sent after a cohort member completes the programme."""
    if record is None or member is None:
        return
    from apps.mel.tracking.models import SMEHubBeneficiaryFeedback

    trigger = f"cohort_member_completed:{member.pk}"
    if SMEHubBeneficiaryFeedback.objects.filter(
        tracking_record=record,
        stage=SMEHubBeneficiaryFeedback.Stage.POST_INCUBATION,
        trigger_event=trigger,
    ).exists():
        return
    channel = _ensure_channel(
        tracking_record=record,
        stage="post-incubation",
        subject_obj=member,
        channel_name=f"Post-incubation feedback — {_beneficiary_label(record)}",
    )
    _record_dispatch(
        tracking_record=record,
        stage=SMEHubBeneficiaryFeedback.Stage.POST_INCUBATION,
        trigger_event=trigger,
        channel=channel,
    )
    _notify_entrepreneur(
        tracking_record=record,
        channel=channel,
        prompt=(
            "You completed your incubation programme — please share a 2-minute "
            "reflection so we can keep improving:"
        ),
    )


def dispatch_post_connection_survey(*, record, connection=None, request=None) -> None:
    """FRSME-MEI018 — sent after a connection request is accepted."""
    target = connection or request
    if record is None or target is None:
        return
    from apps.mel.tracking.models import SMEHubBeneficiaryFeedback

    trigger = f"connection_accepted:{target.pk}"
    if SMEHubBeneficiaryFeedback.objects.filter(
        tracking_record=record,
        stage=SMEHubBeneficiaryFeedback.Stage.POST_CONNECTION,
        trigger_event=trigger,
    ).exists():
        return
    channel = _ensure_channel(
        tracking_record=record,
        stage="post-connection",
        subject_obj=target,
        channel_name=f"Post-connection feedback — {_beneficiary_label(record)}",
    )
    _record_dispatch(
        tracking_record=record,
        stage=SMEHubBeneficiaryFeedback.Stage.POST_CONNECTION,
        trigger_event=trigger,
        channel=channel,
    )
    _notify_entrepreneur(
        tracking_record=record,
        channel=channel,
        prompt=(
            "Your connection was accepted — let us know how it's going so we can "
            "support more matches:"
        ),
    )


def dispatch_post_deal_survey(*, record, deal_room, agreement=None) -> None:
    """FRSME-MEI019 — sent after a deal-room agreement is formalised."""
    if record is None or deal_room is None:
        return
    from apps.mel.tracking.models import SMEHubBeneficiaryFeedback

    trigger = f"agreement_formalised:{deal_room.pk}"
    if SMEHubBeneficiaryFeedback.objects.filter(
        tracking_record=record,
        stage=SMEHubBeneficiaryFeedback.Stage.POST_DEAL,
        trigger_event=trigger,
    ).exists():
        return
    channel = _ensure_channel(
        tracking_record=record,
        stage="post-deal",
        subject_obj=deal_room,
        channel_name=f"Post-deal feedback — {_beneficiary_label(record)}",
    )
    _record_dispatch(
        tracking_record=record,
        stage=SMEHubBeneficiaryFeedback.Stage.POST_DEAL,
        trigger_event=trigger,
        channel=channel,
    )
    _notify_entrepreneur(
        tracking_record=record,
        channel=channel,
        prompt=(
            "Congratulations on closing the deal — please tell us about the journey "
            "so we can sharpen the deal-room playbook:"
        ),
    )


# ---------------------------------------------------------------------------
# Configurable Survey trigger dispatch  (FRSME-MEI017 → MEI019)
# ---------------------------------------------------------------------------


def dispatch_triggered_surveys(*, trigger: str, record, trigger_event: str) -> None:
    """Dispatch every ACTIVE officer-authored :class:`Survey` whose ``trigger``
    matches this SME-Hub event, to the entrepreneur on ``record``.

    This runs *alongside* the built-in :class:`SMEHubBeneficiaryFeedback`
    dispatch — the two are independent (Surveys are configurable questionnaires
    authored in the survey builder; beneficiary feedback is the fixed reflection
    prompt). ``dispatch_survey`` is idempotent on
    ``(survey, recipient, trigger_event)`` so replays of the same upstream
    occurrence never double-dispatch. Best-effort: never raises into the caller.
    """
    if record is None:
        return
    user = getattr(getattr(record, "entrepreneur", None), "user", None)
    if user is None:
        return
    try:
        from apps.mel.feedback.models import Survey, SurveyStatus
        from apps.mel.feedback.services import dispatch_survey

        surveys = list(
            Survey.objects.filter(status=SurveyStatus.ACTIVE, trigger=trigger)
        )
    except Exception as exc:  # noqa: BLE001 — defensive: never break the receiver
        logger.warning("triggered-survey lookup failed trigger=%s: %s", trigger, exc)
        return

    for survey in surveys:
        try:
            dispatch_survey(survey, recipients=[user], trigger_event=trigger_event)
        except Exception as exc:  # noqa: BLE001
            logger.warning(
                "triggered-survey dispatch failed survey=%s trigger_event=%s: %s",
                survey.pk, trigger_event, exc,
            )


# ---------------------------------------------------------------------------
# Submission → completion linkage  (FRSME-MEI018)
# ---------------------------------------------------------------------------


def mark_completed_for_submission(submission) -> None:
    """When a FeedbackSubmission lands on a beneficiary channel, flip the
    matching :class:`SMEHubBeneficiaryFeedback` rows to COMPLETED so M&EL
    reports can roll up survey response rates (FRSME-MEI018).

    A channel can only be linked to one beneficiary feedback row in
    practice (the slug is per ``stage + tracking_record + subject``), but
    the queryset form means replays are still safe.
    """
    if submission is None or submission.channel_id is None:
        return
    from apps.mel.tracking.models import SMEHubBeneficiaryFeedback

    rows = SMEHubBeneficiaryFeedback.objects.filter(
        feedback_channel_id=submission.channel_id,
        status=SMEHubBeneficiaryFeedback.Status.SENT,
    )
    rows.update(
        status=SMEHubBeneficiaryFeedback.Status.COMPLETED,
        completed_at=timezone.now(),
        feedback_submission=submission,
    )
