from typing import Optional
from datetime import date
from decimal import Decimal

from sqlalchemy.orm import Session

from ...common.common_validators import validate_common, validate_required_fields_by_operation
from core.exceptions import ErrorCollector
from ...schemas import InvoiceHeaderUpdate
from ...models import InvoiceHeader

# Helper function para limpiar strings (equivalente a Clip())
def clean_str(value: Optional[str]) -> Optional[str]:
    if value is None or value == "":
        return None
    return value.strip()

def validate_update(
    db: Session,
    invoice: InvoiceHeaderUpdate,
    existing_invoice: InvoiceHeader,
    tenant_id: int,
    company_id: int,
    errors: ErrorCollector,     
) -> None:
    """
    Valida y procesa la actualización parcial de una factura de exportación.
    
    Lógica: Si un campo viene con valor, se limpia/valida.
           Si no, se mantiene el valor existente de la factura.
    
    Args:
        invoice: Datos de la factura a validar/actualizar (modificado in-place)
        existing_invoice: Factura existente en la base de datos
        errors: Colector de errores
    
    Returns:
        None (modifica invoice in-place y acumula errores en errors)
    """
    
    # Validar campos requeridos según el tipo de operación
    invoice_dict = {
        'provider_id': invoice.compliance_mx.provider_id if invoice.compliance_mx else (existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None),
        'sold_to_id': invoice.compliance_mx.sold_to_id if invoice.compliance_mx else (existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None),
        'sold_to_header': invoice.compliance_mx.sold_to_header if invoice.compliance_mx else (existing_invoice.compliance_mx.sold_to_header if existing_invoice.compliance_mx else None),
        'shipped_to_id': invoice.compliance_mx.shipped_to_id if invoice.compliance_mx else (existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None),
        'customs_broker_id': invoice.compliance_mx.customs_broker_id if invoice.compliance_mx else (existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None),
        'pedimento_id': invoice.compliance_mx.pedimento_id if invoice.compliance_mx else (existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None),
    }
    
    validate_required_fields_by_operation(
        invoice_data=invoice_dict,
        operation_type=invoice.operation_type or (existing_invoice.operation_type or 'imp'),
        errors=errors
    )
    
    # Primero ejecutar validaciones comunes
    validate_common(
        db, invoice, tenant_id, company_id, errors, existing_invoice=existing_invoice
    )

    # Mapeo de columnas CSV a campos de la factura
    # Siguiendo la lógica del código Clarion original
    
    # Columna A: Pedimento (si no viene en CSV, usar el existente)
    if invoice.compliance_mx:
        if invoice.compliance_mx.pedimento_id:
            invoice.compliance_mx.pedimento_id = invoice.compliance_mx.pedimento_id
        else:
            invoice.compliance_mx.pedimento_id = existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None
    
    # Columna B: Remesa
    if invoice.compliance_mx:
        if invoice.compliance_mx.remesa:
            invoice.compliance_mx.remesa = invoice.compliance_mx.remesa
        else:
            invoice.compliance_mx.remesa = existing_invoice.compliance_mx.remesa if existing_invoice.compliance_mx else None
    
    # Columna C: Factura (OBLIGATORIO)
    if invoice.invoice_number is not None:
        invoice.invoice_number = clean_str(invoice.invoice_number)
        if not invoice.invoice_number:
            errors.add_required_error("invoice_number")
    else:
        invoice.invoice_number = existing_invoice.invoice_number
    
    # Columna D: Fecha
    if not invoice.invoice_date:
        invoice.invoice_date = existing_invoice.invoice_date
    
    # Columna E: Tipo Cambio
    if invoice.financials:
        if invoice.financials.exchange_rate is None:
            if existing_invoice.financials:
                invoice.financials.exchange_rate = existing_invoice.financials.exchange_rate
    
    # Columna F: Régimen
    if invoice.document_type:
        invoice.document_type = clean_str(invoice.document_type).upper()
    else:
        invoice.document_type = existing_invoice.document_type
    
    # Columna G: Clave Proveedor
    if invoice.compliance_mx:
        if invoice.compliance_mx.provider_id is None:
            invoice.compliance_mx.provider_id = existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None
    
    # Columna H: Clave Vendido A
    if invoice.compliance_mx:
        if invoice.compliance_mx.sold_to_id is None:
            invoice.compliance_mx.sold_to_id = existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None
    
    # Columna I: Clave Enviado A
    if invoice.compliance_mx:
        if invoice.compliance_mx.shipped_to_id is None:
            invoice.compliance_mx.shipped_to_id = existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None
    
    # Columna J: Clave A. Aduanal
    if invoice.compliance_mx:
        if invoice.compliance_mx.customs_broker_id is None:
            invoice.compliance_mx.customs_broker_id = existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None
    
    # Columna K: Clave Transportista
    if invoice.logistics:
        # Note: logistics in update schema seems to be a single object, but in model it's a list.
        # This validator seems to expect a single object (InvoiceLogisticsUpdate).
        # We'll stick to the existing logic but make it safe.
        if hasattr(invoice.logistics, 'carrier_id') and invoice.logistics.carrier_id is None:
            invoice.logistics.carrier_id = existing_invoice.logistics.carrier_id if existing_invoice.logistics else None
    
    # Columna L: Nombre Conductor
    if invoice.logistics:
        if hasattr(invoice.logistics, 'driver_name') and not invoice.logistics.driver_name:
            invoice.logistics.driver_name = existing_invoice.logistics.driver_name if existing_invoice.logistics else None
        elif hasattr(invoice.logistics, 'driver_name'):
             invoice.logistics.driver_name = clean_str(invoice.logistics.driver_name)
    
    # Columna M: Tipo Transporte
    if invoice.logistics:
        if hasattr(invoice.logistics, 'transport_type') and not invoice.logistics.transport_type:
            invoice.logistics.transport_type = existing_invoice.logistics.transport_type if existing_invoice.logistics else None
        elif hasattr(invoice.logistics, 'transport_type'):
            invoice.logistics.transport_type = clean_str(invoice.logistics.transport_type)
    
    # Columna N: Número de Transporte
    if invoice.logistics:
        if hasattr(invoice.logistics, 'transport_num') and not invoice.logistics.transport_num:
            invoice.logistics.transport_num = existing_invoice.logistics.transport_num if existing_invoice.logistics else None
        elif hasattr(invoice.logistics, 'transport_num'):
            invoice.logistics.transport_num = clean_str(invoice.logistics.transport_num)
    
    # Columna O: Tipo de Moneda
    if invoice.financials:
        if not invoice.financials.currency:
            invoice.financials.currency = existing_invoice.financials.currency if existing_invoice.financials else None
        else:
            invoice.financials.currency = clean_str(invoice.financials.currency).lower()
    
    # Columna P: Clave Moneda
    if invoice.financials:
        if not invoice.financials.currency_type:
            invoice.financials.currency_type = existing_invoice.financials.currency_type if existing_invoice.financials else None
        else:
            invoice.financials.currency_type = clean_str(invoice.financials.currency_type).upper()
    
    # Columna Q: Flete
    if invoice.financials:
        if invoice.financials.freight is None:
            invoice.financials.freight = existing_invoice.financials.freight if existing_invoice.financials else None
    
    # Columna R: Val Seguros
    if invoice.financials:
        if invoice.financials.insurance_value is None:
            invoice.financials.insurance_value = existing_invoice.financials.insurance_value if existing_invoice.financials else None
    
    # Columna S: Seguros
    if invoice.financials:
        if invoice.financials.insurance is None:
            invoice.financials.insurance = existing_invoice.financials.insurance if existing_invoice.financials else None
    
    # Columna T: Embalaje
    if invoice.financials:
        if invoice.financials.packaging is None:
            invoice.financials.packaging = existing_invoice.financials.packaging if existing_invoice.financials else None
    
    # Columna U: Otros Incrementables
    if invoice.financials:
        if invoice.financials.other_increments is None:
            invoice.financials.other_increments = existing_invoice.financials.other_increments if existing_invoice.financials else None
    
    # Columna V: Incoterms
    if invoice.logistics:
        if hasattr(invoice.logistics, 'incoterm') and not invoice.logistics.incoterm:
            invoice.logistics.incoterm = existing_invoice.logistics.incoterm if existing_invoice.logistics else None
        elif hasattr(invoice.logistics, 'incoterm'):
            invoice.logistics.incoterm = clean_str(invoice.logistics.incoterm).upper()
    
    # Columna W: Precinto
    if invoice.logistics:
        if hasattr(invoice.logistics, 'seal_number') and not invoice.logistics.seal_number:
            invoice.logistics.seal_number = existing_invoice.logistics.seal_number if existing_invoice.logistics else None
        elif hasattr(invoice.logistics, 'seal_number'):
            invoice.logistics.seal_number = clean_str(invoice.logistics.seal_number)
    
    # Columna X: Fecha de Emisión
    if not invoice.emission_date:
        invoice.emission_date = existing_invoice.emission_date
    
    # Columna Y: Tipo de Peso (Opcional)
    if invoice.logistics:
        if hasattr(invoice.logistics, 'weight_type') and not invoice.logistics.weight_type:
            invoice.logistics.weight_type = existing_invoice.logistics.weight_type if existing_invoice.logistics else None
        elif hasattr(invoice.logistics, 'weight_type'):
            invoice.logistics.weight_type = clean_str(invoice.logistics.weight_type).upper()
    
    # Columna Z: Número de Manifiesto (Opcional)
    if invoice.compliance_mx.manifest_number:
        if not invoice.compliance_mx.manifest_number:
            invoice.compliance_mx.manifest_number = existing_invoice.compliance_mx.manifest_number if existing_invoice.compliance_mx else None
        else:
            invoice.compliance_mx.manifest_number = clean_str(invoice.compliance_mx.manifest_number)
    
    # Columna AA: E-Document (Opcional)
    if invoice.compliance_mx:
        if not invoice.compliance_mx.edocument:
            invoice.compliance_mx.edocument = existing_invoice.compliance_mx.edocument if existing_invoice.compliance_mx else None
        else:
            invoice.compliance_mx.edocument = clean_str(invoice.compliance_mx.edocument)
    
    # Columna AB: Num. Operación (Opcional)
    if invoice.compliance_mx:
        if not invoice.compliance_mx.vucem_operation_num:
            invoice.compliance_mx.vucem_operation_num = existing_invoice.compliance_mx.vucem_operation_num if existing_invoice.compliance_mx else None
        else:
            invoice.compliance_mx.vucem_operation_num = clean_str(invoice.compliance_mx.vucem_operation_num)
    
    # Columna AB: Aduana (OBLIGATORIO)
    if invoice.compliance_mx:
        if not invoice.compliance_mx.aduana:
            invoice.compliance_mx.aduana = existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None
        else:
            invoice.compliance_mx.aduana = clean_str(invoice.compliance_mx.aduana)
    
    # Columna AC: Enviado Por (Obligatorio)
    if invoice.compliance_mx:
        if not invoice.compliance_mx.shipped_by_id:
            invoice.compliance_mx.shipped_by_id = existing_invoice.compliance_mx.shipped_by_id if existing_invoice.compliance_mx else None
        else:
            invoice.compliance_mx.shipped_by_id = clean_str(invoice.compliance_mx.shipped_by_id)
    
    # Columna AD: Aduana_Cruce (Obligatorio)    
    current_aduana = invoice.compliance_mx.aduana if invoice.compliance_mx else (existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None)
    if not current_aduana:
        errors.add_required_error("aduana")
    
    # Columna AC: Sección de Despacho / Puerto de Entrada (Opcional)
    if invoice.compliance_mx:
        if not invoice.compliance_mx.port_of_entry:
            invoice.compliance_mx.port_of_entry = existing_invoice.compliance_mx.port_of_entry if existing_invoice.compliance_mx else None
        else:
            invoice.compliance_mx.port_of_entry = clean_str(invoice.compliance_mx.port_of_entry)
    
    # Columna AE: Observación en Español (Opcional)
    if not invoice.observation_es:
        invoice.observation_es = existing_invoice.observation_es
    else:
        invoice.observation_es = clean_str(invoice.observation_es)
    
    # Columna AF: Observación en Inglés (Opcional)
    if not invoice.observation_en:
        invoice.observation_en = existing_invoice.observation_en
    else:
        invoice.observation_en = clean_str(invoice.observation_en)
        
    # Columna AG: cfdi_uuid (Opcional)    
    if not invoice.cfdi_uuid:
        invoice.cfdi_uuid = existing_invoice.cfdi_uuid if existing_invoice.compliance_mx else None
    else:
        invoice.cfdi_uuid = clean_str(invoice.cfdi_uuid)

    # Columna AH: Localizacion (Opcional)
    if invoice.compliance_mx.location:
        if not invoice.compliance_mx.location:
            invoice.compliance_mx.location = existing_invoice.compliance_mx.location if existing_invoice.compliance_mx.location else None
        else:
            invoice.compliance_mx.location = clean_str(invoice.compliance_mx.location)