
from typing import List

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 .pre_validators import pre_validators
from .sub_process.assign_no_discharges import assign_no_discharges_items, assign_no_discharges_series
from .sub_process.review_class import review_class
from .sub_process.review_exchange_rate import review_exchange_rate
from .sub_process.assign_values import assign_values
from .sub_process.review_exchange_rate import review_exchange_rate
from .sub_process.review_qty_vs_weight import review_qty_vs_weight
from .sub_process.review_unit_cost import review_unit_cost
from .sub_process.review_limits import limit_weight, limit_value
from .sub_process.series.review_qty_series import review_qty_series
from .sub_process.download_balance_collector import collect_lines_to_discharge
from .sub_process.discharge_types import DownloadEntry
from .sub_process.finalize_invoice import (
    finalize_invoice_no_discharge,
    finalize_invoice_with_discharge,
)
from .sub_process.review_origin_procedure import review_origin_procedure
from .sub_process.fill_available_balances import fill_available_balances
from .sub_process.compare_balances import compare_balances
from .sub_process.verify_consolidated import verify_consolidated
from .sub_process.generate_definitive_import import (
    generate_definitive_import,
    generate_definitive_import_all_lines,
)

# ---------------------------------------------------------------------------
# Bloque reutilizable: descarga normal (AFIJO / DONAC / SCRAP / REEXP / VEMEX)
# ---------------------------------------------------------------------------

def _process_with_discharge(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    errors: ErrorCollector,
) -> None:
    """
    Secuencia común para los tipos de factura que realizan descarga de saldos:
    AFIJO, DONAC, SCRAP, REEXP, VEMEX.
    """
    assign_no_discharges_series(db, lines, errors)
    review_class(db, lines, errors)
    review_exchange_rate(db, invoice, errors)
    assign_values(db, invoice, lines, invoice.tenant_id, invoice.company_id, errors)

    review_qty_vs_weight(lines, invoice.logistics.weight_type.upper(), errors)

    review_unit_cost(lines, errors)
    total_qty, total_net_weight = limit_weight(lines)
    total_value = limit_value(lines)
    review_qty_series(db, invoice, lines, invoice.tenant_id, invoice.company_id, errors)

    # QUIERE_DESCARGAR → LLENA_QUEUE_SALDOS → COMPARA_SALDOS
    to_discharge = collect_lines_to_discharge(db, invoice, lines, errors)

    fill_available_balances(db, invoice, to_discharge, errors)
    compare_balances(db, invoice, to_discharge, errors)
    verify_consolidated(db, invoice, to_discharge, errors)

    finalize_invoice_with_discharge(db, invoice, lines, errors, to_discharge)


# ---------------------------------------------------------------------------
# Proceso principal
# ---------------------------------------------------------------------------

def main_process(db: Session, invoice: InvoiceHeader, tenant_id: str, company_id: str) -> dict:
    """
    Proceso principal para actualizar facturas de exportación.

    Flujo (porta la rutina principal del legacy SCAII – Facturas de Exportación):

    1. Validaciones previas (pre_validators)
    2. TODO: Compartir parámetros generales (QSisGen / GEmpresa)
    3. TODO: Compartir parámetros de exportación (QSisExpo) según EsCambioRegimen
    4. TODO: Validar permisos de usuario (GUsuarios / GNivelesSeguridad)
    5. TODO: Iniciar transacción SQL (BEGIN TRAN)
    6. Verificar que existan partidas
    7. TODO: Obtener tipo de cambio según SisGen:CalValBaseTCPedExpo
       (TCPED desde la fecha de pago del pedimento, o TCFAC desde la factura)
    8. TODO: Validar que la factura no exista ya en Importaciones Definitivas (si GeneraID='S')
    9. CASE invoice_type → ejecutar sub-proceso específico por tipo:
       - NODES : sin descarga
       - AFIJO / DONAC / SCRAP : con descarga + lógica de CambioRegimen opcional
       - REEXP / VEMEX          : con descarga + revisión de procedencia DEF
    10. Si hay errores: rollback implícito (raise)
        Si no hay errores: COMMIT y marcar factura como procesada
    """
    errors = ErrorCollector()

    # --- Paso 1: Validaciones previas ----------------------------------------
    lines = pre_validators(db, invoice, tenant_id, company_id, errors)
    errors.raise_if_errors()

    # --- Paso 2-4: Parámetros generales, parámetros expo y permisos ----------
    # TODO: Compartir QSisGen / GEmpresa
    # TODO: Compartir QSisExpo (EsCambioRegimen = 'S' → SisExp:EsCambioRegimen = 'CR')
    # TODO: Validar permisos usuario (GUsuarios / GNivelesSeguridad)

    # --- Paso 5: Iniciar transacción -----------------------------------------
    # TODO: BEGIN TRAN  (en el legacy: GSQLFile{PROP:SQL} = 'BEGIN TRAN')

    # --- Paso 6: Verificar que existan partidas ------------------------------
    if not lines:
        errors.add_error(
            field="items",
            message="Esta Factura no tiene partidas.",
            solution=["Capturar al menos una partida a la factura."],
            code="NO_ITEMS_FOUND",
        )
        errors.raise_if_errors()

    # --- Paso 7: Tipo de cambio ----------------------------------------------
    # TODO: Si SisGen:CalValBaseTCPedExpo = 1:
    #         invoice.which_exchange_rate = 'TCPED'
    #         Buscar pedimento (EqiPed:Pedimento = EqiFex:PedimentoExpo)
    #         Buscar GTipoCambio por EqiPed:Fecha_Pago
    #         exchange_rate = GenTC:Valor
    #       Else:
    #         invoice.which_exchange_rate = 'TCFAC'
    #         exchange_rate = invoice.financials.exchange_rate

    # --- Paso 8: Validar que la factura no exista en ImportDef ---------------
    # TODO: Si invoice.generate_id = True:
    #         Buscar en QFacImpDef por invoice.invoice_number
    #         Si ya existe → agregar error

    # --- Paso 9: Sub-proceso por tipo de factura -----------------------------
    invoice_type = invoice.invoice_type

    if invoice_type == "NODES":
        # Sin descarga de saldos        
        assign_no_discharges_items(lines, errors)
        assign_no_discharges_series(db, lines, errors)
        review_class(db, lines, errors)
        review_exchange_rate(db, invoice, errors)
        assign_values(db, invoice, lines, invoice.tenant_id, invoice.company_id, errors)

        review_qty_vs_weight(lines, invoice.logistics.weight_type.upper(), errors)

        review_unit_cost(lines, errors)
        review_qty_series(db, invoice, lines, tenant_id, company_id, errors)
        total_qty, total_net_weight = limit_weight(lines)
        total_value = limit_value(lines)
        
        finalize_invoice_no_discharge(db, invoice, lines, errors)

    elif invoice_type == "AFIJO":
        if invoice.compliance_mx and invoice.compliance_mx.is_regime_change:
            review_origin_procedure(db, invoice, lines, "TEM", errors)
            if invoice.generate_id and invoice.generate_desc_parties == "Todas":
                def_inv = generate_definitive_import(db, invoice, errors)
                if def_inv:
                    generate_definitive_import_all_lines(db, invoice, def_inv, errors)

        _process_with_discharge(db, invoice, lines, errors)

    elif invoice_type == "DONAC":
        _process_with_discharge(db, invoice, lines, errors)

    elif invoice_type == "SCRAP":
        if invoice.compliance_mx and invoice.compliance_mx.is_regime_change:
            review_origin_procedure(db, invoice, lines, "TEM", errors)
            if invoice.generate_id and invoice.generate_desc_parties == "Todas":
                def_inv = generate_definitive_import(db, invoice, errors)
                if def_inv:
                    generate_definitive_import_all_lines(db, invoice, def_inv, errors)

        _process_with_discharge(db, invoice, lines, errors)

    elif invoice_type == "REEXP":
        review_origin_procedure(db, invoice, lines, "DEF", errors)
        _process_with_discharge(db, invoice, lines, errors)

    elif invoice_type == "VEMEX":
        review_origin_procedure(db, invoice, lines, "DEF", errors)
        _process_with_discharge(db, invoice, lines, errors)

    else:
        errors.add_error(
            field="invoice_type",
            message=f"{invoice_type} no es un Tipo de Factura válido, llamar al proveedor del Sistema SCAII.",
            solution=["Verificar el tipo de factura de exportación."],
            code="INVALID_INVOICE_TYPE",
            value=invoice_type,
        )

    # --- Paso 10: Commit / Rollback ------------------------------------------
    errors.raise_if_errors()

    # TODO: COMMIT TRAN  (en el legacy: gSQLFile{PROP:SQL} = 'COMMIT TRAN')
    # TODO: GBitacora('ACTUALIZAR FACTURA', invoice.invoice_number)

    # invoice.status and totals are set inside finalize_invoice_no_discharge / termina_ac_o_lp_normal
    db.flush()

    return {"status": "success", "invoice_id": str(invoice.id)}
