import logging
from typing import Any, Dict, Optional, Union
from core.exceptions import ErrorCollector
from sqlalchemy import func
from sqlalchemy.orm import Session
from .. import models
from .. import schemas
from ..models import InvoiceComplianceMx
from ..models import TransportType, Currency, WeightUnit
from api.v1.modules.a76.invoices.common.calculations import apply_calculations
from api.v1.modules.a76.items.models import LineItem
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos
from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate
from api.v1.modules.a76.clients_and_providers.models import ClientProvider
from api.v1.modules.a76.customs_brokers.models import CustomsBroker
from api.v1.modules.a76.transportation.transporters.models import Transporter
from api.v1.modules.a76.transportation.vehicles.models import Vehicle
from api.v1.modules.a76.transportation.trailers.models import Trailer
from api.v1.modules.a76.transportation.drivers.models import Driver
from api.v1.modules.a76.manifests.manifest.models import Manifest
from api.v1.modules.public.reference_data.incoterms.models import Incoterm
from api.v1.modules.public.reference_data.currency_types.models import CurrencyType
from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection
from api.v1.modules.a76.app_settings.service import AppSettingsService
logger = logging.getLogger(__name__)


def validate_invoice_items_decimals(
    db: Session,
    lines: list,
    tenant_id: int,
    company_id: int,
    errors: ErrorCollector,
) -> None:
    """
    Validates that items with unit of measure 'PZA' do not have decimal quantities
    if the system parameter 'validadecencant' is active.
    """
    settings = AppSettingsService.get_resolved_settings(db, tenant_id, company_id)
    
    # Check in ssisgen or qsisgen
    gen_params = settings.get("ssisgen", {})
    if not gen_params:
        gen_params = settings.get("qsisgen", {})
        
    # Parameter for decimal validation (typically 'validadecencant')
    param_val = str(gen_params.get("validadecencant", "0")).strip().upper()
    skip_decimals = param_val in ["1", "TRUE", "SI", "SÍ"]
    
    if not skip_decimals:
        return

    violations = []
    for line in lines:
        if not line.unit_of_measure_info or not line.quantity:
            continue
            
        uom_code = str(line.unit_of_measure_info.code).upper().strip()
        # Common codes for pieces
        if uom_code in ["PZA", "PIEZA", "PIEZAS", "PZAS", "PCE", "1"]:
            qty = line.quantity.quantity
            if qty is not None and float(qty) % 1 != 0:
                line_info = f"Partida #{line.line_number}"
                if line.part_info:
                    line_info += f" ({line.part_info.part_number})"
                
                violations.append(line_info)
                logger.warning(f"BLINDAJE: {line_info} tiene decimales ({qty}) en unidad {uom_code}. Bloqueando proceso.")
                
                errors.add_error(
                    field=f"items.{line.line_number}.quantity",
                    message=f"{line_info}: No se permiten decimales en unidades de tipo '{uom_code}' según el parámetro del sistema (validadecencant).",
                    solution=["Ajuste la cantidad a un número entero o cambie la unidad de medida."],
                    code="DECIMALS_NOT_ALLOWED",
                    value=str(qty)
                )


def _logistics_str_nonempty(value) -> bool:
    return value is not None and str(value).strip() != ""


def _normalize_invoice_currency_value(value) -> str:
    """Lowercase currency code (foreign/local/manual) for comparisons."""
    if value is None or value == "":
        return ""
    raw = getattr(value, "value", value)
    return str(raw).lower()


def invoice_exists(
    db: Session,
    invoice_number: str,
    tenant_id: int,
    company_id: int,
    errors: Optional[ErrorCollector],
):
    invoice = (
        db.query(models.InvoiceHeader)
        .filter(
            models.InvoiceHeader.invoice_number == invoice_number,
            models.InvoiceHeader.tenant_id == tenant_id,
            models.InvoiceHeader.company_id == company_id,
        )
        .first()
    )

    if invoice:
        if errors:
            errors.add_duplicate_error(
                "invoice_number",
                invoice_number,
                f"Ya existe una factura con el número '{invoice_number}'",
            )
        return invoice
    return None

def invoice_exists_by_id(
    db: Session,
    invoice_id: str,
    tenant_id: int,
    company_id: int,
    errors: Optional[ErrorCollector],
):
    """
    Check if an invoice exists by ID. 
    WARNING: Adds a DUPLICATE error if found (to be used when creating NEW invoices with specific IDs).
    """
    invoice = (
        db.query(models.InvoiceHeader)
        .filter(
            models.InvoiceHeader.id == invoice_id,
            models.InvoiceHeader.tenant_id == tenant_id,
            models.InvoiceHeader.company_id == company_id,
        )
        .first()
    )

    if invoice:
        if errors:
            errors.add_duplicate_error(
                "invoice_id",
                invoice_id,
                f"Ya existe una factura con el número '{invoice_id}'",
            )
        return invoice
    return None

def invoice_id_required(
    db: Session,
    invoice_id: int,
    tenant_id: int,
    company_id: int,
    errors: ErrorCollector,
) -> Optional[models.InvoiceHeader]:
    """
    Validates that an invoice exists by ID.
    Adds a NOT_FOUND error if it doesn't exist.
    """
    if not invoice_id:
        errors.add_required_error(field="invoice_id")
        return None

    invoice = (
        db.query(models.InvoiceHeader)
        .filter(
            models.InvoiceHeader.id == invoice_id,
            models.InvoiceHeader.tenant_id == tenant_id,
            models.InvoiceHeader.company_id == company_id,
        )
        .first()
    )

    if not invoice:
        errors.add_error(
            field="invoice_id",
            message=f"La factura con ID '{invoice_id}' no existe.",
            solution=["Seleccionar una factura válida."],
            code="NOT_FOUND",
            value=str(invoice_id),
        )
    return invoice

def invoice_processed(
    db: Session,
    invoice_id: str,
    tenant_id: int,
    company_id: int,
    errors: ErrorCollector,
) -> bool:
    status = (
        db.query(models.InvoiceHeader.status)
        .filter(
            models.InvoiceHeader.id == invoice_id,
            models.InvoiceHeader.tenant_id == tenant_id,
            models.InvoiceHeader.company_id == company_id,
        )
        .scalar()
    )

    if status == models.InvoiceStatus.PROCESSED:
        errors.add_error(
            field="invoice_number",
            message=f"La factura con el número '{invoice_id}' ya ha sido actualizada y no se puede modificar.",
            solution="Capturar otro número de Factura de Importación Temporal o Desactualizar la factura.",
            code="INVOICE_processed",
            value=invoice_id,
        )
        return True
    return False

def validate_required_fields_by_operation(
    invoice_data: Dict[str, Any],
    operation_type: str,
    errors: ErrorCollector
) -> None:
    """
    Valida campos obligatorios según tipo de operación.
    Usar ANTES de guardar en BD.
    """
    
    # PROVEEDOR (SIEMPRE OBLIGATORIO - mensaje dinámico)
    if not invoice_data.get('provider_id'):
        # Mensaje dinámico según el header seleccionado
        provider_labels = {
            'proveedor': 'Proveedor',
            'exportador': 'Exportador'
        }
        provider_header = invoice_data.get('provider_header') or 'proveedor'
        field_label = provider_labels.get(provider_header, 'Proveedor')
        
        errors.add_error(
            field="provider_id",
            message=f"Debe seleccionar {field_label}",
            solution=["Seleccione un proveedor de la lista desplegable"],
            code="REQUIRED",
            value=None
        )
    
    # VENDIDO A / CONSIGNADO A (SIEMPRE OBLIGATORIO - mensaje dinámico)
    if not invoice_data.get('sold_to_id'):
        # Mensaje dinámico según el header seleccionado
        sold_to_labels = {
            'consignado_a': 'Consignado a',
            'vendido_a': 'Vendido a',
            'exportado_a': 'Exportado a',
            'importador': 'Importador'
        }
        sold_to_header = invoice_data.get('sold_to_header') or 'consignado_a'
        field_label = sold_to_labels.get(sold_to_header, 'Cliente')
        
        errors.add_error(
            field="sold_to_id",
            message=f"Debe seleccionar {field_label}",
            solution=["Seleccione una opción de la lista desplegable"],
            code="REQUIRED",
            value=None
        )
    
    # ENVIADO A (SIEMPRE OBLIGATORIO - mensaje fijo)
    if not invoice_data.get('shipped_to_id'):
        errors.add_error(
            field="shipped_to_id",
            message="Debe seleccionar el Destinatario",
            solution=["Seleccione un destinatario de la lista desplegable"],
            code="REQUIRED",
            value=None
        )
    
    # AGENTE ADUANAL (OBLIGATORIO si hay pedimento)
    if invoice_data.get('pedimento_id') and not invoice_data.get('customs_broker_id'):
        errors.add_error(
            field="customs_broker_id",
            message="Debe seleccionar un Agente Aduanal",
            solution=["Seleccione un agente aduanal de la lista desplegable"],
            code="REQUIRED",
            value=None
        )


def validate_common(
    db: Session,
    invoice: Union[schemas.InvoiceHeaderCreate, schemas.InvoiceHeaderUpdate],
    tenant_id: int,
    company_id: int,
    errors: ErrorCollector,
    existing_invoice: Optional[models.InvoiceHeader] = None,
):
    if invoice.compliance_mx.pedimento_id:
        pedimento = (
            db.query(Pedimentos)
            .filter(
                Pedimentos.id == invoice.compliance_mx.pedimento_id,
                Pedimentos.tenant_id == tenant_id,
                Pedimentos.company_id == company_id,
            )
            .first()
        )
        if not pedimento:
            errors.add_error(
                field="compliance_mx.pedimento_id",
                message="El Pedimento no existe en el Catálogo de Pedimentos.",
                solution=["Verifica el ID", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.compliance_mx.pedimento_id,
            )
            return  # Stop here if pedimento not found

        if not invoice.compliance_mx.is_regime_change:
            operacion = "Importación" if pedimento.operation_type == "imp" else "Exportación"
            # Validar que el pedimento sea de importación
            if pedimento.operation_type != invoice.operation_type:
                errors.add_error(
                    field="compliance_mx.pedimento_id",
                    message=f"El Pedimento seleccionado no corresponde a una {operacion}.",
                    solution=[f"Selecciona un Pedimento de {operacion}"],
                    code="INVALID_OPERATION_TYPE",
                    value=pedimento.operation_type,
                )
            else:
                # Validar regímenes incompatibles                
                only_regimes = ["EXD", "ETE", "ETR"]      
                is_valid = True                                              
                if invoice.operation_type == "imp" and pedimento.regime in only_regimes:
                    is_valid = False
                    oposite_operacion = "Exportación"
                elif invoice.operation_type == "exp" and pedimento.regime not in only_regimes:
                    is_valid = False
                    oposite_operacion = "Importación"
                
                if not is_valid: 
                    errors.add_error(
                        field="compliance_mx.pedimento_id",
                        message=f"El Pedimento seleccionado no corresponde a una {oposite_operacion}, no a una {operacion}.",
                        solution=[f"Selecciona un Pedimento de {operacion}"],
                        code="INVALID_REGIME",
                        value=pedimento.regime,
                    )
                
                # Validar que el tipo de documento coincida con el régimen del pedimento
                # NOTA: Solo validamos si no hay errores previos y si document_type está presente
                if not errors.has_errors() and invoice.document_type:
                    if invoice.document_type.upper().strip() != pedimento.regime:
                        errors.add_error(
                            field="document_type",
                            message=f"El Tipo de Documento '{invoice.document_type}' no coincide con el Régimen '{pedimento.regime}' del Pedimento seleccionado.",
                            solution=[
                                "Ajusta el Tipo de Documento o selecciona otro Pedimento"
                            ],
                            code="REGIME_MISMATCH",
                            value=invoice.document_type,
                        )
        else:
            # Cambio de Régimen - Generalmente es de Importación Temporal a Definitiva (IMD)
            # En el código legacy se comparaba pedimento.operation_type != 2. 
            # Si asumimos que 2 era Importación en el sistema anterior:
            if pedimento.operation_type != "imp":
                errors.add_error(
                    field="compliance_mx.pedimento_id",
                    message="El Pedimento seleccionado no corresponde a una Importación (requerido para Cambio de Régimen).",
                    solution=["Selecciona un Pedimento de Importación"],
                    code="INVALID_OPERATION_TYPE",
                    value=pedimento.operation_type,
                )
            else:
                if pedimento.regime != "IMD" and invoice.document_type == "IMD":
                     # Si el destino es IMD, validamos que el pedimento original sea de importación
                     # (aunque usualmente el pedimento que se asocia aquí es el nuevo, el de IMD)
                     pass 

                if invoice.document_type.upper().strip() != pedimento.regime:
                    errors.add_error(
                        field="document_type",
                        message=f"El Tipo de Documento '{invoice.document_type}' no coincide con el Régimen '{pedimento.regime}' del Pedimento seleccionado.",
                        solution=[
                            "Ajusta el Tipo de Documento o selecciona otro Pedimento"
                        ],
                        code="REGIME_MISMATCH",
                        value=invoice.document_type,
                    )
                
                if pedimento.pedimento_code not in ["A1", "A3"]:
                    errors.add_error(
                        field="compliance_mx.pedimento_id",
                        message=f"El Pedimento seleccionado no es de tipo A1 o A3 requerido para Cambio de Régimen.",
                        solution=["Selecciona un Pedimento de tipo A1 o A3"],
                        code="INVALID_PEDEMENTO_CODE",
                        value=pedimento.pedimento_code,
                    )

        if pedimento.pedimento_type == "consolidated":
            if not pedimento.pedimento_dates:
                errors.add_error(
                    field="compliance_mx.pedimento_id",
                    message="El Pedimento seleccionado no tiene fechas registradas.",
                    solution=["Verifica las fechas del Pedimento en el catálogo"],
                    code="MISSING_PEDIMENTO_DATES",
                    value=invoice.compliance_mx.pedimento_id,
                )
            else:
                # Convertir invoice_date a date si es datetime para poder comparar
                invoice_date = (
                    invoice.invoice_date.date()
                    if hasattr(invoice.invoice_date, "date")
                    else invoice.invoice_date
                )
                entry_date = (
                    pedimento.pedimento_dates.entry_date.date()
                    if hasattr(pedimento.pedimento_dates.entry_date, "date")
                    else pedimento.pedimento_dates.entry_date
                )
                end_date = (
                    pedimento.pedimento_dates.end_date.date()
                    if hasattr(pedimento.pedimento_dates.end_date, "date")
                    else pedimento.pedimento_dates.end_date
                )

            if pedimento.pedimento_dates and (invoice_date < entry_date or invoice_date > end_date):
                errors.add_error(
                    field="invoice_date",
                    message=f"La Fecha de la Factura {invoice.invoice_date} no está dentro del rango de fechas del Pedimento {pedimento.customs_office}-{pedimento.license}-{pedimento.pedimento_number}.",
                    solution=[
                        f"Capturar una Fecha de Factura, entre la Fecha de Inicio: {pedimento.pedimento_dates.entry_date} y la Fecha Final: {pedimento.pedimento_dates.end_date} ."
                    ],
                    code="DATE_OUT_OF_RANGE",
                    value=invoice.invoice_date,
                )

        # Remesa check
        if pedimento.pedimento_type == "consolidated":
            if not invoice.compliance_mx.remesa:
                errors.add_error(
                    field="compliance_mx.remesa",
                    message="El campo Remesa es obligatorio cuando se asocia un Pedimento consolidado.",
                    solution=["Proporciona un valor para Remesa"],
                    code="REQUIRED_FIELD",
                    value=invoice.compliance_mx.remesa,
                )
            elif invoice.compliance_mx.remesa == 0:
                errors.add_error(
                    field="compliance_mx.remesa",
                    message="El campo Remesa no puede ser cero cuando se asocia un Pedimento.",
                    solution=["Proporciona un valor válido para Remesa"],
                    code="INVALID_VALUE",
                    value=invoice.compliance_mx.remesa,
                )
            else:
                # Misma remesa puede repetirse en distintos pedimentos; solo debe ser única
                # por (pedimento_id, empresa, tenant). Creación: existing_invoice es None.
                current_invoice_id = None
                if existing_invoice is not None:
                    current_invoice_id = existing_invoice.id
                else:
                    current_invoice_id = getattr(invoice, "id", None)

                duplicated_remesa = (
                    db.query(InvoiceComplianceMx)
                    .filter(
                        InvoiceComplianceMx.remesa == invoice.compliance_mx.remesa,
                        InvoiceComplianceMx.pedimento_id
                        == invoice.compliance_mx.pedimento_id,
                        InvoiceComplianceMx.tenant_id == tenant_id,
                        InvoiceComplianceMx.company_id == company_id,
                    )
                    .first()
                )
                if duplicated_remesa and (
                    current_invoice_id is None
                    or duplicated_remesa.invoice_id != current_invoice_id
                ):
                    errors.add_error(
                        field="compliance_mx.remesa",
                        message=(
                            "El valor de Remesa ya está registrado para este mismo Pedimento "
                            "en otra factura."
                        ),
                        solution=["Usa otra remesa o revisa la factura que ya la tiene capturada"],
                        code="DUPLICATE_VALUE",
                        value=invoice.compliance_mx.remesa,
                    )
    
    # Financials checks (if provided)
    if invoice.financials:
        if not invoice.financials.exchange_rate or invoice.financials.exchange_rate <= 0:
            exchange_rate_exists = (
                db.query(ExchangeRate)
                .filter(
                    func.date(ExchangeRate.date) == invoice.invoice_date,
                    ExchangeRate.tenant_id == tenant_id,
                    ExchangeRate.company_id == company_id,
                )
                .first()
            )
            if not exchange_rate_exists:
                errors.add_error(
                    field="financials.exchange_rate",
                    message=f"No existe un Tipo de Cambio registrado para la fecha {invoice.invoice_date}.",
                    solution=["Registra el Tipo de Cambio en el catálogo correspondiente"],
                    code="EXCHANGE_RATE_NOT_FOUND",
                    value=invoice.financials.exchange_rate,
                )
            else:
                invoice.financials.exchange_rate = exchange_rate_exists.value
    else:
        # If financials missing, we might want to error if it's required for this operation
        pass

    if invoice.compliance_mx.is_regime_change:
        if invoice.document_type in ["EXD", "ETE", "ETR"]:
            errors.add_error(
                field="document_type",
                message="El Tipo de Documento no puede ser de Exportación cuando se trata de un Cambio de Régimen.",
                solution=[
                    "Selecciona un Tipo de Documento válido para Cambio de Régimen"
                ],
                code="INVALID_DOCUMENT_TYPE",
                value=invoice.document_type,
            )
    else:
        if invoice.document_type == "IMD" and invoice.invoice_type.upper() != "DEF":
            errors.add_error(
                field="document_type",
                message="El Tipo de Documento no puede ser 'IMD' a menos que sea un Cambio de Régimen.",
                solution=["Selecciona un Tipo de Documento válido"],
                code="INVALID_DOCUMENT_TYPE",
                value=invoice.document_type,
            )

    # Validar proveedor solo si se proporciona
    if invoice.compliance_mx.provider_id:
        provider_exists = (
            db.query(ClientProvider)
            .filter(
                ClientProvider.id == invoice.compliance_mx.provider_id,
                ClientProvider.tenant_id == tenant_id,
                ClientProvider.company_id == company_id,
            )
            .first()
        )
        if not provider_exists:
            errors.add_error(
                field="compliance_mx.provider_id",
                message="El Proveedor no existe en el Catálogo de Clientes y Proveedores.",
                solution=["Verifica el ID del Proveedor", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.compliance_mx.provider_id,
            )

    # Validar vendido a solo si se proporciona
    if invoice.compliance_mx.sold_to_id:
        selled_to_exists = (
            db.query(ClientProvider)
            .filter(
                ClientProvider.id == invoice.compliance_mx.sold_to_id,
                ClientProvider.tenant_id == tenant_id,
                ClientProvider.company_id == company_id,
            )
            .first()
        )
        if not selled_to_exists:
            errors.add_error(
                field="compliance_mx.sold_to_id",
                message="El Cliente no existe en el Catálogo de Clientes y Proveedores.",
                solution=["Verifica el ID del Cliente", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.compliance_mx.sold_to_id,
            )

    # Validar destinatario solo si se proporciona
    if invoice.compliance_mx.shipped_to_id:
        shipped_to_exists = (
            db.query(ClientProvider)
            .filter(
                ClientProvider.id == invoice.compliance_mx.shipped_to_id,
                ClientProvider.tenant_id == tenant_id,
                ClientProvider.company_id == company_id,
            )
            .first()
        )
        if not shipped_to_exists:
            errors.add_error(
                field="compliance_mx.shipped_to_id",
                message="El Destinatario no existe en el Catálogo de Clientes y Proveedores.",
                solution=["Verifica el ID del Destinatario", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.compliance_mx.shipped_to_id,
            )
            
    if invoice.compliance_mx.shipped_by_id:
        shipped_by_exists = (
            db.query(ClientProvider)
            .filter(
                ClientProvider.id == invoice.compliance_mx.shipped_by_id,
                ClientProvider.tenant_id == tenant_id,
                ClientProvider.company_id == company_id,
            )
            .first()
        )
        if not shipped_by_exists:
            errors.add_error(
                field="compliance_mx.shipped_by_id",
                message="El Remitente no existe en el Catálogo de Clientes y Proveedores.",
                solution=["Verifica el ID del Remitente", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.compliance_mx.shipped_by_id,
            )

    # Validar agente aduanal solo si se proporciona
    if invoice.compliance_mx.customs_broker_id:
        customs_broker_exists = (
            db.query(CustomsBroker)
            .filter(
                CustomsBroker.id == invoice.compliance_mx.customs_broker_id,
                CustomsBroker.tenant_id == tenant_id,
                CustomsBroker.company_id == company_id,
            )
            .first()
        )
        if not customs_broker_exists:
            errors.add_error(
                field="compliance_mx.customs_broker_id",
                message="El Agente Aduanal no existe en el Catálogo de Clientes y Proveedores.",
                solution=["Verifica el ID del Agente Aduanal", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.compliance_mx.customs_broker_id,
            )    

    if invoice.logistics:
        carrier_exists = None
        if invoice.logistics.carrier_id:
            # `carrier_id` is the frontend/export key (string). Prefer validating by the internal int
            # when present, otherwise validate by `transporter_key`.
            if getattr(invoice.logistics, "carrier_int_id", None) is not None:
                carrier_exists = (
                    db.query(Transporter)
                    .filter(
                        Transporter.transporter_id
                        == invoice.logistics.carrier_int_id,
                        Transporter.tenant_id == tenant_id,
                        Transporter.company_id == company_id,
                    )
                    .first()
                )
            else:
                carrier_exists = (
                    db.query(Transporter)
                    .filter(
                        Transporter.transporter_key == invoice.logistics.carrier_id,
                        Transporter.tenant_id == tenant_id,
                        Transporter.company_id == company_id,
                    )
                    .first()
                )
            if not carrier_exists:
                errors.add_error(
                    field="logistics.carrier_id",
                    message="El Transportista no existe en el Catálogo de Clientes y Proveedores.",
                    solution=["Verifica el ID del Transportista", "Revisa el catálogo"],
                    code="NOT_FOUND",
                    value=invoice.logistics.carrier_id,
                )

        if carrier_exists and _logistics_str_nonempty(
            getattr(invoice.logistics, "driver_name", None)
        ):
            driver_row = (
                db.query(Driver)
                .filter(
                    Driver.transporter_key == str(invoice.logistics.carrier_id).strip(),
                    Driver.driver_name
                    == str(invoice.logistics.driver_name).strip(),
                    Driver.tenant_id == tenant_id,
                    Driver.company_id == company_id,
                )
                .first()
            )
            if not driver_row:
                errors.add_error(
                    field="logistics.driver_name",
                    message=(
                        "El conductor no existe en el catálogo de conductores "
                        "para el transportista indicado."
                    ),
                    solution=[
                        "Registra el conductor en el catálogo o elige uno de la lista"
                    ],
                    code="NOT_FOUND",
                    value=invoice.logistics.driver_name,
                )

        if invoice.logistics.transport_type not in [t.value for t in TransportType]:
            errors.add_error(
                field="logistics.transport_type",
                message="El Tipo de Transporte proporcionado no es válido.",
                solution=[
                    f"Selecciona un Tipo de Transporte válido: {[t.value for t in TransportType]}"
                ],
                code="INVALID_TRANSPORT_TYPE",
                value=invoice.logistics.transport_type,
            )
        else:
            # Vehículo: solo catálogo si viene informado (cualquier tipo de transporte).
            tid = getattr(invoice.logistics, "transport_int_id", None)
            tcode = invoice.logistics.transport_id
            if tid is not None:
                v_by_int = (
                    db.query(Vehicle)
                    .filter(
                        Vehicle.vehicle_id == tid,
                        Vehicle.tenant_id == tenant_id,
                        Vehicle.company_id == company_id,
                    )
                    .first()
                )
                if not v_by_int:
                    errors.add_error(
                        field="logistics.transport_int_id",
                        message="El Vehículo (transporte) no existe en el catálogo.",
                        solution=[
                            "Verifica el ID interno del vehículo o usa la clave de transporte"
                        ],
                        code="NOT_FOUND",
                        value=tid,
                    )
            elif _logistics_str_nonempty(tcode):
                v_by_key = (
                    db.query(Vehicle)
                    .filter(
                        func.upper(Vehicle.vehicle_key)
                        == str(tcode).strip().upper(),
                        Vehicle.tenant_id == tenant_id,
                        Vehicle.company_id == company_id,
                    )
                    .first()
                )
                if not v_by_key:
                    errors.add_error(
                        field="logistics.transport_id",
                        message="El Vehículo (clave transporte) no existe en el catálogo.",
                        solution=["Verifica la clave en el catálogo de vehículos"],
                        code="NOT_FOUND",
                        value=tcode,
                    )

            # Remolque: obligatorio si tipo != 'none'; con 'none' solo se valida catálogo si hay dato.
            tr_int = getattr(invoice.logistics, "trailer_int_id", None)
            tr_num = invoice.logistics.trailer_num
            if invoice.logistics.transport_type != "none":
                if tr_int is not None:
                    tr = (
                        db.query(Trailer)
                        .filter(
                            Trailer.trailer_id == tr_int,
                            Trailer.tenant_id == tenant_id,
                            Trailer.company_id == company_id,
                        )
                        .first()
                    )
                    if not tr:
                        errors.add_error(
                            field="logistics.trailer_int_id",
                            message="El Remolque no existe en el catálogo.",
                            solution=["Verifica el ID interno del remolque"],
                            code="NOT_FOUND",
                            value=tr_int,
                        )
                elif not _logistics_str_nonempty(tr_num):
                    errors.add_error(
                        field="logistics.trailer_num",
                        message=(
                            "El Remolque es obligatorio cuando el Tipo de Transporte es distinto de 'none'."
                        ),
                        solution=["Selecciona un remolque del catálogo"],
                        code="REQUIRED_FIELD",
                        value=tr_num,
                    )
                else:
                    tr = (
                        db.query(Trailer)
                        .filter(
                            func.upper(Trailer.trailer_number)
                            == str(tr_num).strip().upper(),
                            Trailer.tenant_id == tenant_id,
                            Trailer.company_id == company_id,
                        )
                        .first()
                    )
                    if not tr:
                        errors.add_error(
                            field="logistics.trailer_num",
                            message="El Remolque no existe en el catálogo.",
                            solution=["Verifica el número de remolque"],
                            code="NOT_FOUND",
                            value=tr_num,
                        )
            else:
                if tr_int is not None:
                    tr = (
                        db.query(Trailer)
                        .filter(
                            Trailer.trailer_id == tr_int,
                            Trailer.tenant_id == tenant_id,
                            Trailer.company_id == company_id,
                        )
                        .first()
                    )
                    if not tr:
                        errors.add_error(
                            field="logistics.trailer_int_id",
                            message="El Remolque no existe en el catálogo.",
                            solution=["Verifica el ID interno del remolque"],
                            code="NOT_FOUND",
                            value=tr_int,
                        )
                elif _logistics_str_nonempty(tr_num):
                    tr = (
                        db.query(Trailer)
                        .filter(
                            func.upper(Trailer.trailer_number)
                            == str(tr_num).strip().upper(),
                            Trailer.tenant_id == tenant_id,
                            Trailer.company_id == company_id,
                        )
                        .first()
                    )
                    if not tr:
                        errors.add_error(
                            field="logistics.trailer_num",
                            message="El Remolque no existe en el catálogo.",
                            solution=["Verifica el número de remolque"],
                            code="NOT_FOUND",
                            value=tr_num,
                        )

    if invoice.financials:
        submitted = invoice.financials.currency
        stored_str = ""
        if existing_invoice and existing_invoice.financials is not None:
            stored_str = _normalize_invoice_currency_value(
                existing_invoice.financials.currency
            )

        if submitted is None or submitted == "":
            if stored_str:
                invoice.financials.currency = stored_str
            else:
                invoice.financials.currency = "foreign"
        else:
            invoice.financials.currency = (
                _normalize_invoice_currency_value(submitted) or "foreign"
            )

        if invoice.financials.currency not in [c.value for c in Currency]:
            errors.add_error(
                field="financials.currency",
                message="La Moneda proporcionada no es válida.",
                solution=[f"Selecciona una Moneda válida: {[c.value for c in Currency]}"],
                code="INVALID_CURRENCY",
                value=invoice.financials.currency,
            )
        else:
            invoice_id = getattr(invoice, "id", None)
            if (
                invoice_id is not None
                and existing_invoice is not None
                and existing_invoice.financials is not None
            ):
                old_c = _normalize_invoice_currency_value(
                    existing_invoice.financials.currency
                )
                new_c = invoice.financials.currency
                if old_c != new_c:
                    has_items = (
                        db.query(LineItem)
                        .filter(
                            LineItem.invoice_id == invoice_id,
                            LineItem.tenant_id == tenant_id,
                            LineItem.company_id == company_id,
                        )
                        .first()
                    )
                    if has_items:
                        errors.add_error(
                            field="items",
                            message=(
                                "La opción tipo de moneda no puede ser modificada "
                                "ya que la factura tiene partidas asociadas."
                            ),
                            solution=[
                                "Verifica la moneda de las partidas asociadas a la factura."
                            ],
                            code="CURRENCY_CANNOT_BE_CHANGED",
                            value=invoice.financials.currency,
                        )

        if invoice.financials.currency == "foreign":
            invoice.financials.currency_type = "USD"
        elif invoice.financials.currency == "local":
            invoice.financials.currency_type = "MXN"
        elif invoice.financials.currency == "manual":
            if not invoice.financials.currency_type:
                errors.add_error(
                    field="financials.currency_type",
                    message="El Tipo de Moneda es obligatorio cuando la Moneda es 'manual'.",
                    solution=["Proporciona un Tipo de Moneda válido"],
                    code="REQUIRED_FIELD",
                    value=invoice.financials.currency_type,
                )
            else:
                currency_exists = (
                    db.query(CurrencyType)
                    .filter(CurrencyType.code == invoice.financials.currency_type)
                    .first()
                )
                if not currency_exists:
                    errors.add_error(
                        field="financials.currency_type",
                        message="El Tipo de Moneda no existe en el Catálogo de Tipos de Moneda.",
                        solution=[
                            "Verifica el código del Tipo de Moneda",
                            "Revisa el catálogo",
                        ],
                        code="NOT_FOUND",
                        value=invoice.financials.currency_type,
                    )

    if invoice.logistics.incoterm:
        incoterm_exists = (
            db.query(Incoterm)
            .filter(
                Incoterm.code == invoice.logistics.incoterm
            )
            .first()
        )
        if not incoterm_exists:
            errors.add_error(
                field="logistics.incoterm",
                message="El Incoterm no existe en el Catálogo de Incoterms.",
                solution=["Verifica el código del Incoterm", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.logistics.incoterm,
            )

    if invoice.logistics.weight_type not in [w.value for w in WeightUnit]:
        errors.add_error(
            field="logistics.weight_type",
            message="La Unidad de Peso proporcionada no es válida.",
            solution=[
                f"Selecciona una Unidad de Peso válida: {[w.value for w in WeightUnit]}"
            ],
            code="INVALID_WEIGHT_UNIT",
            value=invoice.logistics.weight_type,
        )

    if invoice.compliance_mx.aduana:
        custom_section_exists = (
            db.query(CustomsSection)
            .filter(
                CustomsSection.customs_code == invoice.compliance_mx.aduana,
            )
            .first()
        )
        if not custom_section_exists:
            errors.add_error(
                field="compliance_mx.aduana",
                message="La Aduana no existe en el Catálogo de Secciones Aduaneras.",
                solution=["Verifica el código de la Aduana", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.compliance_mx.aduana,
            )
    
    if invoice.compliance_mx.manifest_number:
        manifest_exists = (
            db.query(Manifest)
            .filter(
                Manifest.manifest_number == invoice.compliance_mx.manifest_number,
                Manifest.tenant_id == tenant_id,
                Manifest.company_id == company_id,
            )
            .first()
        )
        if not manifest_exists:
            errors.add_error(
                field="compliance_mx.manifest_number",
                message="El Número de Manifiesto no existe en el sistema.",
                solution=["Verifica el Número de Manifiesto", "Revisa el catálogo"],
                code="NOT_FOUND",
                value=invoice.compliance_mx.manifest_number,
             )
            
    apply_calculations(invoice)