from datetime import timedelta

from django.contrib import admin, messages
from django.db.models import Sum
from django.utils import timezone
from unfold.admin import ModelAdmin, TabularInline
from unfold.contrib.filters.admin import RangeDateFilter, RangeDateTimeFilter
from unfold.decorators import display

from core.admin_ui import PortalAdminMixin
from core.status_palette import PAYMENT_STATUS_LABELS, RENEWAL_STATUS_LABELS
from notifications.models import NotificationTemplate
from notifications.workflows import create_notification_from_template
from .models import Payment, Renewal
from .workflows import mark_payment_as_paid, mark_renewal_as_renewed


class PaymentInline(TabularInline):
    model = Payment
    extra = 0
    fields = ("amount", "method", "status", "paid_at", "reference")
    tab = True


@admin.action(description="Marcar pagamentos selecionados como pagos")
def mark_payments_paid(modeladmin, request, queryset):
    count = 0
    for payment in queryset.select_related("renewal"):
        mark_payment_as_paid(payment)
        count += 1
    modeladmin.message_user(request, f"{count} pagamentos foram marcados como pagos.", messages.SUCCESS)


@admin.action(description="Concluir renovações pagas e atualizar próxima data")
def mark_renewed(modeladmin, request, queryset):
    count = blocked = 0
    for renewal in queryset.select_related("service"):
        try:
            mark_renewal_as_renewed(renewal)
            count += 1
        except ValueError as exc:
            blocked += 1
            modeladmin.message_user(request, f"{renewal}: {exc}", messages.WARNING)
    if count:
        modeladmin.message_user(request, f"{count} renovações foram concluídas e as próximas datas foram atualizadas.", messages.SUCCESS)
    if blocked:
        modeladmin.message_user(request, f"{blocked} renovações foram bloqueadas por validações de segurança.", messages.WARNING)


@admin.action(description="Criar pagamento pendente pelo valor em falta")
def create_pending_payments(modeladmin, request, queryset):
    from core.choices import PaymentMethod
    created = skipped = 0
    for renewal in queryset.select_related("service__client"):
        amount = renewal.outstanding_amount
        if amount <= 0 or renewal.status in (Renewal.Status.RENEWED, Renewal.Status.CANCELLED):
            skipped += 1
            continue
        if renewal.payments.exclude(status__in=(Payment.Status.CANCELLED, Payment.Status.REJECTED, Payment.Status.REFUNDED)).exists():
            skipped += 1
            continue
        Payment.objects.create(
            renewal=renewal,
            amount=amount,
            method=renewal.client.preferred_payment_method or PaymentMethod.BANK_TRANSFER,
            status=Payment.Status.PENDING,
            reference=f"REN-{renewal.pk}",
        )
        renewal.status = Renewal.Status.WAITING_PAYMENT
        renewal.save(update_fields=["status", "updated_at"])
        created += 1
    modeladmin.message_user(request, f"Criados {created} pagamentos pendentes; {skipped} renovações foram ignoradas.", messages.INFO)


@admin.action(description="Criar rascunho de aviso de 30 dias")
def create_30_day_draft(modeladmin, request, queryset):
    created = 0
    skipped = 0
    for renewal in queryset.select_related("service__client"):
        try:
            _, was_created = create_notification_from_template(renewal, NotificationTemplate.Type.RENEWAL_30)
            created += int(was_created)
            skipped += int(not was_created)
        except ValueError:
            skipped += 1
    modeladmin.message_user(request, f"Criados {created} rascunhos; {skipped} foram ignorados por duplicação ou falta de dados.", messages.INFO)


@admin.register(Renewal)
class RenewalAdmin(PortalAdminMixin, ModelAdmin):
    portal_icon = "event_repeat"
    portal_kicker = "FINANCEIRO"
    portal_description = "Agenda operacional das renovações, avisos, pagamentos recebidos e atualização da próxima validade."
    portal_tone = "violet"
    portal_stats = (
        {"label": "Renovações", "icon": "event_repeat", "tone": "primary", "caption": "Histórico total"},
        {"label": "Próximos 30 dias", "icon": "event_upcoming", "tone": "warning", "method": "count_upcoming", "caption": "A preparar"},
        {"label": "A aguardar pagamento", "icon": "pending_actions", "tone": "danger", "filters": {"status": Renewal.Status.WAITING_PAYMENT}, "caption": "Cobrança pendente"},
        {"label": "Valor em aberto", "icon": "account_balance_wallet", "tone": "coral", "method": "sum_outstanding", "caption": "Total por receber"},
    )
    portal_related_links = (
        {"label": "Pagamentos", "icon": "payments", "url_name": "admin:billing_payment_changelist"},
        {"label": "Notificações", "icon": "notifications", "url_name": "admin:notifications_notificationlog_changelist"},
        {"label": "Serviços", "icon": "inventory_2", "url_name": "admin:services_service_changelist"},
    )

    list_display = ("renewal_identity", "client_name", "due_date", "days", "show_status", "sale_amount", "paid", "outstanding")
    list_filter = ("status", "service__service_type", ("due_date", RangeDateFilter))
    search_fields = ("service__name", "service__client__name", "service__client__email", "notes")
    autocomplete_fields = ("service",)
    readonly_fields = ("public_id", "client_name", "days", "paid", "outstanding", "created_at", "updated_at")
    inlines = (PaymentInline,)
    actions = (create_30_day_draft, create_pending_payments, mark_renewed)
    list_filter_submit = True
    list_fullwidth = True

    fieldsets = (
        ("Renovação", {"fields": ("service", "client_name", "due_date", "status", "days"), "description": "Serviço, cliente e situação operacional da renovação."}),
        ("Período", {"fields": ("period_start", "period_end", "renewed_until"), "description": "Período faturado e nova data após a renovação."}),
        ("Valores", {"fields": ("cost_amount", "sale_amount", "paid", "outstanding", "paid_at"), "description": "Custos, cobrança e montantes efetivamente recebidos."}),
        ("Avisos", {"fields": ("notice_30_sent_at", "notice_15_sent_at", "notice_7_sent_at", "due_notice_sent_at", "overdue_notice_sent_at"), "description": "Registo cronológico das comunicações enviadas."}),
        ("Notas", {"fields": ("notes",)}),
        ("Sistema", {"fields": ("public_id", "created_at", "updated_at"), "classes": ("collapse",)}),
    )

    def count_upcoming(self, request, queryset):
        today = timezone.localdate()
        return queryset.filter(due_date__range=(today, today + timedelta(days=30))).exclude(status__in=(Renewal.Status.RENEWED, Renewal.Status.CANCELLED)).count()

    def sum_outstanding(self, request, queryset):
        total = sum((renewal.outstanding_amount for renewal in queryset.exclude(status__in=(Renewal.Status.RENEWED, Renewal.Status.CANCELLED))), start=0)
        return f"{total:.2f} €"

    @display(description="Renovação", header=True)
    def renewal_identity(self, obj):
        return obj.service.name, obj.service.get_service_type_display()

    @display(description="Cliente", ordering="service__client__name")
    def client_name(self, obj):
        return obj.client if obj and obj.service_id else "—"

    @display(description="Dias")
    def days(self, obj):
        return obj.days_until_due if obj and obj.due_date else "—"

    @display(description="Estado", label=RENEWAL_STATUS_LABELS, ordering="status")
    def show_status(self, obj):
        return obj.get_status_display()

    @display(description="Pago")
    def paid(self, obj):
        return f"{obj.amount_paid:.2f} €" if obj and obj.pk else "0.00 €"

    @display(description="Em falta")
    def outstanding(self, obj):
        if not obj:
            return "0.00 €"
        return f"{obj.outstanding_amount:.2f} €" if obj.pk else f"{obj.sale_amount:.2f} €"


@admin.register(Payment)
class PaymentAdmin(PortalAdminMixin, ModelAdmin):
    portal_icon = "payments"
    portal_kicker = "FINANCEIRO"
    portal_description = "Registe cobranças, comprovativos, pagamentos parciais e documentos associados a cada renovação."
    portal_tone = "green"
    portal_stats = (
        {"label": "Pagamentos", "icon": "payments", "tone": "primary", "caption": "Movimentos registados"},
        {"label": "Pendentes", "icon": "pending", "tone": "warning", "filters": {"status": Payment.Status.PENDING}, "caption": "A aguardar ação"},
        {"label": "Em validação", "icon": "fact_check", "tone": "violet", "filters": {"status": Payment.Status.VALIDATING}, "caption": "Comprovativo recebido"},
        {"label": "Recebido este mês", "icon": "savings", "tone": "success", "method": "sum_paid_month", "caption": "Pagamentos confirmados"},
    )
    portal_related_links = (
        {"label": "Renovações", "icon": "event_repeat", "url_name": "admin:billing_renewal_changelist"},
        {"label": "Documentos", "icon": "folder", "url_name": "admin:core_document_changelist"},
    )

    list_display = ("payment_identity", "renewal", "method", "show_status", "requested_at", "paid_at", "invoice_number")
    list_filter = ("status", "method", ("requested_at", RangeDateTimeFilter), ("paid_at", RangeDateTimeFilter))
    search_fields = ("renewal__service__client__name", "renewal__service__name", "reference", "invoice_number", "notes")
    autocomplete_fields = ("renewal",)
    readonly_fields = ("public_id", "client_name", "created_at", "updated_at")
    actions = (mark_payments_paid,)
    list_filter_submit = True
    list_fullwidth = True

    fieldsets = (
        ("Pagamento", {"fields": ("renewal", "client_name", "amount", "method", "status"), "description": "Movimento financeiro e renovação a que respeita."}),
        ("Datas e referências", {"fields": ("requested_at", "paid_at", "reference", "invoice_number"), "description": "Cronologia e referências usadas na reconciliação."}),
        ("Comprovativo", {"fields": ("proof",), "description": "Ficheiro privado sujeito às permissões do backoffice."}),
        ("Observações", {"fields": ("notes",)}),
        ("Sistema", {"fields": ("public_id", "created_at", "updated_at"), "classes": ("collapse",)}),
    )

    def sum_paid_month(self, request, queryset):
        today = timezone.localdate()
        total = queryset.filter(status=Payment.Status.PAID, paid_at__year=today.year, paid_at__month=today.month).aggregate(total=Sum("amount"))["total"] or 0
        return f"{total:.2f} €"

    def save_model(self, request, obj, form, change):
        super().save_model(request, obj, form, change)
        if obj.status == Payment.Status.PAID:
            mark_payment_as_paid(obj, obj.paid_at or timezone.now())

    @display(description="Pagamento", header=True)
    def payment_identity(self, obj):
        return f"{obj.amount:.2f} €", obj.client.name

    @display(description="Cliente")
    def client_name(self, obj):
        return obj.client if obj and obj.renewal_id else "—"

    @display(description="Estado", label=PAYMENT_STATUS_LABELS, ordering="status")
    def show_status(self, obj):
        return obj.get_status_display()
