from django import forms
from django.contrib import admin, messages
from django.contrib.admin.utils import unquote
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import Group

admin.site.unregister(Group)
from django.http import Http404, HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe

from django.db.models import Sum

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

User = get_user_model()


class ConsumptionInline(admin.TabularInline):
    model = Consumption
    extra = 0


class UserPaymentInline(admin.TabularInline):
    model = UserPayment
    extra = 0
    readonly_fields = ("created_at",)


class SetPinForm(forms.Form):
    pin = forms.CharField(
        label="Neue PIN (3 Ziffern)",
        min_length=3,
        max_length=3,
    )

    def clean_pin(self):
        pin = self.cleaned_data["pin"]
        if not pin.isdigit():
            raise forms.ValidationError("PIN muss aus genau 3 Ziffern bestehen.")
        return pin


@admin.register(User)
class CustomUserAdmin(UserAdmin):
    list_display = ("username", "is_staff", "is_superuser", "consumed_drinks_price", "paid_amount", "open_balance")
    ordering = ("username",)
    list_filter = ["is_staff", "is_superuser"]
    fieldsets = (
        (
            None,
            {
                "fields": (
                    "username",
                    "password",
                    "pin_status",
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "last_login",
                    "user_permissions",
                )
            },
        ),
        (
            "BILANZ",
            {
                "fields": (
                    "consumed_drinks_price",
                    "paid_amount",
                    "open_balance",
                    "drinks_breakdown",
                    "free_drinks_breakdown",
                )
            },
        ),
    )
    readonly_fields = (
        "consumed_drinks_price",
        "paid_amount",
        "open_balance",
        "pin_status",
        "last_login",
        "drinks_breakdown",
        "free_drinks_breakdown",
    )
    inlines = (UserPaymentInline, ConsumptionInline)

    @admin.display(description="Konsumiert")
    def consumed_drinks_price(self, user: User):
        return euro(user.consumed_drinks_price)

    @admin.display(description="Bezahlt")
    def paid_amount(self, user: User):
        return euro(user.paid_amount)

    @admin.display(description="Offener Betrag")
    def open_balance(self, user: User):
        return euro(user.open_balance)

    def _breakdown(self, user: User, for_free: bool):
        if user.pk is None:
            return "-"
        rows = (
            user.consumption_list.filter(for_free=for_free, drink__year=current_year())
            .values("drink__name")
            .annotate(amount=Sum("amount"))
            .order_by("drink__name")
        )
        if not rows:
            return "-"
        return ", ".join(f"{r['amount']}x {r['drink__name']}" for r in rows)

    @admin.display(description="Bezahlt")
    def drinks_breakdown(self, user: User):
        return self._breakdown(user, for_free=False)

    @admin.display(description="Gratis")
    def free_drinks_breakdown(self, user: User):
        return self._breakdown(user, for_free=True)

    @admin.display(description="PIN")
    def pin_status(self, user: User):
        status = "gesetzt" if user.pin else "nicht gesetzt"
        if user.pk is None:
            return status
        url = reverse("admin:gaehsnitz_user_set_pin", args=[user.pk])
        return format_html('{} &nbsp; <a href="{}">PIN setzen</a>', status, url)

    def get_urls(self):
        urls = super().get_urls()
        custom = [
            path(
                "<id>/pin/",
                self.admin_site.admin_view(self.set_pin_view),
                name="gaehsnitz_user_set_pin",
            ),
        ]
        return custom + urls

    def set_pin_view(self, request, id):
        if not self.has_change_permission(request):
            raise Http404
        user = self.get_object(request, unquote(id))
        if user is None:
            raise Http404

        if request.method == "POST":
            form = SetPinForm(request.POST)
            if form.is_valid():
                user.set_pin(form.cleaned_data["pin"])
                user.save(update_fields=["pin"])
                messages.success(request, f"PIN für {user.username} gesetzt.")
                return HttpResponseRedirect(reverse("admin:gaehsnitz_user_change", args=[user.pk]))
        else:
            form = SetPinForm()

        context = {
            **self.admin_site.each_context(request),
            "title": f"PIN setzen für {user.username}",
            "opts": self.model._meta,
            "original": user,
            "form": form,
        }
        return TemplateResponse(request, "admin/gaehsnitz/user/set_pin.html", context)


class YearFilter(admin.SimpleListFilter):
    title = "Jahr"
    parameter_name = "year"
    field_name = "created_at"

    def lookups(self, request, model_admin):
        years = model_admin.model.objects.dates(self.field_name, "year", order="DESC")
        return [(y.year, str(y.year)) for y in years]

    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(**{f"{self.field_name}__year": self.value()})
        return queryset


@admin.register(UserPayment)
class UserPaymentAdmin(admin.ModelAdmin):
    list_display = ("created_at", "user", "amount", "method", "note")
    list_filter = ("method", YearFilter)
    ordering = ("-created_at",)
    search_fields = ("user__username", "note")

    @admin.display(ordering="amount")
    def amount(self, payment: UserPayment):
        return euro(payment.amount)


@admin.register(Donation)
class DonationAdmin(admin.ModelAdmin):
    list_display = ("date", "amount", "note")
    list_filter = ("date",)
    ordering = ("-date",)
    search_fields = ("note",)

    @admin.display(ordering="amount")
    def amount(self, donation: Donation):
        return euro(donation.amount)


@admin.register(Payment)
class PaymentAdmin(admin.ModelAdmin):
    list_display = ("date", "purpose", "amount", "method")
    list_filter = ("method", "date")
    ordering = ("-date",)
    search_fields = ("purpose",)

    @admin.display(ordering="amount")
    def amount(self, payment: Payment):
        return euro(payment.amount)


@admin.register(Drink)
class DrinkAdmin(admin.ModelAdmin):
    list_display = (
        "name",
        "year",
        "crates_purchased",
        "bottles_sold",
        "bottles_remaining",
        "purchase_price_total",
        "balance",
    )
    list_filter = ("year",)
    fieldsets = (
        (None, {"fields": ("name", "year")}),
        (
            "Kästen",
            {
                "fields": (
                    "crates_ordered",
                    "crates_purchased",
                    "crates_full_returned",
                    "crates_returned",
                    "crates_remaining",
                )
            },
        ),
        (
            "Flaschen",
            {
                "fields": (
                    "bottles_per_crate",
                    "bottles_total",
                    "bottles_returned",
                    "bottles_sold",
                    "bottles_given_away",
                    "bottles_consumed",
                    "bottles_remaining",
                )
            },
        ),
        (
            "Menge",
            {
                "fields": (
                    "bottle_size",
                    "amount_per_crate",
                    "amount_total",
                )
            },
        ),
        (
            "Einkauf",
            {
                "fields": (
                    "purchase_price_per_crate",
                    "purchase_price_per_bottle",
                    "purchase_price_total",
                    "remaining_purchase_value",
                )
            },
        ),
        (
            "Pfand",
            {
                "fields": (
                    "deposit_per_crate",
                    "deposit_total",
                    "deposit_refund",
                    "deposit_kept",
                )
            },
        ),
        (
            "Verkauf",
            {
                "fields": (
                    "sale_price_per_bottle",
                    "sales_purchase_value",
                    "sale_price_total",
                    "giveaway_purchase_value",
                    "balance",
                )
            },
        ),
    )
    readonly_fields = (
        "bottles_total",
        "bottles_returned",
        "bottles_consumed",
        "bottles_remaining",
        "crates_full_returned",
        "crates_remaining",
        "amount_per_crate",
        "amount_total",
        "purchase_price_per_bottle",
        "purchase_price_total",
        "remaining_purchase_value",
        "deposit_total",
        "deposit_refund",
        "deposit_kept",
        "bottles_sold",
        "sales_purchase_value",
        "sale_price_total",
        "bottles_given_away",
        "giveaway_purchase_value",
        "balance",
    )

    @admin.display(description="Kästen voll zurück")
    def crates_full_returned(self, drink: Drink):
        return drink.crates_full_returned

    @admin.display(description="Kästen übrig")
    def crates_remaining(self, drink: Drink):
        return drink.crates_remaining

    @admin.display(description="Flaschen gesamt")
    def bottles_total(self, drink: Drink):
        return drink.bottles_total

    @admin.display(description="Flaschen leer zurück")
    def bottles_returned(self, drink: Drink):
        return drink.bottles_returned

    @admin.display(description="Flaschen verkauft")
    def bottles_sold(self, drink: Drink):
        return drink.bottles_sold

    @admin.display(description="Flaschen verschenkt")
    def bottles_given_away(self, drink: Drink):
        return drink.bottles_given_away

    @admin.display(description="Flaschen konsumiert")
    def bottles_consumed(self, drink: Drink):
        return drink.bottles_consumed

    @admin.display(description="Flaschen übrig")
    def bottles_remaining(self, drink: Drink):
        return drink.bottles_remaining

    @admin.display(description="Menge pro Kasten (l)")
    def amount_per_crate(self, drink: Drink):
        return drink.amount_per_crate

    @admin.display(description="Menge gesamt (l)")
    def amount_total(self, drink: Drink):
        return drink.amount_total

    @admin.display(description="Einkaufspreis pro Flasche")
    def purchase_price_per_bottle(self, drink: Drink):
        return euro(drink.purchase_price_per_bottle)

    @admin.display(description="Einkaufspreis gesamt")
    def purchase_price_total(self, drink: Drink):
        return euro(drink.purchase_price_total)

    @admin.display(description="Einkaufswert übrig")
    def remaining_purchase_value(self, drink: Drink):
        return euro(drink.remaining_purchase_value)

    @admin.display(description="Pfand gesamt")
    def deposit_total(self, drink: Drink):
        return euro(drink.deposit_total)

    @admin.display(description="Pfand zurück")
    def deposit_refund(self, drink: Drink):
        return euro(drink.deposit_refund)

    @admin.display(description="Pfand einbehalten")
    def deposit_kept(self, drink: Drink):
        return euro(drink.deposit_kept)

    @admin.display(description="Einkaufswert verkauft")
    def sales_purchase_value(self, drink: Drink):
        return euro(drink.sales_purchase_value)

    @admin.display(description="Verkaufserlös")
    def sale_price_total(self, drink: Drink):
        return euro(drink.sale_price_total)

    @admin.display(description="Einkaufswert verschenkt")
    def giveaway_purchase_value(self, drink: Drink):
        return euro(drink.giveaway_purchase_value)

    @admin.display(description="Bilanz")
    def balance(self, drink: Drink):
        return mark_safe(f"<b>{euro(drink.balance)}</b>")
