from functools import wraps
from urllib.parse import quote

from django.contrib import messages
from django.db import transaction
from django.db.models import DecimalField, ExpressionWrapper, F, Prefetch, Sum
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone

from .forms import AddOrderItemForm, OrderStatusForm
from .models import Order, Product, Table, UserRole


LINE_TOTAL = ExpressionWrapper(
    F("items__quantity") * F("items__product__price"),
    output_field=DecimalField(max_digits=10, decimal_places=2),
)


def role_required(*allowed_roles):
    def decorator(view_func):
        @wraps(view_func)
        def wrapped(request, *args, **kwargs):
            if not request.user.is_authenticated:
                return redirect(f"/login/?next={quote(request.get_full_path())}")

            resolved_role = UserRole.resolve_for(request.user)
            if allowed_roles and resolved_role not in allowed_roles:
                messages.error(request, "Tu rol no tiene acceso a esta sección.")
                return redirect("home")
            return view_func(request, *args, **kwargs)

        return wrapped

    return decorator


def _dashboard_context():
    active_orders = Order.objects.exclude(status=Order.Status.PAID).prefetch_related("items__product").order_by("-created_at")
    tables = Table.objects.prefetch_related(
        Prefetch("orders", queryset=active_orders, to_attr="open_orders_cache")
    )
    recent_orders = Order.objects.select_related("table").prefetch_related("items__product")[:6]
    kitchen_orders = (
        Order.objects.filter(status__in=[Order.Status.OPEN, Order.Status.PREPARING])
        .select_related("table")
        .prefetch_related("items__product")[:5]
    )
    daily_revenue = (
        Order.objects.filter(status=Order.Status.PAID, paid_at__date=timezone.localdate())
        .aggregate(total=Sum(LINE_TOTAL))["total"]
        or 0
    )
    top_products = (
        Product.objects.filter(order_items__order__status=Order.Status.PAID)
        .annotate(sold=Sum("order_items__quantity"))
        .order_by("-sold", "name")[:4]
    )

    return {
        "tables": tables,
        "recent_orders": recent_orders,
        "kitchen_orders": kitchen_orders,
        "available_products": Product.objects.filter(is_available=True).count(),
        "occupied_tables": Table.objects.filter(status=Table.Status.OCCUPIED).count(),
        "daily_revenue": daily_revenue,
        "top_products": top_products,
        "meta_title": "Restaurante POS | Operación de mesas, cocina y cobro",
        "meta_description": "Dashboard táctil para controlar mesas, comandas, cocina y cobro desde una sola interfaz ligera.",
    }


@role_required(UserRole.Role.ADMIN, UserRole.Role.WAITER, UserRole.Role.KITCHEN)
def home(request):
    context = _dashboard_context()
    context["resolved_user_role"] = UserRole.label_for(request.user)
    return render(request, "core/index.html", context)


@role_required(UserRole.Role.ADMIN, UserRole.Role.WAITER)
def table_detail(request, table_id):
    table = get_object_or_404(
        Table.objects.prefetch_related(
            Prefetch(
                "orders",
                queryset=Order.objects.exclude(status=Order.Status.PAID).prefetch_related("items__product").order_by("-created_at"),
                to_attr="open_orders_cache",
            )
        ),
        pk=table_id,
    )
    current_order = table.current_order

    if request.method == "POST":
        form = AddOrderItemForm(request.POST)
        if form.is_valid():
            with transaction.atomic():
                if current_order is None:
                    current_order = Order.objects.create(table=table)
                item = form.save(commit=False)
                item.order = current_order
                item.save()
                if table.status != Table.Status.OCCUPIED:
                    table.status = Table.Status.OCCUPIED
                    table.save(update_fields=["status", "updated_at"])
            messages.success(request, f"Se agregó {item.product.name} a {table.name}.")
            return redirect("table_detail", table_id=table.pk)
    else:
        form = AddOrderItemForm()

    context = {
        "table": table,
        "current_order": current_order,
        "form": form,
        "meta_title": f"{table.name} | Comanda activa",
        "meta_description": f"Captura de comandas y notas para {table.name}.",
        "resolved_user_role": UserRole.label_for(request.user),
    }
    return render(request, "core/table_detail.html", context)


@role_required(UserRole.Role.ADMIN, UserRole.Role.WAITER, UserRole.Role.KITCHEN)
def order_detail(request, order_id):
    order = get_object_or_404(Order.objects.select_related("table").prefetch_related("items__product"), pk=order_id)

    if request.method == "POST":
        form = OrderStatusForm(request.POST, order=order)
        if form.is_valid():
            next_status = form.cleaned_data["status"]
            with transaction.atomic():
                order.advance_to(next_status)
            messages.success(request, f"La orden #{order.pk} ahora está {order.get_status_display().lower()}.")
            return redirect("order_detail", order_id=order.pk)
    status_forms = {
        status: OrderStatusForm(order=order, initial={"status": status})
        for status in order.allowed_transitions()
    }

    context = {
        "order": order,
        "status_forms": status_forms,
        "meta_title": f"Orden #{order.pk} | {order.table.name}",
        "meta_description": f"Detalle de la orden #{order.pk} con total, ítems y estado operativo.",
        "resolved_user_role": UserRole.label_for(request.user),
    }
    return render(request, "core/order_detail.html", context)


@role_required(UserRole.Role.ADMIN, UserRole.Role.KITCHEN)
def kitchen_board(request):
    orders = (
        Order.objects.filter(status__in=[Order.Status.OPEN, Order.Status.PREPARING])
        .select_related("table")
        .prefetch_related("items__product")
    )
    context = {
        "orders": orders,
        "meta_title": "KDS | Cocina pendiente",
        "meta_description": "Pantalla de cocina con órdenes pendientes de preparación.",
        "resolved_user_role": UserRole.label_for(request.user),
    }
    return render(request, "core/kitchen.html", context)
