from datetime import datetime
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, 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()

BERLIN = ZoneInfo("Europe/Berlin")
# TEST WINDOW (2026-05-15 – 2026-05-31): enabled early for pre-festival testing.
# Original festival phase: BOOKING_START = 2026-06-11, BOOKING_END = 2026-06-14.
# Switch back to original dates before the festival.
BOOKING_START = datetime(2026, 5, 15, 0, 0, 0, tzinfo=BERLIN)
BOOKING_END = datetime(2026, 5, 31, 23, 59, 59, tzinfo=BERLIN)

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


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():
    weekday = _now().weekday()
    return DAY_BY_WEEKDAY.get(weekday, 4)


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


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": UserPayment.Method.choices,
    }


@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:
            error = "Bitte einen Namen eingeben."
        else:
            try:
                existing = User.objects.get(username=username)
            except User.DoesNotExist:
                existing = None

            if existing is None:
                request.session["pending_username"] = username
                request.session["pending_mode"] = "create"
                return HttpResponseRedirect(reverse("suff:pin"))

            if not existing.pin:
                return render(
                    request,
                    "suff/no_pin.html",
                    {"phase": phase, "username": username},
                )

            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"):
        return HttpResponseRedirect(reverse("suff:name"))

    error = None
    if request.method == "POST":
        pin = request.POST.get("pin", "")
        if not (pin.isdigit() and len(pin) == 3):
            error = "PIN muss aus genau 3 Ziffern bestehen."
        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"))
        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"])
def me_view(request):
    redirect, phase = _require_open(request)
    if redirect:
        return redirect
    drinks = (
        Drink.objects.filter(year=current_year())
        .annotate(
            category_order=Case(
                When(category="beer", then=Value(0)),
                When(category="alc_free_beer", then=Value(1)),
                When(category="radler", then=Value(2)),
                When(category="alc_free_radler", then=Value(3)),
                When(category="soft", then=Value(4)),
                When(category="water", then=Value(5)),
                default=Value(99),
                output_field=IntegerField(),
            )
        )
        .order_by("category_order", "name")
        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

    drink_id = request.POST.get("drink_id")
    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=False,
    )
    return HttpResponseRedirect(f"{reverse('suff:me')}?booked={drink.id}")


@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)
    context.update({"phase": phase, "error": error})
    return render(request, "suff/pay.html", context)


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


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

    total_donations = Donation.objects.filter(date__year=year).aggregate(s=Sum("amount"))["s"] or 0
    total_payments = Payment.objects.filter(date__year=year).aggregate(s=Sum("amount"))["s"] or 0
    total_balance = total_donations - total_payments
    total_pct = int(round((total_donations / total_payments) * 100)) if total_payments else 0
    total_pct_capped = min(total_pct, 100)

    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)
    drinks_profit = sum((d.balance for d in drinks), 0)
    refinance_pct = int(round((sales_revenue / purchase_cost) * 100)) if purchase_cost else 0
    refinance_pct_capped = min(refinance_pct, 100)

    user_rows = []
    for user in User.objects.all():
        consumed = user.consumed_drinks_price
        if not consumed:
            continue
        paid = user.paid_amount
        user_rows.append(
            {
                "username": user.username,
                "consumed": consumed,
                "paid": paid,
                "open": consumed - paid,
            }
        )
    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"]})

    context = {
        "year": year,
        "total_donations": total_donations,
        "total_payments": total_payments,
        "total_balance": total_balance,
        "total_pct": total_pct,
        "total_pct_capped": total_pct_capped,
        "drink_rows": drink_rows,
        "sales_revenue": sales_revenue,
        "purchase_cost": purchase_cost,
        "drinks_profit": drinks_profit,
        "refinance_pct": refinance_pct,
        "refinance_pct_capped": refinance_pct_capped,
        "user_rows": user_rows,
        "top_spender": top_spender,
        "top_drink": top_drink,
        "busiest_day": busiest_day,
        "top_per_day": top_per_day,
    }
    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})
