from decimal import Decimal
from sqlalchemy import func, exists
from sqlalchemy.orm import Session
from core.exceptions import ErrorCollector

from ...models import LineItem
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
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 api.v1.modules.a76.classes.models import Class
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 line.description.description_spanish is None:
            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
            )

        # Marca y modelo
        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

        # ==========================================
        # DATOS FA (Activo Fijo) — ACTUALIZACIÓN PARCIAL
        # ==========================================
        fa_data: FaLineItem = getattr(line, "fa_data", None)
        existing_fa_data: FaLineItem = getattr(existing_line, "fa_data", None)

        if fa_data is not None and existing_fa_data is not None:
            # Col. C: Procedencia de la Importación (TipoMovImpo)
            if not fa_data.movement_type_import:
                fa_data.movement_type_import = existing_fa_data.movement_type_import

            # Col. F: ¿Descarga la línea? (Descarga)
            if fa_data.discharge is None:
                fa_data.discharge = existing_fa_data.discharge

            # Col. D: Factura de Importación — obligatoria sólo si hay descarga
            if not fa_data.search_invoice:
                fa_data.search_invoice = existing_fa_data.search_invoice

            # Col. E: Línea de Importación — obligatoria sólo si hay descarga
            if fa_data.search_line is None:
                fa_data.search_line = existing_fa_data.search_line

            # REVISA_FACTURA logic if both invoice and line are present (even after partial update)
            if fa_data.discharge is True and fa_data.search_invoice and fa_data.search_line:
                from sqlalchemy import exists
                linked_inv = db.query(InvoiceHeader).filter(
                    InvoiceHeader.invoice_number == fa_data.search_invoice,
                    InvoiceHeader.tenant_id == tenant_id,
                    InvoiceHeader.company_id == company_id,
                    InvoiceHeader.operation_type == "imp"
                ).first()

                if not linked_inv:
                    errors.add_error(
                        field=f"line[{line_number}].fa_data.search_invoice",
                        message="La Factura No Existe Capture o seleccione una que si exista",
                        code="LINKED_INVOICE_NOT_FOUND",
                        solution=["Verificar el número de factura de importación."]
                    )
                elif linked_inv.status != "processed":
                    errors.add_error(
                        field=f"line[{line_number}].fa_data.search_invoice",
                        message="La Factura no esta Actualizada, Capture o seleccione una que si este Actualizada",
                        code="LINKED_INVOICE_NOT_PROCESSED",
                        solution=["Actualizar/Procesar la factura de importación antes de descargarla."]
                    )
                else:
                    # Invoice is valid and processed, check the line
                    linked_line_exists = db.query(exists().where(
                        (LineItem.invoice_id == linked_inv.id) &
                        (LineItem.line_number == fa_data.search_line) &
                        (LineItem.tenant_id == tenant_id) &
                        (LineItem.company_id == company_id)
                    )).scalar()

                    if not linked_line_exists:
                        errors.add_error(
                            field=f"line[{line_number}].fa_data.search_line",
                            message="La Partida de la Factura No Existe Capture o seleccione una que si exista",
                            code="LINKED_LINE_NOT_FOUND",
                            solution=["Verificar el número de renglón en la factura de importación."]
                        )
                    else:
                        # Validation for search_type == "Clase" (Parity with Clarion Valida.Validaciones)
                        # We need to get the actual IDs to compare
                        linked_import_line = db.query(LineItem).filter(
                            LineItem.invoice_id == linked_inv.id,
                            LineItem.line_number == fa_data.search_line,
                            LineItem.tenant_id == tenant_id,
                            LineItem.company_id == company_id
                        ).first()

                        if linked_import_line:
                            current_class_id = line.class_id if line.class_id else existing_line.class_id
                            if fa_data.search_type == "Clase" and linked_import_line.class_id != current_class_id:
                                current_class = db.query(Class).filter(Class.id == current_class_id).first()
                                import_class = db.query(Class).filter(Class.id == linked_import_line.class_id).first()
                                
                                errors.add_error(
                                    field=f"line[{line_number}].class_id",
                                    message=f"Es necesario que la clase: {current_class.class_code if current_class else current_class_id} sea igual a la clase: {import_class.class_code if import_class else linked_import_line.class_id} de la factura de importación seleccionada.",
                                    code="CLASS_MISMATCH_FOR_SEARCH_TYPE_CLASE",
                                    solution=["Asegurarse de que el activo que se exporta pertenezca a la misma familia/clase que el que se importó."]
                                )

                        # REVISA_CANTIDADES_A_DESC_TEM logic for Update
                        
                        # 0. Get the internal ID of the linked import line
                        linked_import_line = db.query(LineItem).filter(
                            LineItem.invoice_id == linked_inv.id,
                            LineItem.line_number == fa_data.search_line,
                            LineItem.tenant_id == tenant_id,
                            LineItem.company_id == company_id
                        ).first()
                        
                        if linked_import_line:
                            # 1. Get current balance from ledger
                            from api.v1.modules.a24.balance_movements.models import BalanceMovement, NEGATIVE_MOVEMENTS
                            from sqlalchemy import case, select
                            
                            sign_expr = case(
                                (BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), Decimal("-1")),
                                else_=Decimal("1"),
                            )
                            available_balance = db.execute(
                                select(func.sum(sign_expr * BalanceMovement.quantity)).where(
                                    BalanceMovement.import_item_line_id == linked_import_line.id
                                )
                            ).scalar() or Decimal("0")

                            # 2. Get pending discharges in this same invoice (sibling lines)
                            # EXCLUDING the current line we are updating
                            from api.v1.modules.a76.items.line_quantities.models import LineQuantity
                            pending_sum = db.query(func.sum(LineQuantity.quantity)).join(
                                LineItem, LineItem.id == LineQuantity.id
                            ).join(
                                FaLineItem, FaLineItem.id == LineItem.id
                            ).filter(
                                LineItem.invoice_id == line.invoice_id,
                                LineItem.id != existing_line.id, # IMPORTANT: Exclude self
                                FaLineItem.search_invoice == fa_data.search_invoice,
                                FaLineItem.search_line == fa_data.search_line,
                                FaLineItem.movement_type_import == fa_data.movement_type_import,
                                FaLineItem.discharge == True
                            ).scalar() or Decimal("0")

                            current_qty = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity
                            remaining = available_balance - pending_sum - current_qty

                            # Note: Hard validation removed here to allow 'Multiple Source Discharge' 
                            # or 'Automatic Deficit Handling' logic to function during full invoice processing.
                            # Current balance for information: {available_balance}, short: {remaining if remaining < 0 else 0}
                            pass


            # Subpartidas (EsSubPartida / SubPartida)
            if fa_data.is_subitem is None:
                fa_data.is_subitem = existing_fa_data.is_subitem
            if fa_data.subitem_number is None:
                fa_data.subitem_number = existing_fa_data.subitem_number

        # 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: TODO: Tomar de SisImp:MetValor (preferencias del sistema)

        # 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
