"""Notifying members when a news item goes live.

Scheduled from management.views.NewsPublishView with an ETA matching the
item's own published_at (immediate publishes just get an ETA in the past,
which Celery runs right away -- no separate immediate/scheduled branch
needed). Not on CELERY_BEAT_SCHEDULE -- this fires once, on demand, per
publish, not on a recurring schedule, so it's not in features.jobs.JOB_REGISTRY
either (see features/signals.py: only registered jobs show up on the control
panel's Jobs tab).
"""

from celery import shared_task
from django.utils.html import strip_tags

from club.models import ClubMembership
from club.services.access import current_season
from members.models import Member
from notifications.services import notify_members, recipient_emails

from .models import News
from .services import render_body_html


def _notify_audience(news_item):
    """Every current-season, active member linked to this news item -- via its
    teams if it has any, or every active member if it's club-wide. Guardians
    are never part of this set themselves (notifications.services.recipient_emails
    resolves them per member); a guardian ClubMembership has kind=GUARDIAN and
    is excluded the same way teams.services.eligible_roster_members excludes
    it from a roster."""
    season = current_season(news_item.club)
    if season is None:
        return Member.objects.none()

    members = Member.objects.filter(
        member_of__club=news_item.club,
        member_of__season=season,
        member_of__status=ClubMembership.StatusChoices.ACTIVE,
        member_of__kind=ClubMembership.Kind.MEMBER,
    )
    if news_item.teams.exists():
        members = members.filter(team_memberships__team__in=news_item.teams.all(), team_memberships__season=season)
    return members.distinct()


def _dedupe_by_recipients(members):
    """Collapses siblings (or anyone else sharing a guardian) down to one
    notification each, keyed on where the email would actually land -- not
    family membership itself, since a blended family's kids don't
    necessarily share the exact same guardian set, only some overlap. A
    parent of three kids all on the news audience gets one email, not three;
    each kid still gets their own row (and read state) via events.tasks.
    notify_new_event, which is deliberately untouched by this -- an event
    needs a reply per child, a news post doesn't. Members nobody's reachable
    for (no login, no guardian) are never deduped against anything -- there's
    no shared inbox to spare."""
    seen_emails = set()
    representatives = []
    for member in members:
        emails = recipient_emails(member)
        if emails and any(email in seen_emails for email in emails):
            continue
        representatives.append(member)
        seen_emails.update(emails)
    return representatives


def _plain_text_body(news_item) -> str:
    """The notification's own plain-text body: rendered from the same
    Markdown source the public site would use, then stripped back to plain
    text. Notification.body is reused by every future notification source
    (see that model's own docstring), so it stores plain text, not this one
    source's particular markup."""
    return strip_tags(render_body_html(news_item.body))


@shared_task(name="news.tasks.notify_news_published")
def notify_news_published(news_id):
    news_item = News.objects.filter(pk=news_id, status=News.Status.PUBLISHED).select_related("club").prefetch_related("teams").first()
    if news_item is None:
        # Unpublished or deleted between scheduling this and the ETA firing --
        # nothing to notify about, and not an error worth a JobRun-style alert
        # (this task isn't registered for that anyway -- see the module docstring).
        return "Skipped: not published."

    members = _dedupe_by_recipients(_notify_audience(news_item))
    if not members:
        return "Skipped: no current-season active members to notify."

    notifications = notify_members(members, club=news_item.club, title=news_item.title, body=_plain_text_body(news_item), source=news_item)
    return f"Notified {len(notifications)} member(s)."
