from django.contrib import messages
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, HttpResponseForbidden
from django.views.decorators.http import require_POST
from django.contrib.auth.decorators import login_required
from django.contrib.auth import get_user_model
from django.core.paginator import Paginator
from django.db.models import Sum, Count, Q
from django.utils import timezone
from datetime import timedelta
from .models import (
    Sede, Jornada, Venta, Producto, VariacionCatalogo, ProductoVariacion, Categoria, Gasto,
    Mesa, Pedido, HorarioLaboral, METODOS_PAGO, METODOS_QUE_RETIENE_MESERO,
)
from .permisos import es_admin, es_cajero, es_mesero, cajero_required, admin_required
import json


@login_required
def jornadas_list(request):
    if es_mesero(request.user):
        activa = Jornada.activa()
        if activa and activa.fecha == HorarioLaboral.get_solo().fecha_operativa():
            return redirect('ventas:jornada_detail', pk=activa.pk)
        return render(request, 'ventas/sin_jornada.html')

    if es_cajero(request.user) and not es_admin(request.user):
        jornadas_activas = list(Jornada.objects.filter(cerrada=False).select_related('sede'))
        if len(jornadas_activas) == 1:
            return redirect('ventas:jornada_detail', pk=jornadas_activas[0].pk)
        if not jornadas_activas:
            sedes = Sede.objects.all()
            return render(request, 'ventas/jornada_nueva_forzada.html', {
                'sedes': sedes,
                'hoy': timezone.localdate().isoformat(),
            })
        sedes_ocupadas = {j.sede_id for j in jornadas_activas if j.sede_id}
        sedes_libres = Sede.objects.exclude(id__in=sedes_ocupadas)
        return render(request, 'ventas/elegir_jornada.html', {
            'jornadas_activas': jornadas_activas,
            'sedes_libres': sedes_libres,
            'hoy': timezone.localdate().isoformat(),
        })

    jornadas = Jornada.objects.select_related('sede').all()
    sedes_con_jornada_abierta = set(
        Jornada.objects.filter(cerrada=False, sede__isnull=False).values_list('sede_id', flat=True)
    )
    sedes = Sede.objects.all()
    return render(request, 'ventas/jornadas.html', {
        'jornadas': jornadas,
        'sedes': sedes,
        'sedes_con_jornada_abierta': sedes_con_jornada_abierta,
        'hoy': timezone.localdate().isoformat(),
    })


@login_required
@cajero_required
@require_POST
def jornada_nueva(request):
    base = request.POST.get('base', 0) or 0
    fecha = request.POST.get('fecha') or timezone.localdate()
    sede_id = request.POST.get('sede_id') or None
    sede = get_object_or_404(Sede, pk=sede_id) if sede_id else None

    if sede:
        jornada_abierta = Jornada.objects.filter(sede=sede, cerrada=False).first()
        if jornada_abierta:
            messages.error(
                request,
                f'Ya hay una jornada abierta en "{sede.nombre}" del {jornada_abierta.fecha.strftime("%d/%m/%Y")}. '
                'Debes hacer el cuadre de caja y cerrarla antes de abrir una nueva.'
            )
            return redirect('ventas:jornadas_list')

    jornada = Jornada.objects.create(base=base, fecha=fecha, sede=sede)
    return redirect('ventas:jornada_detail', pk=jornada.pk)


@login_required
def jornada_detail(request, pk):
    jornada = get_object_or_404(Jornada, pk=pk)
    if es_mesero(request.user):
        activa = Jornada.activa()
        if not activa or jornada.pk != activa.pk or activa.fecha != HorarioLaboral.get_solo().fecha_operativa():
            messages.error(request, 'Solo puedes tomar pedidos de la jornada de hoy.')
            return redirect('ventas:jornadas_list')

    productos = Producto.objects.filter(activo=True).select_related('categoria').order_by('categoria__nombre', 'nombre')
    asignaciones = ProductoVariacion.objects.filter(
        activo=True, variacion__activo=True, producto__activo=True
    ).select_related('variacion')
    ventas = jornada.ventas.select_related('producto', 'variacion').all()
    gastos = jornada.gastos.all() if es_cajero(request.user) else Gasto.objects.none()
    mesas = _tablero_json(jornada)

    productos_por_cat = {}
    for p in productos:
        cat = p.categoria.nombre if p.categoria else 'Sin categoría'
        productos_por_cat.setdefault(cat, []).append(p)

    variaciones_por_producto = {}
    for pv in asignaciones:
        v = pv.variacion
        variaciones_por_producto.setdefault(pv.producto_id, []).append({
            'id': v.id,
            'nombre': v.nombre,
            'precio': int(pv.precio_efectivo or 0),
            'unidades': v.unidades_por_combo,
        })

    return render(request, 'ventas/jornada_detail.html', {
        'jornada': jornada,
        'productos_por_cat': sorted(productos_por_cat.items()),
        'variaciones_json': json.dumps(variaciones_por_producto),
        'ventas': ventas,
        'gastos': gastos,
        'mesas': mesas,
        'categorias_gasto': Gasto._meta.get_field('categoria').choices,
    })


@login_required
@require_POST
def venta_nueva(request, pk):
    """Registra un carrito (una o más líneas) como ventas de la jornada,
    repartiendo un descuento porcentual opcional entre las líneas."""
    jornada = get_object_or_404(Jornada, pk=pk)
    if es_mesero(request.user) and jornada.fecha != HorarioLaboral.get_solo().fecha_operativa():
        return JsonResponse({'success': False, 'error': 'Solo puedes tomar pedidos de la jornada de hoy.'}, status=403)
    try:
        data = json.loads(request.body)
        items = data.get('items') or []
        if not items:
            raise ValueError('El carrito está vacío')

        nota = data.get('nota', '')
        descuento_pct = float(data.get('descuento_pct') or 0)

        pedido = None
        mesa_id = data.get('mesa_id') or None
        if mesa_id:
            mesa_obj = get_object_or_404(Mesa, pk=mesa_id)
            pedido = Pedido.objects.filter(
                jornada=jornada, mesa=mesa_obj
            ).exclude(estado='LIQUIDADO').order_by('-creado').first()
            if pedido and pedido.estado == 'PAGADO':
                raise ValueError(f'Mesa "{mesa_obj.nombre}" ocupada, pendiente de confirmar pago.')
            if not pedido:
                pedido = Pedido.objects.create(jornada=jornada, mesa=mesa_obj, mesero=request.user)
        elif es_mesero(request.user):
            raise ValueError('Debes seleccionar una mesa.')

        mesa = pedido.mesa.nombre if pedido else data.get('mesa', '')
        metodo_pago = 'Sin Pagar' if pedido else data.get('metodo_pago', 'Efectivo')
        necesita_liquidacion = not pedido and es_mesero(request.user) and metodo_pago in METODOS_QUE_RETIENE_MESERO

        lineas = []
        for item in items:
            producto = get_object_or_404(Producto, pk=item['producto_id'])
            variacion = None
            if item.get('variacion_id'):
                variacion = get_object_or_404(VariacionCatalogo, pk=item['variacion_id'])
            cantidad = int(item.get('cantidad', 1))
            valor = item.get('valor') or Venta.precio_calculado(producto, variacion, cantidad)
            lineas.append({
                'producto': producto,
                'variacion': variacion,
                'cantidad': cantidad,
                'valor': valor,
                'nota_extra': item.get('nota_extra', ''),
            })

        subtotal = sum(l['valor'] for l in lineas)
        descuento_total = round(subtotal * descuento_pct / 100)

        ventas_creadas = []
        descuento_acumulado = 0
        for i, l in enumerate(lineas):
            es_ultima = i == len(lineas) - 1
            if subtotal > 0:
                parte = (descuento_total - descuento_acumulado) if es_ultima else round(l['valor'] / subtotal * descuento_total)
            else:
                parte = 0
            descuento_acumulado += parte
            nota_linea = ' — '.join(filter(None, [nota, l['nota_extra']]))

            venta = Venta.objects.create(
                jornada=jornada,
                pedido=pedido,
                mesa=mesa,
                nota=nota_linea,
                cantidad=l['cantidad'],
                producto=l['producto'],
                variacion=l['variacion'],
                valor=max(0, l['valor'] - parte),
                metodo_pago=metodo_pago,
                vendedor=request.user,
                liquidado=not necesita_liquidacion,
            )
            ventas_creadas.append(venta)

        return JsonResponse({
            'success': True,
            'ventas': [_serializar_venta(v) for v in ventas_creadas],
            'totales': _totales(jornada),
            'mesas': _tablero_json(jornada),
        })
    except Exception as e:
        return JsonResponse({'success': False, 'error': str(e)}, status=400)


@login_required
@require_POST
def venta_eliminar(request, pk, venta_id):
    jornada = get_object_or_404(Jornada, pk=pk)
    venta = get_object_or_404(Venta, pk=venta_id, jornada=jornada)
    puede_eliminar = es_cajero(request.user) or (venta.vendedor_id == request.user.id and not venta.liquidado)
    if not puede_eliminar:
        return HttpResponseForbidden('No tienes permiso para eliminar esta venta.')
    venta.delete()
    return JsonResponse({'success': True, 'totales': _totales(jornada)})


@login_required
@require_POST
def pedido_cobrar(request, pk, pedido_id):
    jornada = get_object_or_404(Jornada, pk=pk)
    pedido = get_object_or_404(Pedido, pk=pedido_id, jornada=jornada)
    if pedido.estado != 'TOMADO':
        return JsonResponse({'success': False, 'error': 'Ese pedido ya fue cobrado.'}, status=400)
    try:
        data = json.loads(request.body or '{}')
    except json.JSONDecodeError:
        data = {}
    metodo_pago = data.get('metodo_pago')
    if metodo_pago not in dict(METODOS_PAGO) or metodo_pago == 'Sin Pagar':
        return JsonResponse({'success': False, 'error': 'Selecciona un método de pago válido.'}, status=400)
    pedido.cobrar(metodo_pago)
    return JsonResponse({'success': True, 'mesas': _tablero_json(jornada), 'totales': _totales(jornada)})


@login_required
def pedido_detalle(request, pk, pedido_id):
    jornada = get_object_or_404(Jornada, pk=pk)
    pedido = get_object_or_404(Pedido, pk=pedido_id, jornada=jornada)
    lineas = pedido.lineas.select_related('producto', 'variacion').all()
    return JsonResponse({
        'success': True,
        'mesa': pedido.mesa.nombre,
        'mesero': (pedido.mesero.get_full_name() or pedido.mesero.username) if pedido.mesero else '',
        'estado': pedido.estado,
        'total': int(pedido.total),
        'lineas': [_serializar_venta(v) for v in lineas],
    })


@login_required
@cajero_required
@require_POST
def pedido_confirmar(request, pk, pedido_id):
    jornada = get_object_or_404(Jornada, pk=pk)
    pedido = get_object_or_404(Pedido, pk=pedido_id, jornada=jornada)
    if pedido.estado != 'PAGADO':
        return JsonResponse({'success': False, 'error': 'Ese pedido no está pendiente de confirmar.'}, status=400)
    pedido.confirmar(request.user)
    return JsonResponse({'success': True, 'mesas': _tablero_json(jornada)})


@login_required
@cajero_required
@require_POST
def gasto_nuevo(request, pk):
    jornada = get_object_or_404(Jornada, pk=pk)
    if jornada.cerrada:
        return JsonResponse({'success': False, 'error': 'La jornada ya está cerrada.'}, status=400)
    try:
        data = json.loads(request.body)
        categoria = data.get('categoria') or 'Otro'
        descripcion = (data.get('descripcion') or '').strip()
        valor = int(data.get('valor') or 0)
        if valor <= 0:
            raise ValueError('El valor del gasto debe ser mayor a cero')

        gasto = Gasto.objects.create(
            jornada=jornada, categoria=categoria, descripcion=descripcion, valor=valor,
        )
        return JsonResponse({
            'success': True,
            'gasto': _serializar_gasto(gasto),
            'totales': _totales(jornada),
        })
    except Exception as e:
        return JsonResponse({'success': False, 'error': str(e)}, status=400)


@login_required
@cajero_required
@require_POST
def gasto_eliminar(request, pk, gasto_id):
    jornada = get_object_or_404(Jornada, pk=pk)
    if jornada.cerrada:
        return JsonResponse({'success': False, 'error': 'La jornada ya está cerrada.'}, status=400)
    gasto = get_object_or_404(Gasto, pk=gasto_id, jornada=jornada)
    gasto.delete()
    return JsonResponse({'success': True, 'totales': _totales(jornada)})


@login_required
@cajero_required
@require_POST
def jornada_cerrar(request, pk):
    jornada = get_object_or_404(Jornada, pk=pk)
    if not jornada.cerrada:
        efectivo_contado = request.POST.get('efectivo_contado') or 0
        jornada.efectivo_contado = int(float(efectivo_contado))
        jornada.cierre_nota = request.POST.get('cierre_nota', '').strip()
        jornada.cerrada = True
        jornada.cerrada_en = timezone.now()
        jornada.cerrada_por = request.user
        jornada.save()
    return redirect('ventas:jornada_detail', pk=jornada.pk)


@login_required
@admin_required
@require_POST
def jornada_reabrir(request, pk):
    jornada = get_object_or_404(Jornada, pk=pk)
    jornada.cerrada = False
    jornada.efectivo_contado = None
    jornada.cierre_nota = ''
    jornada.cerrada_en = None
    jornada.cerrada_por = None
    jornada.save()
    return redirect('ventas:jornada_detail', pk=jornada.pk)


@login_required
def get_precio(request):
    try:
        producto = Producto.objects.get(pk=request.GET['producto_id'])
        variacion = None
        if request.GET.get('variacion_id'):
            variacion = VariacionCatalogo.objects.get(pk=request.GET['variacion_id'])
        cantidad = int(request.GET.get('cantidad', 1))
        precio = Venta.precio_calculado(producto, variacion, cantidad)
        return JsonResponse({'precio': int(precio)})
    except Exception:
        return JsonResponse({'precio': 0})


@login_required
@cajero_required
def productos_list(request):
    categorias = Categoria.objects.prefetch_related('productos').all()
    variaciones = VariacionCatalogo.objects.filter(activo=True)
    return render(request, 'ventas/productos.html', {
        'categorias': categorias,
        'variaciones': variaciones,
    })


@login_required
@cajero_required
def categorias_list(request):
    categorias = Categoria.objects.prefetch_related('productos').all()
    return render(request, 'ventas/categorias.html', {
        'categorias': categorias,
    })


@login_required
@cajero_required
@require_POST
def categoria_crear(request):
    nombre = request.POST.get('nombre', '').strip()
    if nombre:
        Categoria.objects.get_or_create(nombre=nombre)
    return redirect('ventas:categorias_list')


@login_required
@cajero_required
@require_POST
def categoria_editar(request, pk):
    categoria = get_object_or_404(Categoria, pk=pk)
    nombre = request.POST.get('nombre', '').strip()
    if nombre:
        categoria.nombre = nombre
        categoria.save()
    return redirect('ventas:categorias_list')


@login_required
@cajero_required
def dashboard(request):
    hoy = timezone.localdate()
    ventas_hoy = Venta.objects.filter(jornada__fecha=hoy)

    total_hoy = int(ventas_hoy.aggregate(t=Sum('valor'))['t'] or 0)
    transacciones_hoy = ventas_hoy.count()
    ticket_promedio = int(total_hoy / transacciones_hoy) if transacciones_hoy else 0

    def _total_hoy(metodo):
        return int(ventas_hoy.filter(metodo_pago=metodo).aggregate(t=Sum('valor'))['t'] or 0)

    por_metodo = {
        'efectivo': _total_hoy('Efectivo'),
        'nequi': _total_hoy('Nequi'),
        'bold': _total_hoy('Bold'),
        'promocion': _total_hoy('Promocion'),
        'sin_pagar': _total_hoy('Sin Pagar'),
    }

    top_productos = (
        ventas_hoy.values('producto__nombre')
        .annotate(cantidad=Sum('cantidad'), total=Sum('valor'))
        .order_by('-cantidad')[:5]
    )

    dias = [hoy - timedelta(days=i) for i in range(6, -1, -1)]
    ventas_semana = [
        {
            'fecha': d,
            'total': int(Venta.objects.filter(jornada__fecha=d).aggregate(t=Sum('valor'))['t'] or 0),
        }
        for d in dias
    ]
    max_semana = max((d['total'] for d in ventas_semana), default=0) or 1

    jornadas_abiertas = Jornada.objects.filter(cerrada=False).select_related('sede')

    return render(request, 'ventas/dashboard.html', {
        'hoy': hoy,
        'total_hoy': total_hoy,
        'transacciones_hoy': transacciones_hoy,
        'ticket_promedio': ticket_promedio,
        'por_metodo': por_metodo,
        'top_productos': top_productos,
        'ventas_semana': ventas_semana,
        'max_semana': max_semana,
        'jornadas_abiertas': jornadas_abiertas,
    })


@login_required
@cajero_required
def transacciones(request):
    q = request.GET.get('q', '').strip()
    vendedor_id = request.GET.get('vendedor') or ''
    sede_id = request.GET.get('sede') or ''
    jornada_id = request.GET.get('jornada') or ''

    ventas = Venta.objects.select_related(
        'producto', 'variacion', 'vendedor', 'jornada', 'jornada__sede'
    ).all()

    if q:
        ventas = ventas.filter(Q(mesa__icontains=q) | Q(producto__nombre__icontains=q))
    if vendedor_id:
        ventas = ventas.filter(vendedor_id=vendedor_id)
    if sede_id:
        ventas = ventas.filter(jornada__sede_id=sede_id)
    if jornada_id:
        ventas = ventas.filter(jornada_id=jornada_id)

    hoy = timezone.localdate()
    ayer = hoy - timedelta(days=1)
    inicio_semana = hoy - timedelta(days=hoy.weekday())

    def _stats(fecha_filtro):
        agg = Venta.objects.filter(**fecha_filtro).aggregate(t=Sum('valor'), n=Count('id'))
        return {'n': agg['n'] or 0, 'total': int(agg['t'] or 0)}

    stats = {
        'hoy': _stats({'jornada__fecha': hoy}),
        'ayer': _stats({'jornada__fecha': ayer}),
        'semana': _stats({'jornada__fecha__gte': inicio_semana, 'jornada__fecha__lte': hoy}),
        'mes': _stats({'jornada__fecha__year': hoy.year, 'jornada__fecha__month': hoy.month}),
    }

    User = get_user_model()
    vendedores = User.objects.filter(ventas_realizadas__isnull=False).distinct().order_by('username')
    sedes = Sede.objects.all()

    paginator = Paginator(ventas, 50)
    page_obj = paginator.get_page(request.GET.get('page'))

    return render(request, 'ventas/transacciones.html', {
        'page_obj': page_obj,
        'stats': stats,
        'vendedores': vendedores,
        'sedes': sedes,
        'q': q,
        'vendedor_id': vendedor_id,
        'sede_id': sede_id,
        'jornada_id': jornada_id,
    })


def _serializar_venta(venta):
    return {
        'id': venta.pk,
        'mesa': venta.mesa,
        'nota': venta.nota,
        'cantidad': venta.cantidad,
        'producto': venta.producto.nombre,
        'variacion': venta.variacion.nombre if venta.variacion else '',
        'valor': int(venta.valor),
        'metodo_pago': venta.metodo_pago,
        'vendedor': venta.vendedor.get_full_name() or venta.vendedor.username if venta.vendedor else '',
        'hora': venta.creado.strftime('%H:%M'),
    }


def _serializar_gasto(gasto):
    return {
        'id': gasto.pk,
        'categoria': gasto.categoria,
        'categoria_display': gasto.get_categoria_display(),
        'descripcion': gasto.descripcion,
        'valor': int(gasto.valor),
        'hora': gasto.creado.strftime('%H:%M'),
    }


def _totales(jornada):
    return {
        'efectivo': int(jornada.total_efectivo),
        'nequi': int(jornada.total_nequi),
        'bold': int(jornada.total_bold),
        'promocion': int(jornada.total_promocion),
        'sin_pagar': int(jornada.total_sin_pagar),
        'total_ventas': int(jornada.total_ventas),
        'total_gastos': int(jornada.total_gastos),
        'caja': int(jornada.caja),
    }


def _tablero_json(jornada):
    mesas_qs = Mesa.objects.filter(sede=jornada.sede, activa=True) if jornada.sede_id else Mesa.objects.filter(activa=True)

    pedidos_abiertos = {}
    pedidos = Pedido.objects.filter(jornada=jornada).exclude(estado='LIQUIDADO').select_related('mesero').order_by('-creado')
    for p in pedidos:
        pedidos_abiertos.setdefault(p.mesa_id, p)

    resultado = []
    for m in mesas_qs:
        p = pedidos_abiertos.get(m.id)
        resultado.append({
            'mesa_id': m.id,
            'mesa_nombre': m.nombre,
            'pedido_id': p.id if p else None,
            'estado': p.estado if p else 'LIBRE',
            'mesero': ((p.mesero.get_full_name() or p.mesero.username) if p.mesero else '') if p else '',
            'total': int(p.total) if p else 0,
            'hora': p.creado.strftime('%H:%M') if p else '',
        })
    return resultado
