from __future__ import annotations

from decimal import Decimal

from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import get_template
from django.utils import formats
from django.utils import timezone

from apps.catalogos.models import ProductorOficina
from apps.recepciones.models import Flete, PesoFlete, PesoRecepcion, Recepcion


def _format_kg(value: Decimal | float | int | None) -> str:
    if value is None:
        return "0.00 KG"
    try:
        return f"{float(value):,.2f} KG"
    except (TypeError, ValueError):
        return f"{value} KG"


def _format_money(value: Decimal | float | int | None) -> str:
    if value is None:
        return "$ 0.00"
    try:
        return f"$ {float(value):,.2f}"
    except (TypeError, ValueError):
        return f"$ {value}"


def _format_percent(value: Decimal | float | int | None) -> str:
    if value is None:
        return "0.00 %"
    try:
        return f"{float(value):.2f} %"
    except (TypeError, ValueError):
        return f"{value} %"


def build_boleta_entrada_pdf_context(recepcion: Recepcion) -> dict:
    boleta = recepcion.boleta
    productor = boleta.productor
    flete = recepcion.flete

    # Cuando la recepción pertenece a un flete, el muestreo y los pesos del camión son compartidos.
    if flete:
        muestreo = getattr(flete, "muestreo", None)
        pesos = list(flete.pesos.all())
        peso_lleno = next((p for p in pesos if p.tipo == PesoFlete.TIPO_LLENO), None)
        peso_vacio = next((p for p in pesos if p.tipo == PesoFlete.TIPO_VACIO), None)
        camion_source = flete
    else:
        muestreo = getattr(recepcion, "muestreo", None)
        pesos = list(recepcion.pesos.all())
        peso_lleno = next((p for p in pesos if p.tipo == PesoRecepcion.TIPO_LLENO), None)
        peso_vacio = next((p for p in pesos if p.tipo == PesoRecepcion.TIPO_VACIO), None)
        camion_source = boleta

    resultado = getattr(muestreo, "resultado_laboratorio", None) if muestreo else None

    dt = timezone.localtime(recepcion.fecha_cierre) if recepcion.fecha_cierre else timezone.now()
    fecha = formats.date_format(dt, "d/M/Y").upper()
    hora = formats.date_format(dt, "h:i A")

    # Clave del productor: código SAP por oficina, o RFC, o id
    oficina_id = getattr(recepcion, "oficina_id", None) or getattr(boleta, "oficina_id", None)
    productor_clave = ""
    if oficina_id:
        productor_clave = (
            ProductorOficina.objects.filter(productor_id=productor.id, oficina_id=oficina_id)
            .values_list("codigo_sap", flat=True)
            .first()
            or ""
        )
    productor_clave = (productor_clave or getattr(productor, "rfc", "") or str(productor.id)).strip()

    # Pesos
    peso_bruto_kg = Decimal("0")
    tara_kg = Decimal("0")
    peso_neto_kg = Decimal("0")
    if peso_lleno and peso_vacio:
        peso_bruto_kg = Decimal(str(peso_lleno.valor))
        tara_kg = Decimal(str(peso_vacio.valor))
        if flete and recepcion.peso_neto_asignado is not None:
            peso_neto_kg = Decimal(str(recepcion.peso_neto_asignado))
        else:
            peso_neto_kg = peso_bruto_kg - tara_kg

    # Deducciones y bonificaciones
    deducciones_qs = list(recepcion.deducciones.select_related("parametro").all())
    bonificaciones_qs = list(recepcion.bonificaciones.select_related("parametro").all())

    total_deducciones_kg = sum((d.valor_deducido for d in deducciones_qs), Decimal("0"))
    total_bonificaciones = sum((b.monto_total for b in bonificaciones_qs), Decimal("0"))
    peso_analizado_kg = peso_neto_kg - total_deducciones_kg

    # Análisis: unir parámetros de laboratorio con deducciones correspondientes
    deducciones_por_parametro = {d.parametro_id: d for d in deducciones_qs}
    analisis = []
    if resultado:
        for pr in resultado.parametros.select_related("parametro").all():
            parametro = pr.parametro
            ded = deducciones_por_parametro.get(parametro.id)
            if pr.valor is None and ded is None:
                continue

            unidad = pr.unidad or parametro.unidad or "%"
            valor = pr.valor if pr.valor is not None else Decimal("0")

            if ded is not None:
                deduccion_str = _format_kg(ded.valor_deducido)
            elif parametro.nombre.upper() == "HUMEDAD":
                deduccion_str = _format_money(0)
            else:
                deduccion_str = _format_kg(0)

            analisis.append(
                {
                    "parametro": parametro.nombre.upper(),
                    "valor": f"{float(valor):.2f}" if unidad.upper() in {"%", "K/HI", "K/HL"} else str(valor),
                    "unidad": unidad.upper(),
                    "deduccion": deduccion_str,
                }
            )

    # Si no hay resultado de laboratorio, mostrar al menos deducciones calculadas
    if not analisis:
        for ded in deducciones_qs:
            analisis.append(
                {
                    "parametro": ded.parametro.nombre.upper(),
                    "valor": "0.00",
                    "unidad": ded.parametro.unidad.upper() or "%",
                    "deduccion": _format_kg(ded.valor_deducido),
                }
            )

    # Tipo de productor (no existe campo actual; por defecto ambos vacíos)
    alerta = recepcion.alerta or {}
    tipo_productor = alerta.get("tipo_productor", "") if isinstance(alerta, dict) else ""
    tipo_ejidalario_marcado = "X" if tipo_productor == "ejidalario" else ""
    tipo_propietario_marcado = "X" if tipo_productor == "propietario" else ""

    bodega = recepcion.bodega_destino
    bodega_str = ""
    if bodega:
        bodega_str = f"{bodega.codigo} - {bodega.nombre}" if bodega.codigo else bodega.nombre

    origen = ""
    if getattr(boleta, "campo", None):
        origen = boleta.campo.nombre
    if not origen:
        origen = camion_source.origen_trigo or ""

    return {
        "recepcion": recepcion,
        "boleta": boleta,
        "fecha": fecha,
        "hora": hora,
        "folio": boleta.folio,
        "productor_clave": productor_clave,
        "productor_nombre": productor.nombre,
        "tipo_ejidalario_marcado": tipo_ejidalario_marcado,
        "tipo_propietario_marcado": tipo_propietario_marcado,
        "origen": origen.upper(),
        "variedad": getattr(camion_source.variedad, "nombre", "").upper() if flete else getattr(boleta.variedad, "nombre", "").upper(),
        "bodega": bodega_str.upper(),
        "camion": camion_source.linea_nombre.upper() or f"{camion_source.marca_unidad} {camion_source.modelo_unidad}".upper(),
        "placas": camion_source.placas_camion.upper(),
        "conductor": camion_source.chofer_nombre.upper(),
        "observaciones": (recepcion.observaciones or "").upper(),
        "peso_bruto": _format_kg(peso_bruto_kg),
        "tara": _format_kg(tara_kg),
        "peso_neto": _format_kg(peso_neto_kg),
        "total_deducciones": _format_kg(total_deducciones_kg),
        "total_bonificaciones": _format_money(total_bonificaciones),
        "peso_analizado": _format_kg(peso_analizado_kg),
        "analisis": analisis,
    }


def _render_pdf(html_docs: list[str]) -> bytes:
    from apps.boletas.pdf import _make_url_fetcher, _prepare_weasyprint

    HTML, default_url_fetcher = _prepare_weasyprint()
    fetcher = _make_url_fetcher(default_url_fetcher)
    base_url = str(settings.BASE_DIR)

    documents = [HTML(string=html, base_url=base_url, url_fetcher=fetcher).render() for html in html_docs]
    first = documents[0]
    if len(documents) == 1:
        return first.write_pdf()
    return first.copy([page for doc in documents for page in doc.pages]).write_pdf()


def build_boleta_entrada_pdf_bytes(recepcion: Recepcion) -> bytes:
    template = get_template("recepciones/boleta_entrada_pdf.html")
    return _render_pdf([template.render(build_boleta_entrada_pdf_context(recepcion))])


def build_flete_boleta_entrada_pdf_bytes(flete: Flete) -> bytes:
    """
    Genera un único PDF de boleta de entrada para todas las recepciones de un flete.
    Cada recepción se renderiza con el mismo template, cambiando únicamente los
    datos del productor/boleta y conservando los datos compartidos del camión.
    Cada boleta ocupa su propia página dentro del PDF.
    """
    recepciones = list(
        flete.recepciones.select_related("boleta", "boleta__productor", "bodega_destino")
        .prefetch_related("deducciones", "bonificaciones")
        .order_by("id")
    )
    if not recepciones:
        raise ValueError("El flete no tiene recepciones.")

    template = get_template("recepciones/boleta_entrada_pdf.html")
    return _render_pdf([template.render(build_boleta_entrada_pdf_context(r)) for r in recepciones])