from decimal import Decimal
from sqlalchemy.orm import Session
from core.exceptions import ErrorCollector

from ...models import LineItem
from api.v1.modules.a76.invoices.models import InvoiceHeader
from api.v1.modules.sitar.fracciones_usa.catalog_resolve import (
    american_fraction_ad_valorem_from_row,
    resolve_american_fraction_from_sitar,
    store_canonical_american_code,
)
from .common import validate_common


def validate_update(
    db: Session,
    line: LineItem,
    existing_line: LineItem,    
    tenant_id: int,
    company_id: int,
    errors: ErrorCollector,
    line_number: int,
):
    """
    Validar y procesar actualización parcial de línea de importación temporal.
    Si un campo no se proporciona, se mantiene el valor existente.
    """
    validate_common(db, line, tenant_id, company_id, errors, line_number)

    if not errors.has_errors():
        # Ensure nested objects exist for calculations/partial updates
        from ...schemas import (
            LineFinancialCreate,
            LineQuantityCreate,
            LineCustomCreate,
            LineDescriptionCreate,
        )

        if line.financial is None:
            line.financial = LineFinancialCreate()
        if line.quantity is None:
            line.quantity = LineQuantityCreate()
        if line.customs is None:
            line.customs = LineCustomCreate()
        if line.description is None:
            line.description = LineDescriptionCreate()

        # Obtener la factura para acceder a tipo de cambio, moneda y peso
        invoice: InvoiceHeader = (
            db.query(InvoiceHeader)
            .filter(
                InvoiceHeader.id == line.invoice_id,
                InvoiceHeader.tenant_id == tenant_id,
                InvoiceHeader.company_id == company_id
            )
            .first()
        )

        if not invoice or not invoice.financials or not invoice.logistics:
            errors.add_error(
                field=f"line[{line_number}]",
                message="No se pudo obtener información de la factura",
                solution=[
                    "Verificar que la factura existe y tiene datos financieros y logísticos"
                ],
                code="INVOICE_DATA_MISSING",
            )
            return

        # ==========================================
        # ACTUALIZACIÓN PARCIAL DE CAMPOS
        # Si no se proporciona, mantener valor existente
        # ==========================================

        # Tipo de cambio de la factura
        exchange_rate = invoice.financials.exchange_rate or Decimal("1.0")

        # Unidad de medida
        if not line.unit_of_measure:
            line.unit_of_measure = existing_line.unit_of_measure

        # Costo unitario
        if line.financial.unit_cost_capture is None:
            line.financial.unit_cost_capture = existing_line.financial.unit_cost_capture
            
        # Recalcular valores monetarios si el costo o la cantidad cambian
        currency_type = invoice.financials.currency_type
        unit_cost_capture = line.financial.unit_cost_capture or Decimal("0")
        
        if currency_type in ["USD", "ME"]:
            line.financial.unit_cost_usd = unit_cost_capture
            line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate
        elif currency_type in ["MXN", "MN"]:
            line.financial.unit_cost_usd = (unit_cost_capture / exchange_rate) if exchange_rate else Decimal("0")
            line.financial.unit_cost_mxn = unit_cost_capture

        quantity = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity
        quantity = quantity if quantity is not None else Decimal("0")

        if line.financial.unit_cost_usd is not None:
            line.financial.value_usd = line.financial.unit_cost_usd * quantity
        if line.financial.unit_cost_mxn is not None:
            line.financial.value_mxn = line.financial.unit_cost_mxn * quantity

        line.financial.customs_value_usd = line.financial.value_usd
        line.financial.customs_value_mxn = line.financial.value_mxn
        
        line.financial.value_temp_material_usd = line.financial.value_usd
        line.financial.value_temp_material_mxn = line.financial.value_mxn

        # Convertir peso neto si se proporcionó
        invoice_weight_type = invoice.logistics.weight_type
        if line.quantity.net_weight is not None:
            # Se proporcionó nuevo peso neto, convertir según tipo
            net_weight_input = line.quantity.net_weight

            if invoice_weight_type.lower() == "kgs":
                line.quantity.net_weight = net_weight_input
            else:  # libras, convertir a kilos
                line.quantity.net_weight = net_weight_input / Decimal("2.204624")
        else:
            # Mantener peso existente
            line.quantity.net_weight = existing_line.quantity.net_weight        

        # Convertir peso bruto si se proporcionó
        if line.quantity.gross_weight is not None:
            gross_weight_input = line.quantity.gross_weight

            if invoice_weight_type.lower() == "kgs":
                line.quantity.gross_weight = gross_weight_input
            else:  # libras, convertir a kilos
                line.quantity.gross_weight = gross_weight_input / Decimal("2.204624")
        else:
            # Mantener peso existente
            line.quantity.gross_weight = existing_line.quantity.gross_weight

        # Cantidad de bultos
        if line.quantity.package_quantity is None:
            line.quantity.package_quantity = existing_line.quantity.package_quantity

        # Clave de bultos
        if not line.quantity.package_id:
            line.quantity.package_id = existing_line.quantity.package_id

        # País de origen
        if not line.customs.origin_country:
            line.customs.origin_country = existing_line.customs.origin_country

        # Fracción arancelaria
        if not line.customs.fraction:
            line.customs.fraction = existing_line.customs.fraction

        # Tipo de fracción
        if not line.customs.fraction_type:
            line.customs.fraction_type = existing_line.customs.fraction_type

        # Sector
        if not line.customs.sector:
            line.customs.sector = existing_line.customs.sector

        # Fracción americana y su advalorem (SITAR)
        if line.customs.american_fraction:
            resolved = resolve_american_fraction_from_sitar(str(line.customs.american_fraction))
            if resolved:
                sitar_row, canon = resolved
                line.customs.american_fraction = store_canonical_american_code(canon)
                line.customs.advalorem_american = american_fraction_ad_valorem_from_row(
                    sitar_row
                )
        else:
            # Mantener fracción americana existente
            line.customs.american_fraction = existing_line.customs.american_fraction
            line.customs.advalorem_american = existing_line.customs.advalorem_american

        # Orden de compra
        if not line.order:
            line.order = existing_line.order

        # Descripciones
        if not line.description.description_spanish:
            line.description.description_spanish = (
                existing_line.description.description_spanish
            )

        if not line.description.description_english:
            line.description.description_english = (
                existing_line.description.description_english
            )

        if line.description.extra_description is None:
            line.description.extra_description = (
                existing_line.description.extra_description
            )

        # Brand and model
        if line.description.brand is not None:
            line.description.brand = line.description.brand.upper().strip()
        else:
            line.description.brand = existing_line.description.brand

        if line.description.model is not None:
            line.description.model = line.description.model.upper().strip()
        else:
            line.description.model = existing_line.description.model

        # --- Resolve Settings for inherited parameters ---
        from api.v1.modules.a76.app_settings.service import AppSettingsService
        settings = AppSettingsService.get_resolved_settings(db, tenant_id, company_id)
        
        inv_type = (invoice.invoice_type or "").strip().upper()
        op_type = (invoice.operation_type or "").strip().lower() # 'imp' or 'exp'
        
        # Helper to get nested value from invoices.types.{op}.{type}.ssisgen.ssimpFormData
        inv_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(inv_type, {})
        # Prefeir ssisgen for this type, then qsisgen, then root ssimpo
        form_data = inv_map.get("ssisgen", {}).get("ssimpFormData", {}) or inv_map.get("qsisgen", {}).get("ssimpFormData", {}) or settings.get("ssimpo", {})

        # Subpartidas (si aplica)
        # Clarion: LOC:LevantarSubpartidas = S
        levantar_sub = bool(form_data.get("levantar_subpartidas") or form_data.get("LevantarSubpartidas") or False)
        if levantar_sub:
            # TODO: Add specific sub-item validation if needed (e.g. parent_line mandatory if it's a subpartida)
            # Currently we just ensure the field is carried over if not provided
            pass

        # Número de parte
        if not line.part_number_id:
            line.part_number_id = existing_line.part_number_id

        # Pago de impuesto
        if line.tax_payment is None:
            line.tax_payment = existing_line.tax_payment

        # Forma de pago
        if not line.payment_method:
            line.payment_method = existing_line.payment_method

        # Método de valoración
        if not line.valuation_method:
            if existing_line.valuation_method:
                line.valuation_method = existing_line.valuation_method
            else:
                # Tomar de SisImp/SisDef:MetValor (preferencias del sistema)
                line.valuation_method = form_data.get("metvalor") or form_data.get("MetValor")

        # Número de entrada
        if not line.description.entry_number:
            line.description.entry_number = existing_line.description.entry_number

        # Lote
        if not line.description.lot:
            line.description.lot = existing_line.description.lot
