"""
COMPARA_SALDOS_POR_FACTURA
Compares the discharge queue (QueADescargar) against the available PEPS lots
(QSaldoActual) and distributes the quantity to discharge across the available
lots, updating ``entry.quantity_used`` and ``lot.available_qty`` accordingly.

Also validates that the unit of measure on the export line matches the one
on the import lot.

If after consuming all available lots a discharge entry still has remaining
quantity, no explicit error is raised here — the caller (compare_balances)
detects this and reports an insufficient-balance error.

Legacy mapping
--------------
QADesc:Cantidad      → entry.quantity
QADesc:CantUsada     → entry.quantity_used
QSaldo:Cantidad      → lot.available_qty  (net balance from ledger)
QSaldo:CantUsada     → lot_used (tracked locally; lots are mutated in-place)
QADesc:UniMed        → entry.unit_of_measure
QSaldo:UniMed        → resolved from import line (stored on AvailableLot via uom)
"""

from decimal import Decimal
from typing import List, Optional

from sqlalchemy import select
from sqlalchemy.orm import Session

from api.v1.modules.a76.invoices.models import InvoiceHeader
from api.v1.modules.a76.items.models import LineItem
from core.exceptions import ErrorCollector
from .discharge_types import AvailableLot, DownloadEntry


# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------

def _resolve_import_uom(db: Session, import_item_line_id: int) -> Optional[str]:
    """
    Returns the unit-of-measure code of the import line (QSaldo:UniMed).
    Equivalent to EqiPim:UnidadMedida resolved via the import LineItem.
    """
    from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure

    row = db.execute(
        select(LineItem.unit_of_measure).where(LineItem.id == import_item_line_id)
    ).scalar_one_or_none()

    if row is None:
        return None

    uom = db.get(UnitOfMeasure, row)
    return uom.code if uom else None


# ---------------------------------------------------------------------------
# Public entry point
# ---------------------------------------------------------------------------

def compare_balances(
    db: Session,
    export_invoice: InvoiceHeader,
    to_discharge: List[DownloadEntry],
    errors: ErrorCollector,
) -> None:
    """
    COMPARA_SALDOS_POR_FACTURA
    For each discharge entry distributes the quantity to discharge across the
    available PEPS lots attached to the entry by ``fill_available_balances``.

    Mutates ``entry.quantity_used`` and ``lot.available_qty`` in-place.
    After this call, ``entry.quantity_used`` should equal ``entry.quantity``
    for every entry; if not, there is insufficient balance.

    Parameters
    ----------
    db              : active SQLAlchemy session
    export_invoice  : the export invoice being processed
    to_discharge    : list of DownloadEntry objects populated by
                      fill_available_balances (entry.available_lots must be set)
    errors          : shared error collector
    """
    # Sort mirrors Clarion:
    # Sort(QueADescargar, -Procedencia, FacturaImpo, LineaImpo)
    # Sort(QSaldoActual,  -Procedencia, FacturaImpo, LineaImpo)
    sorted_entries = sorted(
        to_discharge,
        key=lambda e: (e.origin_procedure, e.import_invoice, e.import_line),
        reverse=True,
    )

    for entry in sorted_entries:
        if not entry.available_lots:
            # No lots were found for this entry — balance check will catch it
            continue

        # ── Validate unit of measure matches the import lot ───────────────────
        first_lot = entry.available_lots[0]
        import_uom = _resolve_import_uom(db, first_lot.import_item_line_id)

        if import_uom and entry.unit_of_measure and import_uom != entry.unit_of_measure:
            errors.add_error(
                field=f"line[{entry.export_line}].unit_of_measure",
                message=(
                    f"La U.M.: '{entry.unit_of_measure}' de la partida: {entry.export_line} "
                    f"es diferente a la U.M: '{import_uom}' registrada en importación."
                ),
                solution=["Revisar la Partida de Exportación y cambiar la Unidad de Medida."],
                code="UOM_MISMATCH",
                value={
                    "export_line": entry.export_line,
                    "export_uom": entry.unit_of_measure,
                    "import_uom": import_uom,
                },
            )
            continue

        # ── Distribute quantity across available lots (PEPS order) ────────────
        # Lots are already ordered by order_peps (oldest first) from
        # fill_available_balances; sort defensively here too.
        sorted_lots: List[AvailableLot] = sorted(
            entry.available_lots, key=lambda lot: lot.order_peps
        )

        for lot in sorted_lots:
            remaining_entry = entry.quantity - entry.quantity_used
            remaining_lot   = lot.available_qty

            if remaining_entry <= 0:
                break  # Entry fully satisfied

            if remaining_lot <= 0:
                continue  # Lot exhausted — try next

            # Consume as much as possible from this lot
            consume = min(remaining_entry, remaining_lot)

            entry.quantity_used  += consume
            lot.available_qty    -= consume
            lot.consumed_qty     += consume

        # ── Check if the entry was fully satisfied ────────────────────────────
        if entry.quantity_used < entry.quantity:
            shortage = entry.quantity - entry.quantity_used
            errors.add_error(
                field=f"line[{entry.export_line}].quantity",
                message=(
                    f"Saldo insuficiente para la partida: {entry.export_line}. "
                    f"Se requieren {entry.quantity} y solo hay {entry.quantity_used} disponibles "
                    f"(faltan {shortage})."
                ),
                solution=[
                    "Verificar el saldo disponible de la Factura de Importación.",
                    "Reducir la cantidad a descargar.",
                ],
                code="INSUFFICIENT_BALANCE",
                value={
                    "export_line": entry.export_line,
                    "required": str(entry.quantity),
                    "available": str(entry.quantity_used),
                    "shortage": str(shortage),
                },
            )
