import math
from datetime import datetime, timedelta
from decimal import Decimal
from zoneinfo import ZoneInfo

from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth import get_user_model
from django.db.models import Case, CharField, Count, F, IntegerField, Sum, Value, When
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify
from django.views.decorators.http import require_http_methods

from gaehsnitz.models import Consumption, Donation, Drink, Payment, UserPayment, current_year

User = get_user_model()

ANONYMOUS_USERNAME = "anonym"

BERLIN = ZoneInfo("Europe/Berlin")
# Festival window: 2026-05-30 10:00 – 2026-06-14 22:00 Berlin time.
BOOKING_START = datetime(2026, 5, 30, 10, 0, 0, tzinfo=BERLIN)
BOOKING_END = datetime(2026, 6, 17, 23, 59, 0, tzinfo=BERLIN)

DAY_BY_WEEKDAY = {3: 1, 4: 2, 5: 3, 6: 4}
DAY_CUTOFF_HOUR = 6


def _shifted_local(dt):
    """Local Berlin time minus 6h — so 05:59 belongs to previous calendar day."""
    return dt.astimezone(BERLIN) - timedelta(hours=DAY_CUTOFF_HOUR)


def _now():
    return timezone.now().astimezone(BERLIN)


def _phase():
    if not settings.PRODUCTION:
        return "booking"
    now = _now()
    if now < BOOKING_START:
        return "before"
    if now <= BOOKING_END:
        return "booking"
    return "closed"


def _require_open(request):
    """Redirect to closed page when tool is not in booking phase."""
    phase = _phase()
    if phase in ("before", "closed"):
        return HttpResponseRedirect(reverse("suff:closed")), None
    return None, phase


def _current_festival_day():
    # Day rolls at 06:00 Berlin: bookings before 06:00 count as previous day.
    weekday = _shifted_local(timezone.now()).weekday()
    return DAY_BY_WEEKDAY.get(weekday, 4)


def _normalize_name(raw):
    return slugify(raw or "")[:20]


def _username_error(username):
    if len(username) < 2:
        return "Name muss mindestens 2 Zeichen lang sein."
    return None


def _user_has_activity(user):
    return Consumption.objects.filter(user=user).exists() or UserPayment.objects.filter(user=user).exists()


def _is_anonymous(user):
    return user.username == ANONYMOUS_USERNAME


_WEAK_PINS = {"123", "234", "345", "456", "567", "678", "789", "987", "876", "765", "654", "543", "432", "321"} | {d * 3 for d in "0123456789"}


def _pin_error(pin):
    if not (pin.isdigit() and len(pin) == 3):
        return "PIN muss aus genau 3 Ziffern bestehen."
    if pin in _WEAK_PINS:
        return "Diese PIN ist zu einfach. Bitte eine andere wählen."
    return None


def _staff_required(view):
    def wrapped(request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseRedirect(reverse("suff:name"))
        if not request.user.is_staff:
            raise Http404
        return view(request, *args, **kwargs)

    return wrapped


def _drink_grid_qs():
    return (
        Drink.objects.filter(year=current_year())
        .annotate(
            alcohol_order=Case(
                When(category__in=["beer", "radler", "sekt"], then=Value(0)),
                default=Value(1),
                output_field=IntegerField(),
            ),
            alcohol_label=Case(
                When(category__in=["beer", "radler", "sekt"], then=Value("mit Alkohol")),
                default=Value("ohne Alkohol"),
                output_field=CharField(),
            ),
            category_order=Case(
                When(category="beer", then=Value(0)),
                When(category="radler", then=Value(1)),
                When(category="sekt", then=Value(2)),
                When(category="alc_free_beer", then=Value(3)),
                When(category="alc_free_radler", then=Value(4)),
                When(category="soft", then=Value(5)),
                When(category="water", then=Value(6)),
                default=Value(99),
                output_field=IntegerField(),
            ),
        )
        .order_by("alcohol_order", "category_order", "name")
    )


def _tab_context(user):
    year = current_year()
    consumption = user.consumption_list.filter(drink__year=year).select_related("drink").order_by("-created_at", "-id")
    total = (
        user.paid_drinks.filter(drink__year=year)
        .annotate(cost=F("amount") * F("drink__sale_price_per_bottle"))
        .aggregate(total=Sum("cost"))["total"]
        or 0
    )
    payments = user.user_payments.filter(created_at__year=year).order_by("-created_at")
    paid = payments.aggregate(sum=Sum("amount"))["sum"] or 0
    return {
        "tab_user": user,
        "consumption_list": consumption,
        "total": total,
        "paid": paid,
        "open_balance": total - paid,
        "user_payments": payments,
        "payment_methods": [(m, l) for m, l in UserPayment.Method.choices if m != "other"],
    }


@require_http_methods(["GET", "POST"])
def name_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    if request.user.is_authenticated:
        return HttpResponseRedirect(reverse("suff:me"))

    error = None
    if request.method == "POST":
        raw_name = request.POST.get("name", "")
        username = _normalize_name(raw_name)
        if not username or (error := _username_error(username)):
            error = error or "Bitte einen Namen eingeben."
        else:
            try:
                existing = User.objects.get(username=username)
            except User.DoesNotExist:
                existing = None

            if existing is None:
                return render(
                    request,
                    "suff/party_over.html",
                    {"phase": phase, "username": username},
                )

            if not existing.pin:
                if _user_has_activity(existing):
                    return render(
                        request,
                        "suff/no_pin.html",
                        {"phase": phase, "username": username},
                    )
                request.session["pending_username"] = username
                request.session["pending_mode"] = "claim"
                return HttpResponseRedirect(reverse("suff:pin"))

            request.session["pending_username"] = username
            request.session["pending_mode"] = "login"
            return HttpResponseRedirect(reverse("suff:pin"))

    return render(request, "suff/name.html", {"phase": phase, "error": error})


@require_http_methods(["GET", "POST"])
def pin_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    username = request.session.get("pending_username")
    mode = request.session.get("pending_mode")
    if not username or mode not in ("create", "login", "claim"):
        return HttpResponseRedirect(reverse("suff:name"))

    error = None
    if request.method == "POST":
        pin = request.POST.get("pin", "")
        if error := _pin_error(pin):
            pass
        elif mode == "create":
            if User.objects.filter(username=username).exists():
                error = "Name bereits vergeben."
                return HttpResponseRedirect(reverse("suff:name"))
            user = User(username=username)
            user.set_pin(pin)
            user.save()
            authed = authenticate(request, username=username, pin=pin)
            if authed is None:
                error = "Login nach Anlegen fehlgeschlagen."
            else:
                login(request, authed, backend="gaehsnitz.auth_backends.PinBackend")
                request.session.pop("pending_username", None)
                request.session.pop("pending_mode", None)
                return HttpResponseRedirect(reverse("suff:me"))
        elif mode == "claim":
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                return HttpResponseRedirect(reverse("suff:name"))
            if user.pin or _user_has_activity(user):
                return HttpResponseRedirect(reverse("suff:name"))
            user.set_pin(pin)
            user.save()
            authed = authenticate(request, username=username, pin=pin)
            if authed is None:
                error = "Login nach Anlegen fehlgeschlagen."
            else:
                login(request, authed, backend="gaehsnitz.auth_backends.PinBackend")
                request.session.pop("pending_username", None)
                request.session.pop("pending_mode", None)
                return HttpResponseRedirect(reverse("suff:me"))
        else:
            authed = authenticate(request, username=username, pin=pin)
            if authed is None:
                error = "Falsche PIN."
            else:
                login(request, authed, backend="gaehsnitz.auth_backends.PinBackend")
                request.session.pop("pending_username", None)
                request.session.pop("pending_mode", None)
                return HttpResponseRedirect(reverse("suff:me"))

    return render(
        request,
        "suff/pin.html",
        {
            "phase": phase,
            "error": error,
            "mode": mode,
            "username": username,
        },
    )


@login_required
@require_http_methods(["GET", "POST"])
def change_pin_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect

    error = None
    if request.method == "POST":
        pin = request.POST.get("pin", "")
        if error := _pin_error(pin):
            pass
        else:
            request.user.set_pin(pin)
            request.user.save()
            return HttpResponseRedirect(reverse("suff:me"))

    return render(request, "suff/change_pin.html", {"phase": phase, "error": error})


@login_required
@require_http_methods(["GET"])
def me_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    drinks = _drink_grid_qs() if phase == "booking" else Drink.objects.none()
    booked_drink = None
    booked_id = request.GET.get("booked")
    if booked_id:
        try:
            booked_drink = Drink.objects.get(pk=int(booked_id))
        except (Drink.DoesNotExist, TypeError, ValueError):
            booked_drink = None
    context = _tab_context(request.user)
    context.update(
        {
            "phase": phase,
            "drinks": drinks,
            "current_day": _current_festival_day(),
            "booked_drink": booked_drink,
            "paid_toast": request.GET.get("paid") == "1",
        }
    )
    return render(request, "suff/me.html", context)


@login_required
@require_http_methods(["POST"])
def book_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    if phase != "booking":
        raise Http404

    if _is_anonymous(request.user):
        raise Http404

    drink_id = request.POST.get("drink_id")
    booking_mode = request.POST.get("booking_mode", "normal")
    for_free = booking_mode == "for_free"
    cash_paid = booking_mode == "cash_paid"
    try:
        drink = Drink.objects.get(pk=int(drink_id))
    except (Drink.DoesNotExist, TypeError, ValueError):
        return HttpResponseRedirect(reverse("suff:me"))

    Consumption.objects.create(
        user=request.user,
        drink=drink,
        amount=1,
        day=_current_festival_day(),
        for_free=for_free,
    )
    if cash_paid:
        UserPayment.objects.create(
            user=request.user,
            amount=drink.sale_price_per_bottle,
            method=UserPayment.Method.cash,
            note=f"Auto: {drink.name}",
        )
    free_suffix = "&free=1" if for_free else ""
    return HttpResponseRedirect(f"{reverse('suff:me')}?booked={drink.id}{free_suffix}")


@login_required
@require_http_methods(["GET", "POST"])
def delete_consumption_view(request, consumption_id):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    if phase != "booking":
        raise Http404

    try:
        consumption = Consumption.objects.select_related("drink").get(pk=consumption_id, user=request.user)
    except Consumption.DoesNotExist:
        raise Http404

    if request.method == "POST":
        consumption.delete()
        return HttpResponseRedirect(reverse("suff:me"))

    return render(
        request,
        "suff/confirm_delete.html",
        {"phase": phase, "consumption": consumption},
    )


@login_required
@require_http_methods(["GET", "POST"])
def delete_payment_view(request, payment_id):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    if phase != "booking":
        raise Http404

    try:
        payment = UserPayment.objects.get(pk=payment_id, user=request.user)
    except UserPayment.DoesNotExist:
        raise Http404

    if request.method == "POST":
        payment.delete()
        return HttpResponseRedirect(reverse("suff:pay"))

    return render(
        request,
        "suff/confirm_delete_payment.html",
        {"phase": phase, "payment": payment},
    )


@login_required
@require_http_methods(["GET", "POST"])
def pay_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect

    error = None
    if request.method == "POST":
        raw_amount = (request.POST.get("amount") or "").replace(",", ".").strip()
        method = request.POST.get("method") or ""
        note = (request.POST.get("note") or "").strip()[:64]

        valid_methods = {m for m, _ in UserPayment.Method.choices}
        try:
            amount = float(raw_amount)
        except ValueError:
            amount = 0.0

        if method not in valid_methods:
            error = "Bitte eine Zahlungsmethode wählen."
        elif amount <= 0:
            error = "Bitte einen Betrag größer 0 eingeben."
        else:
            UserPayment.objects.create(
                user=request.user,
                amount=amount,
                method=method,
                note=note,
            )
            return HttpResponseRedirect(f"{reverse('suff:me')}?paid=1")

    context = _tab_context(request.user)
    open_balance = max(float(context["open_balance"]), 0.0)
    drinks_rounded = int(math.ceil(open_balance / 5.0) * 5) if open_balance > 0 else 0
    suggestions = [
        {"amount": drinks_rounded + d, "entry": round(drinks_rounded + d - open_balance, 2)}
        for d in (10, 15, 20, 25, 30)
    ]
    prefill_raw = request.GET.get("amount")
    try:
        prefill_amount = float(prefill_raw) if prefill_raw else None
        if prefill_amount is not None and prefill_amount <= 0:
            prefill_amount = None
    except ValueError:
        prefill_amount = None
    if prefill_amount is None:
        prefill_amount = float(suggestions[2]["amount"])
    prefill_value = f"{prefill_amount:.2f}"
    context.update(
        {
            "phase": phase,
            "error": error,
            "drinks_rounded": drinks_rounded,
            "suggestions": suggestions,
            "prefill_amount": prefill_amount,
            "prefill_value": prefill_value,
            "open_balance_url": f"{open_balance:.2f}",
        }
    )
    return render(request, "suff/pay.html", context)


def _get_staff_target(username):
    try:
        return User.objects.get(username=username)
    except User.DoesNotExist:
        raise Http404


@_staff_required
@require_http_methods(["GET", "POST"])
def staff_register_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect

    error = None
    prefill_name = ""
    prefill_pin = ""
    if request.method == "POST":
        raw_name = request.POST.get("name", "")
        username = _normalize_name(raw_name)
        pin = (request.POST.get("pin") or "").strip()
        prefill_name = raw_name
        prefill_pin = pin

        if not username:
            error = "Bitte einen Namen eingeben."
        elif error := _username_error(username):
            pass
        elif User.objects.filter(username=username).exists():
            error = "Name bereits vergeben."
        elif pin and (error := _pin_error(pin)):
            pass
        else:
            user = User(username=username)
            if pin:
                user.set_pin(pin)
            user.save()
            return HttpResponseRedirect(reverse("suff:staff_user", args=[username]))

    return render(
        request,
        "suff/staff_register.html",
        {
            "phase": phase,
            "error": error,
            "prefill_name": prefill_name,
            "prefill_pin": prefill_pin,
        },
    )


@_staff_required
@require_http_methods(["GET", "POST"])
def staff_pin_reset_view(request, username):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect

    target = _get_staff_target(username)
    if _is_anonymous(target):
        raise Http404

    error = None
    if request.method == "POST":
        pin = (request.POST.get("pin") or "").strip()
        if error := _pin_error(pin):
            pass
        else:
            target.set_pin(pin)
            target.save()
            return HttpResponseRedirect(reverse("suff:staff_user", args=[username]))

    return render(
        request,
        "suff/staff_pin_reset.html",
        {"phase": phase, "tab_user": target, "error": error},
    )


@_staff_required
@require_http_methods(["GET"])
def staff_index_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect

    anon = User.objects.filter(username=ANONYMOUS_USERNAME).first()
    others = User.objects.exclude(username=ANONYMOUS_USERNAME).order_by("username")
    return render(
        request,
        "suff/staff_index.html",
        {"phase": phase, "anonymous": anon, "users": others},
    )


@_staff_required
@require_http_methods(["GET"])
def staff_user_view(request, username):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect

    target = _get_staff_target(username)
    drinks = _drink_grid_qs() if phase == "booking" else Drink.objects.none()
    booked_drink = None
    booked_id = request.GET.get("booked")
    if booked_id:
        try:
            booked_drink = Drink.objects.get(pk=int(booked_id))
        except (Drink.DoesNotExist, TypeError, ValueError):
            booked_drink = None

    context = _tab_context(target)
    context.update(
        {
            "phase": phase,
            "drinks": drinks,
            "current_day": _current_festival_day(),
            "booked_drink": booked_drink,
            "booked_free": request.GET.get("free") == "1",
            "paid_toast": request.GET.get("paid") == "1",
            "is_anonymous_target": _is_anonymous(target),
        }
    )
    return render(request, "suff/staff_user.html", context)


@_staff_required
@require_http_methods(["POST"])
def staff_book_view(request, username):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    if phase != "booking":
        raise Http404

    target = _get_staff_target(username)
    drink_id = request.POST.get("drink_id")
    booking_mode = request.POST.get("booking_mode", "normal")
    for_free = booking_mode == "for_free"
    cash_paid = booking_mode == "cash_paid"
    try:
        drink = Drink.objects.get(pk=int(drink_id))
    except (Drink.DoesNotExist, TypeError, ValueError):
        return HttpResponseRedirect(reverse("suff:staff_user", args=[username]))

    Consumption.objects.create(
        user=target,
        drink=drink,
        amount=1,
        day=_current_festival_day(),
        for_free=for_free,
    )
    if cash_paid or (_is_anonymous(target) and not for_free):
        UserPayment.objects.create(
            user=target,
            amount=drink.sale_price_per_bottle,
            method=UserPayment.Method.cash,
            note=f"Auto: {drink.name}",
        )
    suffix = "&free=1" if for_free else ""
    return HttpResponseRedirect(f"{reverse('suff:staff_user', args=[username])}?booked={drink.id}{suffix}")


@_staff_required
@require_http_methods(["GET", "POST"])
def staff_pay_view(request, username):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect

    target = _get_staff_target(username)
    if _is_anonymous(target):
        raise Http404

    error = None
    if request.method == "POST":
        raw_amount = (request.POST.get("amount") or "").replace(",", ".").strip()
        method = request.POST.get("method") or ""
        note = (request.POST.get("note") or "").strip()[:64]

        valid_methods = {m for m, _ in UserPayment.Method.choices}
        try:
            amount = float(raw_amount)
        except ValueError:
            amount = 0.0

        if method not in valid_methods:
            error = "Bitte eine Zahlungsmethode wählen."
        elif amount <= 0:
            error = "Bitte einen Betrag größer 0 eingeben."
        else:
            UserPayment.objects.create(
                user=target,
                amount=amount,
                method=method,
                note=note,
            )
            return HttpResponseRedirect(f"{reverse('suff:staff_user', args=[username])}?paid=1")

    context = _tab_context(target)
    context.update({"phase": phase, "error": error, "is_anonymous_target": False})
    return render(request, "suff/staff_pay.html", context)


@_staff_required
@require_http_methods(["GET", "POST"])
def staff_delete_payment_view(request, username, payment_id):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    if phase != "booking":
        raise Http404

    target = _get_staff_target(username)
    try:
        payment = UserPayment.objects.get(pk=payment_id, user=target)
    except UserPayment.DoesNotExist:
        raise Http404

    if request.method == "POST":
        payment.delete()
        return HttpResponseRedirect(reverse("suff:staff_pay", args=[username]))

    return render(
        request,
        "suff/staff_confirm_delete_payment.html",
        {"phase": phase, "payment": payment, "tab_user": target},
    )


@_staff_required
@require_http_methods(["GET", "POST"])
def staff_delete_consumption_view(request, username, consumption_id):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    if phase != "booking":
        raise Http404

    target = _get_staff_target(username)
    try:
        consumption = Consumption.objects.select_related("drink").get(pk=consumption_id, user=target)
    except Consumption.DoesNotExist:
        raise Http404

    if request.method == "POST":
        if _is_anonymous(target):
            matching = (
                UserPayment.objects.filter(
                    user=target,
                    method=UserPayment.Method.cash,
                    amount=consumption.drink.sale_price_per_bottle,
                    note=f"Auto: {consumption.drink.name}",
                )
                .order_by("-created_at")
                .first()
            )
            if matching:
                matching.delete()
        consumption.delete()
        return HttpResponseRedirect(reverse("suff:staff_user", args=[username]))

    return render(
        request,
        "suff/staff_confirm_delete.html",
        {"phase": phase, "consumption": consumption, "tab_user": target},
    )


DAY_LABELS = {1: "Donnerstag", 2: "Freitag", 3: "Samstag", 4: "Sonntag"}


@staff_member_required
@require_http_methods(["GET"])
def dashboard_view(request):
    year = current_year()

    CASH_PREFILL = Decimal("500.00")

    total_donations = Donation.objects.filter(date__year=year).aggregate(s=Sum("amount"))["s"] or 0
    total_costs = Payment.objects.filter(date__year=year).aggregate(s=Sum("amount"))["s"] or 0
    cash_raw = UserPayment.objects.filter(created_at__year=year, method="cash").aggregate(s=Sum("amount"))["s"] or Decimal("0")
    cash_net = cash_raw - CASH_PREFILL
    non_cash = UserPayment.objects.filter(created_at__year=year).exclude(method="cash").aggregate(s=Sum("amount"))["s"] or Decimal("0")
    user_payments_total = cash_net + non_cash  # real income, prefill excluded

    drinks = list(Drink.objects.filter(year=year).order_by("name"))
    drink_rows = [
        {
            "name": d.name,
            "sold": d.bottles_sold,
            "total": d.bottles_total,
            "balance": d.balance,
        }
        for d in drinks
    ]
    sales_revenue = sum((d.sale_price_total for d in drinks), 0)
    purchase_cost = sum((d.purchase_price_total for d in drinks), 0)
    free_drinks_value = sum((d.giveaway_purchase_value for d in drinks), 0)
    unsold_purchase_value = sum((d.remaining_purchase_value for d in drinks), 0)
    unsold_sale_value = sum((d.bottles_remaining * d.sale_price_per_bottle for d in drinks), 0)

    income_total = total_donations + user_payments_total
    finance_balance = income_total - total_costs
    finance_pct = int(round((income_total / total_costs) * 100)) if total_costs else 0
    finance_pct_capped = min(finance_pct, 100)

    user_rows = []
    for user in User.objects.all():
        consumed = user.consumed_drinks_price
        if not consumed:
            continue
        paid = user.paid_amount
        open_amount = consumed - paid
        if open_amount <= 0:
            continue
        user_rows.append(
            {
                "username": user.username,
                "consumed": consumed,
                "paid": paid,
                "open": open_amount,
            }
        )
    user_rows.sort(key=lambda r: r["open"], reverse=True)

    top_spender = None
    if user_rows:
        top = max(user_rows, key=lambda r: r["consumed"])
        top_spender = {"username": top["username"], "total": top["consumed"]}

    drink_amounts = (
        Consumption.objects.filter(drink__year=year, for_free=False)
        .values("drink__name")
        .annotate(amount=Sum("amount"))
        .order_by("-amount")
    )
    top_drink = None
    if drink_amounts:
        top_drink = {"name": drink_amounts[0]["drink__name"], "amount": drink_amounts[0]["amount"]}

    day_amounts = (
        Consumption.objects.filter(drink__year=year, for_free=False)
        .values("day")
        .annotate(amount=Sum("amount"))
        .order_by("-amount")
    )
    busiest_day = None
    if day_amounts:
        d = day_amounts[0]
        busiest_day = {"label": DAY_LABELS.get(d["day"], "?"), "amount": d["amount"]}

    top_per_day = []
    for day_num, label in DAY_LABELS.items():
        rows = (
            Consumption.objects.filter(drink__year=year, day=day_num, for_free=False)
            .values("user__username")
            .annotate(amount=Sum("amount"))
            .order_by("-amount")
        )
        if rows:
            r = rows[0]
            top_per_day.append({"label": label, "username": r["user__username"], "amount": r["amount"]})

    cons_qs = Consumption.objects.filter(drink__year=year)
    paid_cons_qs = cons_qs.filter(for_free=False)

    # Time facts
    first_cons = cons_qs.exclude(created_at__isnull=True).order_by("created_at").select_related("user", "drink").first()
    last_cons = cons_qs.exclude(created_at__isnull=True).order_by("-created_at").select_related("user", "drink").first()

    # Day rolls at 06:00 Berlin: 05:59 counts as late "previous day", 07:00 = early "today".
    # Compute time-of-day relative to 06:00 cutoff.
    earliest_per_user = None
    latest_per_user = None
    earliest_key = None
    latest_key = None
    hour_buckets = {}
    for c in cons_qs.exclude(created_at__isnull=True).values("user__username", "created_at", "amount"):
        local = c["created_at"].astimezone(BERLIN)
        # minutes after 06:00 cutoff (0 = 06:00, 1439 = 05:59 next morning)
        shifted = (local.hour * 60 + local.minute - DAY_CUTOFF_HOUR * 60) % (24 * 60)
        if earliest_key is None or shifted < earliest_key:
            earliest_key = shifted
            earliest_per_user = {"user__username": c["user__username"], "t": c["created_at"]}
        if latest_key is None or shifted > latest_key:
            latest_key = shifted
            latest_per_user = {"user__username": c["user__username"], "t": c["created_at"]}
        hour_buckets[local.hour] = hour_buckets.get(local.hour, 0) + (c["amount"] or 0)

    golden_hour = None
    if hour_buckets:
        top_hour, top_amount = max(hour_buckets.items(), key=lambda kv: kv[1])
        golden_hour = {
            "label": f"{top_hour:02d} – {(top_hour + 1) % 24:02d} Uhr",
            "amount": top_amount,
        }

    # Drink mix / variety
    variety_rows = (
        paid_cons_qs.values("user__username")
        .annotate(distinct=Count("drink", distinct=True))
        .order_by("-distinct")
    )
    variety_champ = variety_rows[0] if variety_rows and variety_rows[0]["distinct"] > 1 else None

    cat_rows = (
        paid_cons_qs.values("drink__category")
        .annotate(amount=Sum("amount"))
    )
    cat_total = sum(r["amount"] for r in cat_rows) or 0
    beer_amount = sum(r["amount"] for r in cat_rows if r["drink__category"] == "beer")
    beer_share = int(round(beer_amount / cat_total * 100)) if cat_total else 0

    def _top_in_categories(cats, label):
        rows = (
            paid_cons_qs.filter(drink__category__in=cats)
            .values("user__username")
            .annotate(amount=Sum("amount"))
            .order_by("-amount")
        )
        if rows:
            return {"label": label, "username": rows[0]["user__username"], "amount": rows[0]["amount"]}
        return None

    alcfree_top = _top_in_categories(["alc_free_beer", "alc_free_radler"], "Alkoholfrei-Held")
    radler_top = _top_in_categories(["radler"], "Radler-Fan")
    water_top = _top_in_categories(["water"], "Wassertrinker")

    # Money / behavior
    overpay_rows = []
    for user in User.objects.all():
        consumed = user.consumed_drinks_price or 0
        paid = user.paid_amount or 0
        if paid > consumed and consumed > 0:
            overpay_rows.append({"username": user.username, "tip": paid - consumed})
    overpay_rows.sort(key=lambda r: r["tip"], reverse=True)
    biggest_tip = overpay_rows[0] if overpay_rows else None
    total_entry = sum(r["tip"] for r in overpay_rows)

    method_rows = (
        UserPayment.objects.filter(created_at__year=year)
        .values("method")
        .annotate(s=Sum("amount"))
    )
    method_split = {r["method"]: r["s"] for r in method_rows}
    cash_total = method_split.get(UserPayment.Method.cash, 0)

    # Free drinks
    free_recipient_rows = (
        cons_qs.filter(for_free=True)
        .values("user__username")
        .annotate(amount=Sum("amount"))
        .order_by("-amount")
    )
    top_free_recipient = free_recipient_rows[0] if free_recipient_rows else None
    free_total_count = cons_qs.filter(for_free=True).aggregate(s=Sum("amount"))["s"] or 0


    context = {
        "year": year,
        "total_donations": total_donations,
        "total_costs": total_costs,
        "user_payments_total": user_payments_total,
        "income_total": income_total,
        "finance_balance": finance_balance,
        "finance_pct": finance_pct,
        "finance_pct_capped": finance_pct_capped,
        "drink_rows": drink_rows,
        "sales_revenue": sales_revenue,
        "purchase_cost": purchase_cost,
        "free_drinks_value": free_drinks_value,
        "unsold_purchase_value": unsold_purchase_value,
        "unsold_sale_value": unsold_sale_value,
        "user_rows": user_rows,
        "top_spender": top_spender,
        "top_drink": top_drink,
        "busiest_day": busiest_day,
        "top_per_day": top_per_day,
        "first_cons": first_cons,
        "last_cons": last_cons,
        "earliest_per_user": earliest_per_user,
        "latest_per_user": latest_per_user,
        "golden_hour": golden_hour,
        "variety_champ": variety_champ,
        "beer_share": beer_share,
        "alcfree_top": alcfree_top,
        "radler_top": radler_top,
        "water_top": water_top,
        "biggest_tip": biggest_tip,
        "total_entry": total_entry,
        "method_split": method_split,
        "cash_total": cash_total,
        "cash_prefill": CASH_PREFILL,
        "cash_net": cash_net,
        "top_free_recipient": top_free_recipient,
        "free_total_count": free_total_count,
    }
    return render(request, "suff/dashboard.html", context)


@require_http_methods(["POST"])
def logout_view(request):
    logout(request)
    return HttpResponseRedirect(reverse("suff:name"))


@require_http_methods(["GET"])
def closed_view(request):
    phase = _phase()
    if phase == "booking":
        return HttpResponseRedirect(reverse("suff:name"))
    return render(request, "suff/closed.html", {"phase": phase, "booking_start": BOOKING_START})
