from __future__ import annotations

import base64
from io import BytesIO
import os
from urllib.parse import unquote, urlparse

from django.core.exceptions import ObjectDoesNotExist
from django.core.files.base import ContentFile
from django.conf import settings
from django.template.loader import get_template
from django.utils import timezone
import qrcode

from apps.boletas.models import Boleta
from apps.boletas.utils import build_qr_text
from apps.catalogos.models import ProductorOficina


def _fmt_peso(value) -> str:
    if value is None:
        return "________"
    try:
        return f"{float(value):,.2f} KG"
    except (TypeError, ValueError):
        return str(value)


def build_boleta_pdf_context(boleta: Boleta) -> dict:
    dt = timezone.localtime(boleta.fecha_emision) if boleta.fecha_emision else None

    if dt:
        fecha = dt.strftime("%d/%b/%Y").upper()
        hora = dt.strftime("%I:%M %p").upper().replace(".", "")
    else:
        fecha = "________"
        hora = "____"

    productor = boleta.productor
    contrato = boleta.contrato
    variedad = getattr(boleta, "variedad", None)
    linea = getattr(boleta, "linea", None)

    contrato_numero = getattr(contrato, "numero_contrato", None) or "________"
    clave_linea = getattr(linea, "codigo", None) or "________"

    oficina_id = None
    if getattr(boleta, "oficina_id", None):
        oficina_id = boleta.oficina_id
    elif getattr(contrato, "oficina_id", None):
        oficina_id = contrato.oficina_id
    elif getattr(boleta, "zafra_id", None) and getattr(boleta, "zafra", None) is not None:
        oficina_id = getattr(boleta.zafra, "oficina_id", None)

    try:
        recepcion = boleta.recepcion
    except ObjectDoesNotExist:
        recepcion = None

    centro_recepcion = "________"
    if recepcion is not None and getattr(recepcion, "oficina", None) is not None:
        centro_recepcion = getattr(recepcion.oficina, "nombre", None) or str(recepcion.oficina)
        oficina_id = getattr(recepcion, "oficina_id", None) or oficina_id
    elif getattr(boleta, "oficina", None) is not None:
        centro_recepcion = getattr(boleta.oficina, "nombre", None) or str(boleta.oficina)

    productor_clave = None
    if oficina_id is not None:
        productor_clave = (
            ProductorOficina.objects.filter(productor_id=productor.id, oficina_id=oficina_id)
            .values_list("codigo_sap", flat=True)
            .first()
        )
        productor_clave = (productor_clave or "").strip() or None
    if not productor_clave:
        productor_clave = productor.rfc if getattr(productor, "rfc", None) else str(productor.id)

    peso_bascula = "________"
    if recepcion is not None:
        try:
            pesos = list(recepcion.pesos.all())
        except Exception:
            pesos = []
        lleno = next((p for p in pesos if getattr(p, "tipo", None) == "lleno"), None)
        vacio = next((p for p in pesos if getattr(p, "tipo", None) == "vacio"), None)
        bascula_valor = None
        if lleno is not None and vacio is not None:
            try:
                bascula_valor = lleno.valor - vacio.valor
            except Exception:
                bascula_valor = lleno.valor
        elif lleno is not None:
            bascula_valor = lleno.valor
        if bascula_valor is not None:
            peso_bascula = _fmt_peso(bascula_valor)

    bodega = getattr(recepcion, "bodega_destino", None) if recepcion is not None else None
    bodega_destino_nombre = "________"
    bodega_destino_codigo = "________"
    bodega_destino_ubicacion = "________"
    if bodega is not None:
        if getattr(bodega, "nombre", None):
            bodega_destino_nombre = bodega.nombre
        if getattr(bodega, "codigo", None):
            bodega_destino_codigo = bodega.codigo
        if getattr(bodega, "ubicacion", None):
            bodega_destino_ubicacion = bodega.ubicacion

    firmas_recepcion = recepcion.firmas if recepcion and recepcion.firmas else {}
    firma_chofer = firmas_recepcion.get("firma_chofer") if firmas_recepcion else None

    peso_origen_estimado = "________"
    if boleta.peso_estimado is not None:
        peso_origen_estimado = _fmt_peso(boleta.peso_estimado)
    elif contrato is not None and variedad is not None:
        try:
            cv = contrato.variedades.get(variedad=variedad)
            if cv.peso_teorico is not None:
                peso_origen_estimado = _fmt_peso(cv.peso_teorico)
        except ObjectDoesNotExist:
            pass

    origen_trigo = boleta.origen_trigo if boleta.origen_trigo else "________"
    campo = "________"
    if getattr(boleta, "campo", None) is not None:
        campo = getattr(boleta.campo, "nombre", None) or str(boleta.campo)
    else:
        campo = origen_trigo
    linea_nombre = boleta.linea_nombre if boleta.linea_nombre else "________"
    marca_unidad = boleta.marca_unidad if boleta.marca_unidad else "________"
    modelo_unidad = boleta.modelo_unidad if boleta.modelo_unidad else "________"

    qr_texto = (boleta.qr_texto or "").strip()
    if not qr_texto and boleta.zafra_id and boleta.folio:
        qr_texto = build_qr_text(boleta.zafra_id, boleta.folio)

    observaciones = (recepcion.observaciones if recepcion and recepcion.observaciones else " ").strip()
    if not observaciones:
        observaciones = " "

    return {
        "boleta": boleta,
        "folio": boleta.folio,
        "fecha": fecha,
        "hora": hora,
        "contrato": contrato_numero,
        "productor_clave": productor_clave,
        "productor_nombre": productor.nombre,
        "variedad_trigo": variedad.nombre if variedad else "________",
        "peso_origen_estimado": peso_origen_estimado,
        "origen_trigo": origen_trigo,
        "clave_productor": productor_clave,
        "nombre_productor": productor.nombre,
        "campo": campo,
        "peso_estimado": peso_origen_estimado,
        "peso_bascula": peso_bascula,
        "clave_linea": clave_linea,
        "carta_porte": " ",
        "centro_recepcion": centro_recepcion,
        "chofer_nombre": boleta.chofer_nombre if boleta.chofer_nombre else "________",
        "placas_camion": boleta.placas_camion if boleta.placas_camion else "________",
        "linea_nombre": linea_nombre,
        "marca_unidad": marca_unidad,
        "modelo_unidad": modelo_unidad,
        "bodega_destino_nombre": bodega_destino_nombre,
        "bodega_destino_codigo": bodega_destino_codigo,
        "bodega_destino_ubicacion": bodega_destino_ubicacion,
        "observaciones": observaciones,
        "firma_productor": None,
        "firma_tecnico": None,
        "firma_chofer": firma_chofer,
        "qr_texto": qr_texto,
        "qr_image_src": None,
        "qr_code": None,
    }


def _prepare_weasyprint():
    # WeasyPrint depende de librerías nativas (Pango/GObject).
    # En Windows se cargan desde MSYS2 vía add_dll_directory;
    # en Linux/macOS se resuelven del sistema y no hay que hacer nada.
    if os.name == "nt":
        for dll_dir in (
            os.environ.get("WEASYPRINT_DLL_DIR"),
            r"C:\msys64\ucrt64\bin",
            r"C:\msys64\mingw64\bin",
        ):
            if dll_dir and os.path.isdir(dll_dir):
                try:
                    os.add_dll_directory(dll_dir)
                except OSError:
                    pass
                break
    from weasyprint import HTML, default_url_fetcher

    return HTML, default_url_fetcher


def _make_url_fetcher(default_url_fetcher):
    static_url = getattr(settings, "STATIC_URL", None) or ""
    static_root = getattr(settings, "STATIC_ROOT", None)
    media_url = getattr(settings, "MEDIA_URL", None) or ""
    media_root = getattr(settings, "MEDIA_ROOT", None)

    def _fetcher(url):
        parsed = urlparse(url)
        path = unquote(parsed.path)

        for prefix, root in ((static_url, static_root), (media_url, media_root)):
            if root and prefix and path.startswith(prefix):
                rel = path[len(prefix):]
                local = os.path.join(str(root), *rel.split("/"))
                return default_url_fetcher(local if parsed.scheme == "file" else f"file://{local}")

        # URL tipo "static/..." relativa al BASE_DIR (usada en el template)
        if parsed.scheme not in ("http", "https", "data") and static_root:
            local = os.path.join(str(static_root), *path.lstrip("/").split("/"))
            if os.path.exists(local):
                return default_url_fetcher(f"file://{local}")

        return default_url_fetcher(url)

    return _fetcher


def _qr_data_uri(texto: str) -> str:
    buf = BytesIO()
    qr = qrcode.QRCode(border=1, box_size=10)
    qr.add_data(texto)
    qr.make(fit=True)
    qr.make_image().save(buf, format="PNG")
    return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode("ascii")


def build_boleta_pdf_bytes(boleta: Boleta) -> bytes:
    HTML, default_url_fetcher = _prepare_weasyprint()

    template = get_template("boletas/boleta_pdf.html")
    ctx = build_boleta_pdf_context(boleta)

    qr_texto = (ctx.get("qr_texto") or "").strip()
    if qr_texto:
        ctx["qr_image_src"] = _qr_data_uri(qr_texto)
        ctx["qr_code"] = ctx["qr_image_src"]

    html = template.render(ctx)
    return HTML(
        string=html,
        base_url=str(settings.BASE_DIR),
        url_fetcher=_make_url_fetcher(default_url_fetcher),
    ).write_pdf()


def attach_boleta_pdf(boleta: Boleta, pdf_bytes: bytes, *, filename: str | None = None) -> Boleta:
    if not filename:
        filename = f"boleta_{boleta.id}_{boleta.folio}.pdf"

    boleta.pdf_generado.save(filename, ContentFile(pdf_bytes), save=True)
    return boleta
