"""
finalize_invoice_no_discharge / finalize_invoice_with_discharge
(TERMINA_AC_O_LP_NODES / TERMINA_AC_O_LP_NORMAL)

Last step of export invoice processing.  Both variants:
  1. TODO: DO REVISACLASESHABILITADAS
  2. Validate SisExp quantity / weight / value limits (min and max).
  3. If no errors: assign invoice-level totals and mark as PROCESSED.

The "with_discharge" variant additionally:
  4. DO GENERAIMPODEFINITIVA             (if is_regime_change and generate_id)
  5. DO REGISTRA_DESCARGA_IMPORTACION   (update returned qty/value on import lines)
  6. DO REGISTRA_DESCARGA_SERIES        (flag import series as exported)

The legacy 'Of LP' branch (print-preview / progress-bar UI) is not ported.
"""

import datetime
from decimal import Decimal
from typing import TYPE_CHECKING, List

from sqlalchemy.orm import Session

from .register_import_discharge import register_import_discharge
from .register_discharge_series import register_discharge_series
from .register_discharge_ledger import register_discharge_ledger

if TYPE_CHECKING:
    from .discharge_types import DownloadEntry

from api.v1.modules.a76.classes.models import Class
from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus
from api.v1.modules.a76.items.models import LineItem
from core.exceptions import ErrorCollector
from .review_limits import limit_weight, limit_value
from .generate_definitive_import import generate_definitive_import


# ---------------------------------------------------------------------------
# REVISACLASESHABILITADAS
# ---------------------------------------------------------------------------

def _review_enabled_classes(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    errors: ErrorCollector,
) -> None:
    """
    REVISACLASESHABILITADAS
    Verifies that every line item's class is active (not disabled).

    Clarion: loops QEqeMaq for the invoice, fetches QClaAct by class code,
    errors if HabilitaDeshabilitaClase = 1  →  Python: Class.is_active = False.
    """
    for line in lines:
        if not line.class_id:
            continue
        cls: Class | None = db.get(Class, line.class_id)
        if cls is not None and cls.is_active is False:
            errors.add_error(
                field=f"line[{line.line_number}].class",
                message=(
                    f"La Clase: '{cls.class_code}' esta desactivada, "
                    "no se pueden hacer movimientos."
                ),
                solution=["Seleccionar una clase activa."],
                code="CLASS_DISABLED",
            )


# ---------------------------------------------------------------------------
# SisExp limit checks  (shared by both public functions)
# ---------------------------------------------------------------------------

def _validate_sisexp_limits(
    invoice: InvoiceHeader,
    total_qty: Decimal,
    total_net_weight: Decimal,
    total_value: Decimal,
    errors: ErrorCollector,
) -> None:
    """
    Validates invoice totals against the SisExp min/max limit parameters.

    TODO: Read actual SisExp parameters from the tenant system-config model.
          Until then all limits default to 0 (= disabled) so no checks fire.

    Clarion names → Python (TODO):
      SisExp:CantLimiteMin  / SisExp:CantLimite   → qty    min / max
      SisExp:PesoLimiteMin  / SisExp:PesoLimite   → weight min / max
      SisExp:ValorLimiteMin / SisExp:ValorLimite  → value  min / max
    """
    # TODO: load from SisExp tenant config
    cant_limite_min:  Decimal = Decimal(0)
    cant_limite:      Decimal = Decimal(0)
    peso_limite_min:  Decimal = Decimal(0)
    peso_limite:      Decimal = Decimal(0)
    valor_limite_min: Decimal = Decimal(0)
    valor_limite:     Decimal = Decimal(0)

    solution = ["Consulte a su Administrador de sistema para parametrizar la factura."]
    code     = "PAR.EXPO"

    if cant_limite_min != 0 and cant_limite_min > total_qty:
        errors.add_error(
            field="invoice.total_quantity",
            message=(
                f"La cantidad total de la factura: {total_qty} "
                f"no supera a la cantidad mínima parametrizada: {cant_limite_min}."
            ),
            solution=solution, code=code,
        )
    if cant_limite != 0 and cant_limite < total_qty:
        errors.add_error(
            field="invoice.total_quantity",
            message=(
                f"La cantidad total de la factura: {total_qty} "
                f"excede a la cantidad máxima parametrizada: {cant_limite}."
            ),
            solution=solution, code=code,
        )
    if peso_limite_min != 0 and peso_limite_min > total_net_weight:
        errors.add_error(
            field="invoice.net_weight",
            message=(
                f"El Peso Neto total de la factura: {total_net_weight} "
                f"no supera el Peso mínimo parametrizado: {peso_limite_min}."
            ),
            solution=solution, code=code,
        )
    if peso_limite != 0 and peso_limite < total_net_weight:
        errors.add_error(
            field="invoice.net_weight",
            message=(
                f"El Peso Neto total de la factura: {total_net_weight} "
                f"excede el Peso máximo parametrizado: {peso_limite}."
            ),
            solution=solution, code=code,
        )
    if valor_limite_min != 0 and valor_limite_min > total_value:
        errors.add_error(
            field="invoice.total_value",
            message=(
                f"El Valor total de la factura: {total_value} "
                f"no supera el Valor mínimo parametrizado: {valor_limite_min}."
            ),
            solution=solution, code=code,
        )
    if valor_limite != 0 and valor_limite < total_value:
        errors.add_error(
            field="invoice.total_value",
            message=(
                f"El Valor total de la factura: {total_value} "
                f"excede el Valor máximo parametrizado: {valor_limite}."
            ),
            solution=solution, code=code,
        )


# ---------------------------------------------------------------------------
# DO ASIGNA_VALORES_FACTURA
# ---------------------------------------------------------------------------

def _assign_invoice_totals(
    invoice: InvoiceHeader,
    lines: List[LineItem],
) -> None:
    """
    DO ASIGNA_VALORES_FACTURA
    Aggregates line-level values (MN, ME, qty, packages, net/gross weight)
    and writes the totals to the invoice header, then marks it as PROCESSED.

    Clarion equivalent:
      SELECT SUM(ValorExpoMN), SUM(ValorExpoME), SUM(CantExpo),
             SUM(CantBultos),  SUM(PesoNeto),    SUM(PesoBruto)
      FROM   QEqeMaq WHERE Consecutivo = <invoice.id>

    TODO: SisGen:CalValBaseTCPedExpo = 1 → invoice.financials.exchange_rate = Loc:TipoCambio
    TODO: SisGen:CalValBaseTCPedExpo = 1 →
          invoice.process_log = 'Se Actualizó con el Tipo de Cambio de la Fecha de Pago de Pedimento.'
    TODO: SisGen:ActSeguridad = 1 → invoice.updated_by = current_user
    """
    total_value_mn  = Decimal(0)
    total_value_me  = Decimal(0)
    total_qty       = Decimal(0)
    total_packages  = 0
    total_net_weight   = Decimal(0)
    total_gross_weight = Decimal(0)

    for line in lines:
        if line.financial:
            total_value_mn += Decimal(str(line.financial.value_mxn or 0))
            total_value_me += Decimal(str(line.financial.value_usd or 0))
        if line.quantity:
            total_qty          += line.quantity.quantity        or Decimal(0)
            total_packages     += line.quantity.package_quantity or 0
            total_net_weight   += line.quantity.net_weight      or Decimal(0)
            total_gross_weight += line.quantity.gross_weight    or Decimal(0)

    if invoice.financials is not None:
        invoice.financials.value_mn      = float(total_value_mn)
        invoice.financials.value_me      = float(total_value_me)
        invoice.financials.total_quantity  = float(total_qty)
        invoice.financials.total_packages  = total_packages
        invoice.financials.net_weight      = float(total_net_weight)
        invoice.financials.gross_weight    = float(total_gross_weight)

    invoice.party_count  = len([l for l in lines if not (l.fa_data and l.fa_data.is_subitem)])
    invoice.updated_date = datetime.date.today()
    invoice.status       = InvoiceStatus.PROCESSED


# ---------------------------------------------------------------------------
# Public entry points
# ---------------------------------------------------------------------------

def finalize_invoice_no_discharge(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    errors: ErrorCollector,
) -> None:
    """
    TERMINA_AC_O_LP_NODES
    Finalizes a NODES-type export invoice (no inventory discharge).

    Flow:
      1. Verify all line classes are active (REVISACLASESHABILITADAS).
      2. Validate SisExp limits (qty / weight / value).
      3. If no errors: write invoice totals and set status = PROCESSED.
    """
    _review_enabled_classes(db, invoice, lines, errors)

    total_qty, total_net_weight = limit_weight(lines)
    total_value                 = limit_value(lines)

    _validate_sisexp_limits(invoice, total_qty, total_net_weight, total_value, errors)

    if not errors.has_errors():
        _assign_invoice_totals(invoice, lines)


def finalize_invoice_with_discharge(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    errors: ErrorCollector,
    to_discharge: List["DownloadEntry"] | None = None,
) -> None:
    """
    TERMINA_AC_O_LP_NORMAL
    Finalizes a discharge-type export invoice (AFIJO / DONAC / SCRAP / REEXP / VEMEX).

    Flow:
      1. Verify all line classes are active (REVISACLASESHABILITADAS).
      2. Validate SisExp limits (qty / weight / value).
      3. If no errors:
         a. DO GENERAIMPODEFINITIVA        (only if is_regime_change and generate_id)
         b. TODO: DO REGISTRA_DESCARGA_IMPORTACION  (write a24 discharge movements)
         c. TODO: DO REGISTRA_DESCARGA_SERIES       (write series discharge records)
         d. Write invoice totals and set status = PROCESSED.

    Note: the legacy 'Of LP' branch (print-preview UI) is not ported.
    """
    _review_enabled_classes(db, invoice, lines, errors)

    total_qty, total_net_weight = limit_weight(lines)
    total_value                 = limit_value(lines)

    _validate_sisexp_limits(invoice, total_qty, total_net_weight, total_value, errors)

    if not errors.has_errors():
        if invoice.compliance_mx and invoice.compliance_mx.is_regime_change and invoice.generate_id:
            generate_definitive_import(db, invoice, errors)

        if to_discharge:
            # Write BalanceMovement (CONSUMPTION) + DischargeHeader + DischargeDetail
            register_discharge_ledger(db, invoice, to_discharge)
            # Update quantity_returned / value_returned on the import lines
            register_import_discharge(db, invoice, to_discharge)
            register_discharge_series(db, invoice, to_discharge)

        _assign_invoice_totals(invoice, lines)
