from server_backsemot.api.dte import generar_factura_tickets, enviar_datos, generar_params_obtener_link_web, generar_nota_credito_factura
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from server_backsemot.models import Transaccion, Qr, Cliente, FacturaVenta, TicketFacturaVenta, ControlQr, TransaccionWeb, InteraccionApiWeb, Abono, Obra
from server_backsemot.personal.personal import validaciones_basicas, mantener_personal_activo
from server_backsemot.personal.despachador import validaciones_basicas_despachador, mantener_cliente_activo, buscar_cliente_activo
from django.db.models import F, Avg, Sum, Count, ExpressionWrapper, DurationField
from server_backsemot.personal.abono import abonar_cliente_pago_factura
from server_backsemot.personal.accionUsuario import registrar_accion
from django.db.models.functions import Coalesce, Now, TruncDate
from server_backsemot.api.errorlog import guardar_error
from server_backsemot.api.qr import formatear_qr
from rest_framework.decorators import api_view
from django.db.models import Exists, OuterRef
from base64 import b64encode, b64decode
from server_backsemot.utiles import *
import xml.etree.ElementTree as ET
from django.db import transaction
from django.db.models import Sum
from django.db.models import Q
from datetime import datetime
from calendar import monthrange
import json


@api_view(["GET"])
def buscar_factura_venta(request):
    try:
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)
        
        if "id" in request.GET:
            id = request.GET["id"]
            facturas = FacturaVenta.objects.filter(
                Q(id__icontains=id) |
                Q(folio__icontains=id)
            ).all().order_by("-fecha_creacion", "-id")
        elif "texto" in request.GET:
            texto = request.GET["texto"]
            facturas = FacturaVenta.objects.filter(
                Q(cliente__rut__icontains=texto) |
                Q(cliente__nombre__icontains=texto)
            ).all().order_by("-fecha_creacion", "-id")
        else:
            # Búsqueda general: retornar todas las facturas con paginación
            facturas = FacturaVenta.objects.all().order_by("-fecha_creacion", "-id")
            
        # Paginación
        page = request.GET.get('page', 1)  # Página solicitada, por defecto es 1
        paginator = Paginator(facturas, 20)  # 20 elementos por página
        
        try:
            paginated_facturas = paginator.page(page)
        except PageNotAnInteger:
            paginated_facturas = paginator.page(1)
        except EmptyPage:
            paginated_facturas = paginator.page(paginator.num_pages)
        
        # Datos de paginación
        total_items = paginator.count
        total_pages = paginator.num_pages
        current_page = paginated_facturas.number
        start_index = paginated_facturas.start_index()
        end_index = paginated_facturas.end_index()
        
        # Preparar la respuesta con los datos paginados
        datos = [formatear_factura(factura) for factura in paginated_facturas]
        
        # Respuesta final incluyendo la información de paginación
        respuesta = {
            "mensaje": "Listado facturas",
            "datos": datos,
            "paginacion": {
                "pagina_actual": current_page,
                "total_paginas": total_pages,
                "total_elementos": total_items,
                "elementos_pagina_actual": f"{start_index} al {end_index}"
            }
        }
        
        mantener_personal_activo(request.GET.get("token"))
        return Utiles.responder_200("Listado de facturas", respuesta)

    except Exception as e:
        return Utiles.responder_500("Error inesperado", sys.exc_info())
    


@api_view(["GET"])
def buscar_facturas_facturadas_venta(request):
    try:
        # Validaciones iniciales
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        # Obtener fechas desde el request
        fecha_inicio_str = request.GET.get("fecha_inicio")
        fecha_termino_str = request.GET.get("fecha_termino")
        cliente_id = request.GET.get("cliente")

        if not cliente_id:
            return Utiles.responder_500("Falta el id del cliente")
        
        cliente = Cliente.objects.get(id=cliente_id)

        if not fecha_inicio_str or not fecha_termino_str:
            return Utiles.responder_500("Parámetros 'fecha_inicio' y 'fecha_termino' son requeridos (formato YYYY-MM-DD).")

        try:
            fecha_inicio = datetime.strptime(fecha_inicio_str, "%Y-%m-%d").date()
            fecha_termino = datetime.strptime(fecha_termino_str, "%Y-%m-%d").date()
        except ValueError:
            return Utiles.responder_500("Formato de fecha inválido. Use 'YYYY-MM-DD'.")

        if fecha_inicio > fecha_termino:
            return Utiles.responder_500("La 'fecha_inicio' no puede ser posterior a 'fecha_termino'.")

        # Filtrado de facturas
        #texto = request.GET.get("texto", "").strip()
        #palabras = [p for p in texto.split() if p]

        filtros = Q(facturado=1, fecha_factura__date__range=(fecha_inicio, fecha_termino), cliente=cliente)

        # if texto:
        #     filtro_nombre = Q()
        #     for palabra in palabras:
        #         filtro_nombre &= Q(cliente__nombre__icontains=palabra)

        #     filtros &= (
        #         filtro_nombre |
        #         Q(cliente__rut__icontains=texto)
        #     )

        facturas = FacturaVenta.objects.filter(filtros).order_by("-fecha_creacion", "-id")

        # Paginación
        page = request.GET.get('page', 1)
        paginator = Paginator(facturas, 20)

        try:
            paginated_facturas = paginator.page(page)
        except PageNotAnInteger:
            paginated_facturas = paginator.page(1)
        except EmptyPage:
            paginated_facturas = paginator.page(paginator.num_pages)

        # Preparar respuesta
        datos = [formatear_factura(f) for f in paginated_facturas]
        respuesta = {
            "mensaje": "Listado de facturas facturadas",
            "datos": datos,
            "paginacion": {
                "pagina_actual": paginated_facturas.number,
                "total_paginas": paginator.num_pages,
                "total_elementos": paginator.count,
                "elementos_pagina_actual": f"{paginated_facturas.start_index()} al {paginated_facturas.end_index()}"
            }
        }

        mantener_personal_activo(request.GET.get("token"))
        return Utiles.responder_200("Facturas facturadas listadas correctamente", respuesta)
    except Cliente.DoesNotExist:
        return Utiles.responder_500("No existe cliente con el id indicado")
    except Exception:
        return Utiles.responder_500("Error inesperado", sys.exc_info())
    


@api_view(["GET"])
def buscar_facturas_no_facturadas_venta(request):
    try:
        # Validaciones iniciales
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        # Filtrado por rango de fechas
        fecha_inicio_str = request.GET.get("fecha_inicio")
        fecha_termino_str = request.GET.get("fecha_termino")

        try:
            if fecha_inicio_str and fecha_termino_str:
                fecha_inicio = datetime.strptime(fecha_inicio_str, "%Y-%m-%d").date()
                fecha_termino = datetime.strptime(fecha_termino_str, "%Y-%m-%d").date()

                if "texto" in request.GET:
                    texto = request.GET["texto"]
                    facturas = FacturaVenta.objects.filter(
                        (Q(cliente__nombre__icontains=texto) |
                         Q(cliente__rut__icontains=texto)),
                        facturado=0,
                        fecha_factura__date__gte=fecha_inicio,
                        fecha_factura__date__lte=fecha_termino,
                    ).order_by("-fecha_creacion", "-id")
                else:
                    facturas = FacturaVenta.objects.filter(
                        facturado=0,
                        fecha_factura__date__gte=fecha_inicio,
                        fecha_factura__date__lte=fecha_termino
                    ).order_by("-fecha_creacion", "-id")
            else:
                # Si no se pasan fechas, obtener todas las facturas no facturadas
                facturas = FacturaVenta.objects.filter(facturado=0).order_by("-fecha_creacion", "-id")
        except ValueError:
            return Utiles.responder_500("Las fechas deben tener el formato YYYY-MM-DD.")

        # Paginación
        page = request.GET.get('page', 1)
        paginator = Paginator(facturas, 20)

        try:
            paginated_facturas = paginator.page(page)
        except PageNotAnInteger:
            paginated_facturas = paginator.page(1)
        except EmptyPage:
            paginated_facturas = paginator.page(paginator.num_pages)

        # Datos de paginación
        total_items = paginator.count
        total_pages = paginator.num_pages
        current_page = paginated_facturas.number
        start_index = paginated_facturas.start_index()
        end_index = paginated_facturas.end_index()

        # Preparar datos para la respuesta
        datos = [formatear_factura(f) for f in paginated_facturas]

        respuesta = {
            "mensaje": "Listado de facturas pagadas",
            "datos": datos,
            "paginacion": {
                "pagina_actual": current_page,
                "total_paginas": total_pages,
                "total_elementos": total_items,
                "elementos_pagina_actual": f"{start_index} al {end_index}"
            }
        }

        mantener_personal_activo(request.GET.get("token"))
        return Utiles.responder_200("Facturas pagadas listadas correctamente", respuesta)

    except Exception as e:
        return Utiles.responder_500("Error inesperado", sys.exc_info())
    


@api_view(["GET"])
def buscar_facturas_pagadas_venta(request):
    try:
        # Validaciones iniciales
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        # Filtrado por rango de fechas
        fecha_inicio_str = request.GET.get("fecha_inicio")
        fecha_termino_str = request.GET.get("fecha_termino")

        try:
            if fecha_inicio_str and fecha_termino_str:
                fecha_inicio = datetime.strptime(fecha_inicio_str, "%Y-%m-%d").date()
                fecha_termino = datetime.strptime(fecha_termino_str, "%Y-%m-%d").date()

                if "texto" in request.GET:
                    texto = request.GET["texto"]
                    facturas = FacturaVenta.objects.filter(
                        (Q(cliente__nombre__icontains=texto) |
                         Q(cliente__rut__icontains=texto)),
                        pagado=1,
                        fecha_factura__date__gte=fecha_inicio,
                        fecha_factura__date__lte=fecha_termino,
                    ).order_by("-fecha_creacion", "-id")
                else:
                    facturas = FacturaVenta.objects.filter(
                        pagado=1,
                        fecha_factura__date__gte=fecha_inicio,
                        fecha_factura__date__lte=fecha_termino
                    ).order_by("-fecha_creacion", "-id")
            else:
                # Si no se pasan fechas, obtener todas las facturas pagadas
                facturas = FacturaVenta.objects.filter(pagado=1).order_by("-fecha_creacion", "-id")
        except ValueError:
            return Utiles.responder_500("Las fechas deben tener el formato YYYY-MM-DD.")

        # Paginación
        page = request.GET.get('page', 1)
        paginator = Paginator(facturas, 20)

        try:
            paginated_facturas = paginator.page(page)
        except PageNotAnInteger:
            paginated_facturas = paginator.page(1)
        except EmptyPage:
            paginated_facturas = paginator.page(paginator.num_pages)

        # Datos de paginación
        total_items = paginator.count
        total_pages = paginator.num_pages
        current_page = paginated_facturas.number
        start_index = paginated_facturas.start_index()
        end_index = paginated_facturas.end_index()

        # Preparar datos para la respuesta
        datos = [formatear_factura(f) for f in paginated_facturas]

        respuesta = {
            "mensaje": "Listado de facturas pagadas",
            "datos": datos,
            "paginacion": {
                "pagina_actual": current_page,
                "total_paginas": total_pages,
                "total_elementos": total_items,
                "elementos_pagina_actual": f"{start_index} al {end_index}"
            }
        }

        mantener_personal_activo(request.GET.get("token"))
        return Utiles.responder_200("Facturas pagadas listadas correctamente", respuesta)

    except Exception as e:
        return Utiles.responder_500("Error inesperado", sys.exc_info())
    


@api_view(["GET"])
def buscar_facturas_no_pagadas_venta(request):
    try:
        # Validaciones iniciales
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        # Filtrado por rango de fechas
        fecha_inicio_str = request.GET.get("fecha_inicio")
        fecha_termino_str = request.GET.get("fecha_termino")

        try:
            if fecha_inicio_str and fecha_termino_str:
                fecha_inicio = datetime.strptime(fecha_inicio_str, "%Y-%m-%d").date()
                fecha_termino = datetime.strptime(fecha_termino_str, "%Y-%m-%d").date()

                if "texto" in request.GET:
                    texto = request.GET["texto"]
                    facturas = FacturaVenta.objects.filter(
                        (Q(cliente__nombre__icontains=texto) |
                         Q(cliente__rut__icontains=texto)),
                        pagado=0,
                        fecha_factura__date__gte=fecha_inicio,
                        fecha_factura__date__lte=fecha_termino,
                    ).order_by("-fecha_creacion", "-id")
                else:
                    facturas = FacturaVenta.objects.filter(
                        pagado=0,
                        fecha_factura__date__gte=fecha_inicio,
                        fecha_factura__date__lte=fecha_termino
                    ).order_by("-fecha_creacion", "-id")
            else:
                # Si no se pasan fechas, obtener todas las facturas no pagadas
                facturas = FacturaVenta.objects.filter(pagado=0).order_by("-fecha_creacion", "-id")
        except ValueError:
            return Utiles.responder_500("Las fechas deben tener el formato YYYY-MM-DD.")

        # Paginación
        page = request.GET.get('page', 1)
        paginator = Paginator(facturas, 20)

        try:
            paginated_facturas = paginator.page(page)
        except PageNotAnInteger:
            paginated_facturas = paginator.page(1)
        except EmptyPage:
            paginated_facturas = paginator.page(paginator.num_pages)

        # Datos de paginación
        total_items = paginator.count
        total_pages = paginator.num_pages
        current_page = paginated_facturas.number
        start_index = paginated_facturas.start_index()
        end_index = paginated_facturas.end_index()

        # Preparar datos para la respuesta
        datos = [formatear_factura(f) for f in paginated_facturas]

        respuesta = {
            "mensaje": "Listado de facturas pagadas",
            "datos": datos,
            "paginacion": {
                "pagina_actual": current_page,
                "total_paginas": total_pages,
                "total_elementos": total_items,
                "elementos_pagina_actual": f"{start_index} al {end_index}"
            }
        }

        mantener_personal_activo(request.GET.get("token"))
        return Utiles.responder_200("Facturas pagadas listadas correctamente", respuesta)

    except Exception as e:
        return Utiles.responder_500("Error inesperado", sys.exc_info())




@api_view(["GET"])
def buscar_facturas_cliente(request):
    try:
        # Validación de permisos y autenticación
        validacion = validaciones_basicas_despachador(validaciones=[1, 2, 3, 4], perfiles=[1000, 999], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        token = request.GET.get("token")
        despa = buscar_cliente_activo(token)
        if not despa:
            return Utiles.responder_500("No existe cliente asociado al token")

        cli = despa.cliente
        facturas = FacturaVenta.objects.filter(cliente=cli).prefetch_related(
            'ticketfacturaventa_set__transaccion__camion'
        )

        # Filtro por rango de fechas
        fechainicio = request.GET.get("fechainicio")
        fechatermino = request.GET.get("fechatermino")
        if fechainicio and fechatermino:
            try:
                fi = datetime.strptime(fechainicio, "%Y-%m-%d")
                ft = datetime.strptime(fechatermino, "%Y-%m-%d")
                ft = datetime.combine(ft, datetime.max.time())
                facturas = facturas.filter(fecha_factura__range=(fi, ft))
            except ValueError:
                return Utiles.responder_500("Formato de fechas incorrecto. Use AAAA-MM-DD.")

        # Filtro por texto
        texto = request.GET.get("texto")
        if texto:
            facturas = facturas.filter(
                Q(folio__icontains=texto) |
                Q(ticketfacturaventa__transaccion__camion__patente__icontains=texto) |
                Q(ticketfacturaventa__transaccion__obra__nombre__icontains=texto)
            ).distinct()

        # Paginación
        page = request.GET.get('page', 1)
        paginator = Paginator(facturas.order_by("-fecha_creacion", "-id"), 20)

        try:
            paginated_facturas = paginator.page(page)
        except PageNotAnInteger:
            paginated_facturas = paginator.page(1)
        except EmptyPage:
            paginated_facturas = paginator.page(paginator.num_pages)

        # Formateo de datos con glosa
        datos = [formatear_factura_cliente(f) for f in paginated_facturas]

        respuesta = {
            "mensaje": "Listado de facturas del cliente",
            "datos": datos,
            "paginacion": {
                "pagina_actual": paginated_facturas.number,
                "total_paginas": paginator.num_pages,
                "total_elementos": paginator.count,
                "elementos_pagina_actual": f"{paginated_facturas.start_index()} al {paginated_facturas.end_index()}"
            }
        }

        mantener_cliente_activo(token)
        return Utiles.responder_200("Facturas listadas correctamente", respuesta)

    except Exception as e:
        return Utiles.responder_500("Error inesperado", str(e))



# PARA FACTURAS / BOLETAS WEB
@api_view(["GET"])
def buscar_boletas_facturadas_web(request):
    try:
        # Validaciones básicas
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        mes = request.GET.get("mes")
        anio = request.GET.get("anio")
        texto = request.GET.get("texto")
        
        transacciones = TransaccionWeb.objects.filter(activo=1)

        if mes and anio:
            try:
                mes = int(mes)
                anio = int(anio)

                if mes < 1 or mes > 12:
                    return Utiles.responder_500("El mes debe estar entre 1 y 12.")

                transacciones = transacciones.filter(fecha__year=anio, fecha__month=mes)

            except ValueError:
                return Utiles.responder_500("El mes y el año deben ser números válidos.")
        
        # Filtro por texto (cliente o QR)
        if texto:
            transacciones = transacciones.filter(
                Q(cliente__nombre__icontains=texto) |
                Q(cliente__rut__icontains=texto) |
                Q(qr__codigoQr__icontains=texto)
            )

        # Ordenar por fecha más reciente
        transacciones = transacciones.order_by("-fecha")

        # Paginación
        page = request.GET.get("page", 1)
        paginator = Paginator(transacciones, 20)

        try:
            paginated = paginator.page(page)
        except PageNotAnInteger:
            paginated = paginator.page(1)
        except EmptyPage:
            paginated = paginator.page(paginator.num_pages)

        total_items = paginator.count
        total_pages = paginator.num_pages
        current_page = paginated.number
        start_index = paginated.start_index()
        end_index = paginated.end_index()

        # Formatear resultados
        datos = []
        for tx in paginated:
            interaccion = InteraccionApiWeb.objects.filter(transaccion=tx).first()
            neto = round(tx.total * 100 / 119)
            datos.append({
                "id": tx.id,
                "cliente": tx.cliente.nombre,
                "rut": tx.cliente.rut,
                "fecha": tx.fecha,
                "neto": neto,
                "iva": tx.total - neto,
                "monto": tx.total,
                "unidad": tx.unidad,
                "folio": interaccion.folio if interaccion else None,
                "qr": f"W{tx.qr.id}",
                "estado_pago": tx.estado_pago.nombre,
                "link_factura": interaccion.link if interaccion else None,
                "tipo_documento": "boleta" if tx.dte.id == 1 else "factura"
            })

        respuesta = {
            "mensaje": "Listado de boletas/facturas web",
            "datos": datos,
            "paginacion": {
                "pagina_actual": current_page,
                "total_paginas": total_pages,
                "total_elementos": total_items,
                "elementos_pagina_actual": f"{start_index} al {end_index}"
            }
        }

        mantener_personal_activo(request.GET.get("token"))
        return Utiles.responder_200("Transacciones Web listadas correctamente", respuesta)

    except Exception as e:
        return Utiles.responder_500("Error inesperado", sys.exc_info())



@api_view(["POST"])
def crear_factura_venta(request):
    try:
        # Validación inicial
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        campos_requeridos = ["tickets", "cliente", "fecha", "pagado", "facturar"]
        validacion_campos = validar_campos_requeridos(request, campos_requeridos)
        if validacion_campos != "ok":
            return Utiles.responder_500(validacion_campos)

        # Validación y parsing de campos
        cli = request.POST["cliente"]
        listado = request.POST["tickets"].strip()
        fecha = request.POST["fecha"]
        pagado = int(request.POST["pagado"])
        facturar = int(request.POST["facturar"])

        if not Utiles.validar_fecha_formato(fecha):
            return Utiles.responder_500("El formato de la fecha es incorrecto")
        
        if pagado == 1 and not "fecha_pago" in request.POST:
            return Utiles.responder_500("Falta la fecha de pago")
        
        if "fecha_pago" in request.POST:
            fecha_pago = request.POST["fecha_pago"]
            if not Utiles.validar_fecha_formato(fecha_pago):
                return Utiles.responder_500("El formato de la fecha de pago es incorrecto")

        try:
            listado = [int(ticket.strip()) for ticket in listado.split(",")]
        except ValueError:
            return Utiles.responder_500("Solo se permiten valores numéricos en los ID de los tickets")

        if len(listado) > 60:
            return Utiles.responder_500(f"El máximo de tickets por factura es de 60 y usted envió {len(listado)}")

        # Obtener cliente y tickets
        cliente = Cliente.objects.get(id=cli)
        tickets = Transaccion.objects.filter(qr_id__in=listado, cliente=cliente)

        # Verificar tickets
        tickets_encontrados = set(tickets.values_list('qr_id', flat=True))
        tickets_faltantes = set(listado) - tickets_encontrados
        if tickets_faltantes:
            return Utiles.responder_500(f"Los siguientes tickets no existen o no pertenecen al cliente: {tickets_faltantes}")

        # Verificar si ya están facturados
        facturados = TicketFacturaVenta.objects.filter(transaccion__qr_id__in=listado).exists()
        if facturados:
            return Utiles.responder_500("Uno o más de los tickets ya se encuentran facturados")

        # Calcular totales
        total_tickets = tickets.aggregate(total=Sum('total'))['total'] or 0
        if total_tickets == 0:
            return Utiles.responder_500("El total a facturar es de: $0")

        neto_tickets = total_tickets / 1.19
        iva_tickets = total_tickets - neto_tickets
        if (int(neto_tickets) + int(iva_tickets)) < total_tickets:
            iva_tickets += 1

        if not tickets:
            return Utiles.responder_500("La factura debe tener al menos 1 ticket")
        if tickets.count() == 0:
            return Utiles.responder_500("La factura debe tener al menos 1 ticket")

        # Crear la factura
        factura = FacturaVenta.objects.create(
            fecha_creacion=datetime.now(),
            fecha_factura=fecha,
            fecha_pago=fecha_pago if "fecha_pago" in request.POST else None,
            cliente=cliente,
            total=total_tickets,
            iva=iva_tickets,
            neto=neto_tickets,
            pagado=pagado,
        )

        TicketFacturaVenta.objects.bulk_create([
            TicketFacturaVenta(transaccion=t, factura=factura) for t in tickets
        ])

        # Facturar si corresponde
        if facturar == 1:
            resp, texto = facturar_factura(factura)
            if not resp:
                return Utiles.responder_500(texto)
            return Utiles.responder_200("Se ha generado la factura", {"link": texto})
        mantener_personal_activo(request.POST["token"])
        registrar_accion(request.POST["token"], 1, 51, factura)
        return Utiles.responder_200("Factura registrada correctamente y pendiente de facturación")
    except Cliente.DoesNotExist:
        return Utiles.responder_500("El cliente indicado no existe")
    except Exception as e:
        guardar_error(sys.exc_info())
        return Utiles.responder_500(f"Error inesperado: {str(e)}", sys.exc_info())
    


def facturar_factura(f: FacturaVenta):
    try:
        if f.facturado == 1:
            return False, "Ya se encuentra facturada"
        fresp, fxml, fparams = generar_factura_tickets(f.id)
        if not fresp:
            return False, fxml
        
        fparams["file"] = b64encode(fxml.encode("utf-8")).decode("utf-8")
        fparams["formato"] = "2" # 2 = XML
        f.xml = fxml
        f.save()
        eresp, etext = enviar_datos(fparams, 1)
        if not eresp:
            return False, etext
        
        root = ET.fromstring(etext)
        fact_resp = root.find("./Resultado").text
        if fact_resp == "False":
            f.facturacioncl = etext
            f.save()
            return False, "No fue posible generar el documento electrónico de pago"
        
        folio = root.find("./Detalle/Documento/Folio").text
        f.facturacioncl = etext
        f.folio = folio
        f.facturado = 1
        f.save()

        lresp, lparams = generar_params_obtener_link_web(folio, 33)
        if not lresp:
            return False, lparams

        fenviar, ftexto = enviar_datos(lparams, 2)
        root = ET.fromstring(ftexto)
        link = root.find("./Mensaje").text
        f.link = b64decode(link).decode("utf-8")
        f.save()
        return True, f.link
    except:
        guardar_error(sys.exc_info())
        return False, "Error inesperado"



@api_view(["POST"])
def ejecutar_facturacion_venta(request):
    try:
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)
        
        if not "factura" in request.POST:
            return Utiles.responder_500("Falta factura")
        
        fact = request.POST["factura"]
        f = FacturaVenta.objects.get(id=fact)
        t = TicketFacturaVenta.objects.filter(factura=f).count()
        if t <= 0:
            return Utiles.responder_500("La factura debe tener al menos 1 ticket")
        resp, texto = facturar_factura(f)
        if not resp:
            return Utiles.responder_500(texto)
        datos = {"link": texto}
        mantener_personal_activo(request.POST["token"])
        registrar_accion(request.POST["token"], 1, 52, f)
        return Utiles.responder_200("Se ha generado la factura", datos)
    
    except FacturaVenta.DoesNotExist:
        return Utiles.responder_500("No existe factura con ese id")
    except:
        return Utiles.responder_500("Error inesperado", sys.exc_info())



def formatear_factura(self):
    # Obtener los tickets asociados a esta factura
    tickets = TicketFacturaVenta.objects.filter(factura=self).select_related('transaccion__qr')
    
    # Extraer los IDs de los QRs correspondientes
    qr_ids = [ticket.transaccion.qr.id for ticket in tickets if ticket.transaccion.qr]

    # Construir el diccionario con los datos de la factura
    factura_data = {
        "id": self.id,
        "fecha_creacion": self.fecha_creacion.isoformat(),
        "fecha_factura": self.fecha_factura.isoformat(),
        "fecha_pago": self.fecha_pago.isoformat() if self.fecha_pago else None,
        "neto": self.neto,
        "iva": self.iva,
        "total": self.total,
        "cliente": {
            "id": self.cliente.id,
            "nombre": self.cliente.nombre,
            "rut": self.cliente.rut,
        },
        "pagado": self.pagado,
        "facturado": self.facturado,
        "folio": self.folio,
        "link": self.link,
        "qr_ids": qr_ids,  # Lista de IDs de QRs asociados
    }
    
    # Convertir a JSON y devolver
    return factura_data



@api_view(["POST"])
def liberar_tickets_nc(request):
    try:
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        if not "factura" in request.POST:
            return Utiles.responder_500("Falta la factura")
        
        id = request.POST["factura"]
        
        try:
            f = FacturaVenta.objects.get(id=id)
        except FacturaVenta.DoesNotExist:
            return Utiles.responder_500("No existe factura con el id indicado")
        
        # Cambiar datos de la factura que se le creo una NC en facturacion.cl
        f.link = None
        f.facturado = 0
        f.save()

        # Elimina los tickets de esta factura
        TicketFacturaVenta.objects.filter(factura=f).delete()

        return Utiles.responder_200("Se han liberado los tickets de la factura seleccionada")
    except:
        return Utiles.responder_500("Error inesperado", sys.exc_info())



@api_view(["POST"])
def marcar_factura_pagada(request):
    try:
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        if not "factura" in request.POST:
            return Utiles.responder_500("Falta la factura")
        
        fac = request.POST["factura"]
        f = FacturaVenta.objects.get(id=fac)

        if f.pagado == 1:
            return Utiles.responder_200("La factura ya se encontraba marcada como pagada")
        
        f.pagado = 1
        f.fecha_pago = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        f.save()
        mantener_personal_activo(request.POST["token"])
        registrar_accion(request.POST["token"], 1, 53, f)

        try:
            resp, text = abonar_cliente_pago_factura(f, request.POST["token"])
        except:
            x=0
        
        if resp:
            return Utiles.responder_200("Se ha marcado la factura como pagada")
        else:
            return Utiles.responder_200("Se ha marcado la factura como pagada, pero ocurrió un error al generar el abono")

    except FacturaVenta.DoesNotExist:
        return Utiles.responder_500("No existe factura con ese id")
    except:
        return Utiles.responder_500("Error inesperado", sys.exc_info())



@api_view(["POST"])
def marcar_factura_no_pagada(request):
    try:
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        if not "factura" in request.POST:
            return Utiles.responder_500("Falta la factura")
        
        fac = request.POST["factura"]
        f = FacturaVenta.objects.get(id=fac)

        if f.pagado == 0:
            return Utiles.responder_200("La factura ya se encontraba marcada como no pagada")
        
        f.pagado = 0
        f.fecha_pago = None
        f.save()
        mantener_personal_activo(request.POST["token"])
        registrar_accion(request.POST["token"], 1, 54, f)

        try:
            eliminados, _ = Abono.objects.filter(factura=f).delete()
            if eliminados > 0:
                return Utiles.responder_200(
                    f"Factura marcada como no pagada y se eliminaron {eliminados} abono(s)."
                )
        except Exception:
            return Utiles.responder_200(
                "Factura marcada como no pagada, pero no se pudieron eliminar los abonos asociados."
            )
        
        return Utiles.responder_200("Se ha marcado la factura como no pagada")

    except FacturaVenta.DoesNotExist:
        return Utiles.responder_500("No existe factura con ese id")
    except:
        return Utiles.responder_500("Error inesperado", sys.exc_info())
    


@api_view(["POST"])
def modificar_factura_venta(request):
    try:
        # Validación inicial
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        campos_requeridos = ["id", "tickets", "cliente", "fecha", "pagado", "facturar"]
        validacion_campos = validar_campos_requeridos(request, campos_requeridos)
        if validacion_campos != "ok":
            return Utiles.responder_500(validacion_campos)

        # Validación y parsing de campos
        cli = request.POST["cliente"]
        listado = request.POST["tickets"].strip()
        fecha = request.POST["fecha"]
        id = request.POST["id"]

        try:
            pagado = int(request.POST["pagado"])
        except ValueError:
            return Utiles.responder_500("El valor de pagado es incorrecto")
        
        try:
            facturar = int(request.POST["facturar"])
        except ValueError:
            return Utiles.responder_500("El valor de facturar es incorrecto")
        
        if pagado != 0 and pagado != 1:
            return Utiles.responder_500("El valor de pagado es incorrecto")
        if facturar != 0 and facturar != 1:
            return Utiles.responder_500("El valor de facturar es incorrecto")

        f = FacturaVenta.objects.get(id=id)
        if f.facturado == 1:
            return Utiles.responder_500("No se puede realizar la modificación, porque el documento ya fue facturado")

        if not Utiles.validar_fecha_formato(fecha):
            return Utiles.responder_500("El formato de la fecha es incorrecto")
        
        if pagado == 1 and not "fecha_pago" in request.POST:
            return Utiles.responder_500("Falta la fecha de pago")
        
        if "fecha_pago" in request.POST:
            fecha_pago = request.POST["fecha_pago"]
            if not Utiles.validar_fecha_formato(fecha_pago):
                return Utiles.responder_500("El formato de la fecha de pago es incorrecto")

        try:
            listado = [int(ticket.strip()) for ticket in listado.split(",")]
        except ValueError:
            return Utiles.responder_500("Solo se permiten valores numéricos en los ID de los tickets")

        if len(listado) > 60:
            return Utiles.responder_500(f"El máximo de tickets por factura es de 60 y usted envió {len(listado)}")

        # Obtener cliente y tickets
        cliente = Cliente.objects.get(id=cli)
        tickets = Transaccion.objects.filter(qr_id__in=listado, cliente=cliente)

        # Verificar tickets
        tickets_encontrados = set(tickets.values_list('qr_id', flat=True))
        tickets_faltantes = set(listado) - tickets_encontrados
        if tickets_faltantes:
            return Utiles.responder_500(f"Los siguientes tickets no existen o no pertenecen al cliente: {tickets_faltantes}")

        # Verificar si ya están facturados
        facturados = TicketFacturaVenta.objects.filter(transaccion__qr_id__in=listado).exclude(factura_id=f.id).exists()
        if facturados:
            return Utiles.responder_500("Uno o más de los tickets ya se encuentran facturados")

        # Calcular totales
        total_tickets = tickets.aggregate(total=Sum('total'))['total'] or 0
        if total_tickets == 0:
            return Utiles.responder_500("El total a facturar es de: $0")

        neto_tickets = total_tickets / 1.19
        iva_tickets = total_tickets - neto_tickets
        if (int(neto_tickets) + int(iva_tickets)) < total_tickets:
            iva_tickets += 1

        # Crear la factura
        factura = FacturaVenta.objects.filter(id=id).update(
            fecha_creacion=datetime.now().strftime("%Y-%m-%d"),
            fecha_factura=fecha,
            fecha_pago=fecha_pago if "fecha_pago" in request.POST else None,
            cliente=cliente,
            total=total_tickets,
            iva=iva_tickets,
            neto=neto_tickets,
            pagado=pagado,
        )

        with transaction.atomic():
            TicketFacturaVenta.objects.filter(factura=f).delete()

            nuevos_tickets = [
                TicketFacturaVenta(transaccion=t, factura=f) for t in tickets
            ]
            TicketFacturaVenta.objects.bulk_create(nuevos_tickets)
        mantener_personal_activo(request.POST["token"])
        registrar_accion(request.POST["token"], 1, 55, factura)
        # Facturar si corresponde
        if facturar == 1:
            resp, texto = facturar_factura(factura)
            if not resp:
                return Utiles.responder_500(texto)
            return Utiles.responder_200("Se ha modificado la factura", {"link": texto})

        return Utiles.responder_200("Factura modificada correctamente y pendiente de facturación")
    except FacturaVenta.DoesNotExist:
        return Utiles.responder_500("No existe factura de venta con ese id")
    except Cliente.DoesNotExist:
        return Utiles.responder_500("El cliente indicado no existe")
    except Exception as e:
        return Utiles.responder_500(f"Error inesperado: {str(e)}", sys.exc_info())



@api_view(["POST"])
def nota_credito_factura_venta(request):
    try:
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        campos_requeridos = ["id", "razon"]
        validacion_campos = validar_campos_requeridos(request, campos_requeridos)
        if validacion_campos != "ok":
            return Utiles.responder_500(validacion_campos)
        
        id = request.POST["id"]
        razon = request.POST["razon"]
        f = FacturaVenta.objects.get(id=id)
        if f.facturado == 0 or f.folio == "" or f.link == "" or f.folio == None or f.link == None or f.folio == 0:
            return Utiles.responder_500("El documento no ha sido facturado todavía")
        
        gresp, gxml, gparams = generar_nota_credito_factura(f.id, razon)
        if not gresp:
            return Utiles.responder_500(gxml)
        
        gparams["file"] = b64encode(gxml.encode("utf-8")).decode("utf-8")
        gparams["formato"] = "2" # 2 = XML
        eresp, etext = enviar_datos(gparams, 1)
        if not eresp:
            return Utiles.responder_500(etext)
        
        root = ET.fromstring(etext)
        fact_resp = root.find("./Resultado").text
        if fact_resp == "False":
            f.facturacioncl = etext
            f.save()
            return Utiles.responder_500("No fue posible eliminar el documento electrónico de pago")
        
        f.facturacioncl = etext
        f.folio = 0
        f.facturado = 0
        f.link = None
        f.save()

        # Elimina los tickets de esta factura
        TicketFacturaVenta.objects.filter(factura=f).delete()

        mantener_personal_activo(request.POST["token"])
        registrar_accion(request.POST["token"], 1, 56, f)
        return Utiles.responder_200("Se ha eliminado el documento")
        
    except FacturaVenta.DoesNotExist:
        return Utiles.responder_500("No existe factura con ese id")
    except:
        return Utiles.responder_500("Error inesperado", sys.exc_info())



@api_view(["GET"])
def ranking_mejores_peores_pagadores(request):
    try:
        # Validación de acceso
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], request=request, perfiles=[1, 2])
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        # Parámetros de fecha
        fecha_inicio = request.GET.get("fecha_inicio")
        fecha_fin = request.GET.get("fecha_fin")

        # Ya NO filtramos por fecha_pago__isnull=False, porque queremos incluir impagas
        filtros = {
            "cliente__isnull": False,
        }

        if fecha_inicio:
            try:
                fecha_inicio = datetime.strptime(fecha_inicio, "%Y-%m-%d")
                filtros["fecha_factura__gte"] = fecha_inicio
            except ValueError:
                return Utiles.responder_500("Formato de fecha_inicio inválido. Use YYYY-MM-DD")

        if fecha_fin:
            try:
                fecha_fin = datetime.strptime(fecha_fin, "%Y-%m-%d")
                filtros["fecha_factura__lte"] = fecha_fin
            except ValueError:
                return Utiles.responder_500("Formato de fecha_fin inválido. Use YYYY-MM-DD")

        # Orden dinámico por parámetro
        orden = request.GET.get("orden", "asc")
        if orden not in ["asc", "desc"]:
            return Utiles.responder_500("Parámetro 'orden' inválido. Use 'asc' o 'desc'")
        orden_ordenamiento = "dias_promedio_pago" if orden == "asc" else "-dias_promedio_pago"

        # "Fecha de pago efectiva" (si no hay fecha_pago => hoy)
        hoy = TruncDate(Now())  # eliminar la hora

        facturas = (
            FacturaVenta.objects
            .filter(**filtros)
            .annotate(
                fecha_pago_efectiva=Coalesce(F("fecha_pago"), hoy),
            )
            .annotate(
                dias_pago=ExpressionWrapper(
                    F("fecha_pago_efectiva") - F("fecha_factura"),
                    output_field=DurationField(),
                )
            )
            .values("cliente__id", "cliente__rut", "cliente__nombre")
            .annotate(
                dias_promedio_pago=Avg("dias_pago"),
                total_facturado=Sum("total"),
                facturas_utilizadas=Count("id"),
            )
            .order_by(orden_ordenamiento)
        )

        # Formateo de resultados (protegiendo nulls por si acaso)
        ranking = []
        for f in facturas:
            dp = f.get("dias_promedio_pago")
            dias = None
            if dp is not None:
                dias = round(dp.days + (dp.seconds / 86400), 2)

            ranking.append({
                "cliente_id": f["cliente__id"],
                "rut": f["cliente__rut"],
                "nombre": f["cliente__nombre"],
                "dias_promedio_pago": dias,
                "total_facturado": f["total_facturado"],
                "facturas_utilizadas": f["facturas_utilizadas"],
            })

        # Paginación
        page = request.GET.get("page", 1)
        paginator = Paginator(ranking, 20)
        try:
            paginated_ranking = paginator.page(page)
        except PageNotAnInteger:
            paginated_ranking = paginator.page(1)
        except EmptyPage:
            paginated_ranking = paginator.page(paginator.num_pages)

        respuesta = {
            "datos": list(paginated_ranking),
            "paginacion": {
                "pagina_actual": paginated_ranking.number,
                "total_paginas": paginator.num_pages,
                "total_elementos": paginator.count,
                "elementos_pagina_actual": f"{paginated_ranking.start_index()} al {paginated_ranking.end_index()}",
            }
        }

        return Utiles.responder_200("Ranking de mejores pagadores", respuesta)

    except Exception as e:
        return Utiles.responder_500({
            "error": "Error interno del servidor",
            "detalle": str(e)
        })



@api_view(["GET"])
def ver_tickets_no_facturados(request):
    try:
        # Validación de acceso
        validacion = validaciones_basicas(validaciones=[1, 2, 3, 4], perfiles=[1, 2], request=request)
        if validacion != "ok":
            return Utiles.responder_500(validacion)

        cliente_id = request.GET.get("cliente", "").strip()
        if not cliente_id:
            return Utiles.responder_500("Falta cliente")

        try:
            cliente = Cliente.objects.get(id=cliente_id)
        except Cliente.DoesNotExist:
            return Utiles.responder_500("No existe cliente con ese ID")
        
        obra_id = request.GET.get("obra", None)
        obra = None
        if obra_id:
            obra = Obra.objects.get(id=obra_id)
            if obra.cliente.id != cliente.id:
                return Utiles.responder_500("La obra no pertenece al cliente seleccionado")

        # === Validar fechas opcionales ===
        fecha_inicio_str = request.GET.get("fecha_inicio")
        fecha_termino_str = request.GET.get("fecha_termino")

        fecha_inicio = fecha_termino = None

        if fecha_termino_str and not fecha_inicio_str:
            return Utiles.responder_500("Debe ingresar 'fecha_inicio' si usa 'fecha_termino'.")

        try:
            if fecha_inicio_str:
                fecha_inicio = datetime.strptime(fecha_inicio_str, "%Y-%m-%d").date()
            if fecha_termino_str:
                fecha_termino = datetime.strptime(fecha_termino_str, "%Y-%m-%d").date()
        except ValueError:
            return Utiles.responder_500("Formato de fecha inválido. Use YYYY-MM-DD.")

        # Subconsulta para verificar ControlQr
        control_qr_exists = ControlQr.objects.filter(qr_id=OuterRef('id'))

        # Construir filtros
        filtros = Q(Exists(control_qr_exists)) & Q(transaccion__cliente=cliente)

        if obra:
            filtros &= Q(transaccion__obra=obra)

        if fecha_inicio:
            filtros &= Q(transaccion__fecha__date__gte=fecha_inicio)
        if fecha_termino:
            filtros &= Q(transaccion__fecha__date__lte=fecha_termino)

        # Obtener QR válidos
        qr_no_facturados_controlados = Qr.objects.filter(filtros).exclude(
            transaccion__ticketfacturaventa__isnull=False
        ).distinct()

        datos = [formatear_qr(q) for q in qr_no_facturados_controlados]
        mantener_personal_activo(request.GET["token"])
        return Utiles.responder_200("QR controlados y no facturados del cliente", datos)

    except Obra.DoesNotExist:
        return Utiles.responder_500("No existe obra con el id indicado")
    except Exception as e:
        return Utiles.responder_500(f"Error inesperado: {str(e)}", sys.exc_info())



def formatear_factura_cliente(factura):
    tickets = factura.ticketfacturaventa_set.all()

    glosa = []
    for ticket in tickets:
        transaccion = ticket.transaccion
        camion = getattr(transaccion, "camion", None)

        if camion:
            glosa.append({
                "transaccion": f"N{transaccion.id}",
                "patente": camion.patente,
                "cantidad": float(transaccion.cantidad),
                "unidad": transaccion.unidad or "m3",
                "valor": transaccion.total
            })

    return {
        "id": factura.id,
        "folio": factura.folio,
        "fecha_factura": factura.fecha_factura.strftime("%Y-%m-%d"),
        "fecha_pago": factura.fecha_pago.strftime("%Y-%m-%d %H:%M:%S") if factura.fecha_pago else None,
        "neto": factura.neto,
        "iva": factura.iva,
        "total": factura.total,
        "facturado": factura.facturado,
        "link": factura.link,
        "glosa": glosa
    }




def validar_campos_requeridos(request, campos):
    # Valida que los campos requeridos estén presentes en la solicitud
    for campo in campos:
        if campo not in request.POST:
            return f"Falta {campo}"
    return "ok"