from __future__ import annotations

from datetime import date, timedelta

import pytest
from django.contrib.auth import get_user_model
from django.utils import timezone

from apps.alumni.profiles.models import AlumniProfile
from apps.alumni.recognition.models import (
    Broadcast,
    BroadcastAudience,
    BroadcastStatus,
    Endorsement,
    Spotlight,
    SpotlightCategory,
)
from apps.alumni.recognition.services import (
    current_spotlights,
    endorse,
    publish_spotlight,
    top_endorsement_candidates,
)

User = get_user_model()


@pytest.fixture
def alumni_profile(db, alumni_user):
    return AlumniProfile.objects.create(user=alumni_user, visibility_consent=True)


@pytest.fixture
def other_user(db):
    return User.objects.create_user(email="other@example.com", password="pw")


@pytest.fixture
def third_user(db):
    return User.objects.create_user(email="third@example.com", password="pw")


@pytest.mark.django_db
def test_endorse_is_idempotent(alumni_profile, other_user):
    e1 = endorse(
        alumni_profile,
        other_user,
        category=SpotlightCategory.MENTOR_OF_MONTH,
        period_label="2026-04",
        citation="Helpful mentor",
    )
    e2 = endorse(
        alumni_profile,
        other_user,
        category=SpotlightCategory.MENTOR_OF_MONTH,
        period_label="2026-04",
        citation="Different citation",
    )
    assert e1.pk == e2.pk
    assert Endorsement.objects.count() == 1


@pytest.mark.django_db
def test_publish_spotlight_deactivates_prior_winner(alumni_profile, alumni_officer_user, other_user):
    second_user = User.objects.create_user(email="winner-two@example.com", password="pw")
    second_profile = AlumniProfile.objects.create(user=second_user, visibility_consent=True)

    today = date.today()
    s1 = publish_spotlight(
        alumni_profile,
        category=SpotlightCategory.PARTNER_OF_YEAR,
        period_label="2026",
        title="Inaugural winner",
        citation="First citation",
        curator=alumni_officer_user,
        featured_from=today,
        featured_until=today + timedelta(days=30),
    )
    assert s1.is_active is True

    s2 = publish_spotlight(
        second_profile,
        category=SpotlightCategory.PARTNER_OF_YEAR,
        period_label="2026",
        title="Replacement winner",
        citation="Second citation",
        curator=alumni_officer_user,
        featured_from=today,
        featured_until=today + timedelta(days=30),
    )

    s1.refresh_from_db()
    # Update_or_create on the same (category, period) replaces the row in place,
    # so there's only ever one Spotlight row per (category, period_label).
    assert Spotlight.objects.filter(
        category=SpotlightCategory.PARTNER_OF_YEAR, period_label="2026"
    ).count() == 1
    assert s2.profile_id == second_profile.pk
    assert s2.is_active is True


@pytest.mark.django_db
def test_top_endorsement_candidates_ranks_by_count(
    alumni_profile, other_user, third_user, alumni_officer_user
):
    second_user = User.objects.create_user(email="second@example.com", password="pw")
    second_profile = AlumniProfile.objects.create(user=second_user, visibility_consent=True)

    # alumni_profile gets 2 endorsements
    endorse(alumni_profile, other_user, category=SpotlightCategory.INNOVATOR, period_label="2026")
    endorse(alumni_profile, third_user, category=SpotlightCategory.INNOVATOR, period_label="2026")
    # second_profile gets 1
    endorse(second_profile, other_user, category=SpotlightCategory.INNOVATOR, period_label="2026")

    rows = top_endorsement_candidates(category=SpotlightCategory.INNOVATOR, period_label="2026")
    assert rows[0]["profile"].pk == alumni_profile.pk
    assert rows[0]["count"] == 2
    assert rows[1]["profile"].pk == second_profile.pk
    assert rows[1]["count"] == 1


@pytest.mark.django_db
def test_current_spotlights_only_returns_active_in_window(alumni_profile, alumni_officer_user):
    today = date.today()
    # Past
    publish_spotlight(
        alumni_profile,
        category=SpotlightCategory.NEWSLETTER,
        period_label="2025-Q1",
        title="Past",
        citation="...",
        curator=alumni_officer_user,
        featured_from=today - timedelta(days=60),
        featured_until=today - timedelta(days=30),
    )
    # Active
    publish_spotlight(
        alumni_profile,
        category=SpotlightCategory.MENTOR_OF_MONTH,
        period_label="2026-04",
        title="Now",
        citation="...",
        curator=alumni_officer_user,
        featured_from=today - timedelta(days=2),
        featured_until=today + timedelta(days=10),
    )
    active = list(current_spotlights())
    assert len(active) == 1
    assert active[0].title == "Now"


@pytest.mark.django_db
def test_publish_spotlight_writes_audit_and_fires_notification(
    alumni_profile, alumni_officer_user
):
    from apps.core.audit.models import AuditLog
    from apps.core.notifications.models import Notification

    today = date.today()
    spotlight = publish_spotlight(
        alumni_profile,
        category=SpotlightCategory.PARTNER_OF_YEAR,
        period_label="2026",
        title="Outstanding partner",
        citation="A citation that explains why",
        curator=alumni_officer_user,
        featured_from=today,
        featured_until=today + timedelta(days=365),
    )
    # Audit row written via on_commit -> persisted in test transactions when we exit the atomic block
    # (pytest-django wraps each test in a transaction; AuditLog rows are queued via transaction.on_commit
    # and only flush when the outer transaction commits. We assert the notification + spotlight here.)
    assert Spotlight.objects.filter(pk=spotlight.pk).exists()
    notif = Notification.objects.filter(
        recipient=alumni_profile.user,
        verb=Notification.Verb.ALUMNI_SPOTLIGHT_FEATURED,
    ).first()
    assert notif is not None


@pytest.mark.django_db
def test_send_spotlight_digest_only_notifies_consenting(
    alumni_profile, alumni_officer_user
):
    from apps.alumni.tasks import send_spotlight_digest
    from apps.core.notifications.models import Notification

    # Add a non-consenting profile to verify it doesn't get the digest.
    silent_user = User.objects.create_user(email="silent@example.com", password="pw")
    AlumniProfile.objects.create(user=silent_user, visibility_consent=False)

    today = date.today()
    publish_spotlight(
        alumni_profile,
        category=SpotlightCategory.PARTNER_OF_YEAR,
        period_label="2026",
        title="Outstanding partner",
        citation="A citation",
        curator=alumni_officer_user,
        featured_from=today,
        featured_until=today + timedelta(days=30),
    )
    result = send_spotlight_digest.run()
    assert result["spotlights"] == 1
    digests = Notification.objects.filter(verb=Notification.Verb.ALUMNI_SPOTLIGHT_DIGEST)
    recipients = set(digests.values_list("recipient_id", flat=True))
    assert alumni_profile.user.pk in recipients
    assert silent_user.pk not in recipients


@pytest.mark.django_db
def test_send_due_scheduled_broadcasts_delivers_past_due(
    alumni_profile, alumni_officer_user
):
    """A SCHEDULED broadcast whose scheduled_for is in the past becomes SENT and
    its opted-in audience is notified once the beat task runs."""
    from apps.alumni.tasks import send_due_scheduled_broadcasts
    from apps.core.notifications.models import Notification

    # A not-yet-due scheduled broadcast must be left untouched.
    future = Broadcast.objects.create(
        subject="Later",
        body_html="<p>Not yet</p>",
        audience=BroadcastAudience.ALL_OPTED_IN,
        status=BroadcastStatus.SCHEDULED,
        created_by=alumni_officer_user,
        scheduled_for=timezone.now() + timedelta(hours=2),
    )
    due = Broadcast.objects.create(
        subject="Newsletter — March",
        body_html="<p>Hello alumni</p>",
        audience=BroadcastAudience.ALL_OPTED_IN,
        status=BroadcastStatus.SCHEDULED,
        created_by=alumni_officer_user,
        scheduled_for=timezone.now() - timedelta(minutes=1),
    )

    result = send_due_scheduled_broadcasts.run()

    assert result == {"sent": 1, "failed": 0}
    due.refresh_from_db()
    assert due.status == BroadcastStatus.SENT
    assert due.sent_at is not None
    assert due.sent_count == 1
    future.refresh_from_db()
    assert future.status == BroadcastStatus.SCHEDULED

    # The opted-in recipient (alumni_profile.user) got the broadcast notification.
    notif = Notification.objects.filter(
        recipient=alumni_profile.user,
        verb=Notification.Verb.ALUMNI_BROADCAST,
    ).first()
    assert notif is not None
