from __future__ import annotations

from datetime import date

from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.db.models import BooleanField, Case, Q, Value, When
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.decorators.http import require_POST
from django.views.generic import DetailView, ListView, View

from apps.alumni.profiles.models import AlumniProfile
from apps.alumni.profiles.views import _user_is_staff
from apps.alumni.recognition.forms import (
    BroadcastForm,
    EndorsementForm,
    SpotlightPublishForm,
)
from apps.alumni.recognition.models import (
    Broadcast,
    BroadcastStatus,
    Endorsement,
    Spotlight,
    SpotlightCategory,
)
from apps.alumni.recognition.services import (
    current_spotlights,
    dispatch_broadcast,
    endorse,
    preview_recipients,
    publish_spotlight,
    top_endorsement_candidates,
)


def _current_period_label() -> str:
    return f"{date.today().year}"


class SpotlightListView(ListView):
    template_name = "alumni/recognition/spotlight_list.html"
    context_object_name = "spotlights"
    paginate_by = 12

    def get_queryset(self):
        today = date.today()
        return (
            Spotlight.objects.select_related("profile", "profile__user")
            .annotate(
                is_currently_featured=Case(
                    When(
                        Q(is_active=True)
                        & Q(featured_from__lte=today)
                        & Q(featured_until__gte=today),
                        then=Value(True),
                    ),
                    default=Value(False),
                    output_field=BooleanField(),
                )
            )
            .order_by("-featured_from", "-pk")
        )

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["active"] = list(current_spotlights())
        ctx["categories"] = SpotlightCategory.choices
        return ctx


class SpotlightDetailView(DetailView):
    model = Spotlight
    template_name = "alumni/recognition/spotlight_detail.html"
    context_object_name = "spotlight"

    def get_queryset(self):
        return Spotlight.objects.select_related("profile", "profile__user", "curated_by")

    def get_object(self, queryset=None):
        spotlight = super().get_object(queryset=queryset)
        # Respect the celebrated profile's privacy gate — staff and the owner can always see it.
        viewer = self.request.user
        if not spotlight.profile.visibility_consent:
            if not viewer.is_authenticated or (
                spotlight.profile.user_id != viewer.pk and not _user_is_staff(viewer)
            ):
                raise Http404("Spotlight subject is no longer publicly visible.")
        return spotlight


@login_required
@require_POST
def endorse_view(request, profile_pk: int) -> HttpResponse:
    profile = get_object_or_404(AlumniProfile, pk=profile_pk)
    if profile.user_id == request.user.pk:
        messages.error(request, "You cannot endorse yourself.")
        return redirect("alumni_profiles:profile_detail", pk=profile.pk)
    form = EndorsementForm(request.POST)
    if not form.is_valid():
        messages.error(request, "Endorsement form was invalid.")
        return redirect("alumni_profiles:profile_detail", pk=profile.pk)
    endorse(
        profile,
        request.user,
        category=form.cleaned_data["category"],
        period_label=form.cleaned_data["period_label"],
        citation=form.cleaned_data.get("citation", ""),
    )
    if getattr(request, "htmx", False):
        count = Endorsement.objects.filter(
            profile=profile,
            category=form.cleaned_data["category"],
            period_label=form.cleaned_data["period_label"],
        ).count()
        return render(
            request,
            "alumni/recognition/_endorsement_count.html",
            {"profile": profile, "count": count},
        )
    messages.success(request, "Endorsement recorded.")
    return redirect("alumni_profiles:profile_detail", pk=profile.pk)


class RecognitionCockpitView(LoginRequiredMixin, UserPassesTestMixin, ListView):
    """Officer cockpit — pick winners from endorsement counts and publish spotlights."""

    template_name = "alumni/recognition/cockpit.html"
    context_object_name = "active_spotlights"

    def test_func(self):
        return _user_is_staff(self.request.user)

    def get_queryset(self):
        return list(current_spotlights())

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        period = self.request.GET.get("period") or _current_period_label()
        ctx["period_label"] = period
        ctx["candidates_by_category"] = {
            label: top_endorsement_candidates(category=code, period_label=period, limit=10)
            for code, label in SpotlightCategory.choices
        }
        ctx["form"] = SpotlightPublishForm(initial={"period_label": period})
        ctx["categories"] = SpotlightCategory.choices
        ctx["selectable_profiles"] = (
            AlumniProfile.objects.filter(visibility_consent=True)
            .select_related("user")
            .order_by("user__last_name", "user__first_name", "user__email")
        )
        return ctx


@login_required
@require_POST
def publish_spotlight_view(request) -> HttpResponse:
    if not _user_is_staff(request.user):
        raise Http404("Not available.")
    form = SpotlightPublishForm(request.POST, request.FILES)
    if not form.is_valid():
        for msg in form.errors.values():
            messages.error(request, "; ".join(msg))
        return redirect("alumni_recognition:cockpit")
    profile = get_object_or_404(AlumniProfile, pk=form.cleaned_data["profile_pk"])
    nominated_by = None
    nominator_pk = form.cleaned_data.get("nominated_by_pk")
    if nominator_pk:
        nominated_by = get_user_model().objects.filter(pk=nominator_pk).first()
    publish_spotlight(
        profile,
        category=form.cleaned_data["category"],
        period_label=form.cleaned_data["period_label"],
        title=form.cleaned_data["title"],
        citation=form.cleaned_data["citation"],
        curator=request.user,
        featured_from=form.cleaned_data["featured_from"],
        featured_until=form.cleaned_data["featured_until"],
        cover_image=form.cleaned_data.get("cover_image"),
        nominated_by=nominated_by,
    )
    messages.success(request, f"Spotlight published for {profile.display_name}.")
    return redirect("alumni_recognition:cockpit")


class DigestPreviewView(LoginRequiredMixin, UserPassesTestMixin, ListView):
    template_name = "alumni/recognition/digest_preview.html"
    context_object_name = "spotlights"

    def test_func(self):
        return _user_is_staff(self.request.user)

    def get_queryset(self):
        return list(current_spotlights())


# ---------------------------------------------------------------------------
# Broadcast (officer-composed alumni-wide announcements)
# ---------------------------------------------------------------------------


class BroadcastListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
    template_name = "alumni/recognition/broadcast_list.html"
    context_object_name = "broadcasts"
    paginate_by = 25

    def test_func(self):
        return _user_is_staff(self.request.user)

    def get_queryset(self):
        return Broadcast.objects.select_related("created_by").order_by("-created_at")


class BroadcastComposeView(LoginRequiredMixin, UserPassesTestMixin, View):
    template_name = "alumni/recognition/broadcast_compose.html"

    def test_func(self):
        return _user_is_staff(self.request.user)

    def get(self, request, pk: int | None = None):
        broadcast = get_object_or_404(Broadcast, pk=pk) if pk else None
        if broadcast and broadcast.status == BroadcastStatus.SENT:
            messages.info(request, "This broadcast has already been sent.")
            return redirect("alumni_recognition:broadcast_list")
        form = BroadcastForm(instance=broadcast) if broadcast else BroadcastForm()
        return render(
            request,
            self.template_name,
            {"form": form, "broadcast": broadcast},
        )

    def post(self, request, pk: int | None = None):
        broadcast = get_object_or_404(Broadcast, pk=pk) if pk else None
        if broadcast and broadcast.status == BroadcastStatus.SENT:
            messages.info(request, "This broadcast has already been sent.")
            return redirect("alumni_recognition:broadcast_list")
        form = BroadcastForm(request.POST, instance=broadcast)
        if not form.is_valid():
            return render(
                request,
                self.template_name,
                {"form": form, "broadcast": broadcast},
            )
        instance: Broadcast = form.save(commit=False)
        instance.audience_filter = form.cleaned_data["audience_filter"]
        if not instance.pk:
            instance.created_by = request.user
        # Arm the beat sender: a future scheduled_for saves as SCHEDULED so
        # send_due_scheduled_broadcasts picks it up; otherwise it stays a DRAFT
        # awaiting a manual "Send now". (SENT/SENDING rows never reach here.)
        if instance.scheduled_for and instance.scheduled_for > timezone.now():
            instance.status = BroadcastStatus.SCHEDULED
        else:
            instance.status = BroadcastStatus.DRAFT
        instance.save()
        if instance.status == BroadcastStatus.SCHEDULED:
            messages.success(
                request,
                f"Broadcast scheduled for {instance.scheduled_for:%d %b %Y %H:%M}.",
            )
        else:
            messages.success(request, "Broadcast saved as a draft.")
        return redirect("alumni_recognition:broadcast_compose", pk=instance.pk)


@login_required
def broadcast_preview_view(request, pk: int) -> HttpResponse:
    """HTMX endpoint — return the live recipient count for a draft broadcast."""
    if not _user_is_staff(request.user):
        raise Http404("Not available.")
    broadcast = get_object_or_404(Broadcast, pk=pk)
    qs = preview_recipients(broadcast.audience, broadcast.audience_filter)
    count = qs.count()
    if getattr(request, "htmx", False):
        return render(
            request,
            "alumni/recognition/_broadcast_recipients.html",
            {"broadcast": broadcast, "recipient_count": count},
        )
    return render(
        request,
        "alumni/recognition/_broadcast_recipients.html",
        {"broadcast": broadcast, "recipient_count": count},
    )


@login_required
@require_POST
def broadcast_send_view(request, pk: int) -> HttpResponse:
    if not _user_is_staff(request.user):
        raise Http404("Not available.")
    broadcast = get_object_or_404(Broadcast, pk=pk)
    if broadcast.status == BroadcastStatus.SENT:
        messages.info(request, "Already sent.")
        return redirect("alumni_recognition:broadcast_list")
    try:
        dispatch_broadcast(broadcast)
    except Exception as exc:  # pragma: no cover - defensive
        messages.error(request, f"Broadcast failed: {exc}")
        return redirect("alumni_recognition:broadcast_compose", pk=broadcast.pk)
    messages.success(
        request,
        f"Broadcast sent to {broadcast.sent_count} recipient{'s' if broadcast.sent_count != 1 else ''}.",
    )
    return redirect("alumni_recognition:broadcast_list")
