from sqlalchemy.orm import Session
from api.v1.modules.a76.invoices.models import InvoiceFinancials, InvoiceHeader, InvoiceLogistics

from ...models import LineItem
from ...series.models import Serie
from api.v1.modules.a76.items.models import LineItem
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
from api.v1.modules.a76.classes.models import Class
from api.v1.modules.a76.app_settings.service import AppSettingsService
from decimal import Decimal


def apply_calculations(
    db: Session, line: LineItem, tenant_id: int, company_id: int, line_number: int
):
    # 0. Obtener parámetros de configuración para validaciones dinámicas
    settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id))
    
    def find_in_obj(obj, target_key):
        if isinstance(obj, dict):
            for k, v in obj.items():
                if k.lower() == target_key.lower():
                    return v
                res = find_in_obj(v, target_key)
                if res is not None: return res
        elif hasattr(obj, '__dict__'):
            dict_rep = obj.__dict__
            for k, v in dict_rep.items():
                if k.startswith('_'): continue
                if k.lower() == target_key.lower():
                    return v
                res = find_in_obj(v, target_key)
                if res is not None: return res
        return None

    # Parámetro SCAF: Calcular costo unitario en base a valor total
    calc_costo_unit = find_in_obj(settings, 'calcularcostounitarioenbaseavalortotal') or \
                      find_in_obj(settings, 'calcularcostounitarioenbaseavalortotalscaf')

    if str(calc_costo_unit) == "1" and line.financial and (line.financial.unit_cost_capture or 0) == 0:
        qty = line.quantity.quantity or Decimal("0")
        if qty > 0:
            total_val = line.financial.value_usd or line.financial.value_mxn or Decimal("0")
            if total_val > 0:
                line.financial.unit_cost_capture = total_val / qty

    calculate_values(db, line, tenant_id, company_id)

    # ==========================================
    # LLEVASERIE / LlevaCodFDA defaults
    # ==========================================
    # EqiPex:LlevaCodFDA = 'N'
    line.has_fda_code = False

    # ==========================================
    # PAGO IMPUESTO default: SisExp:PagoImpuesto
    # ==========================================
    if line.tax_payment is None:
        pref_pago = find_in_obj(settings, 'pagoimpuesto')
        if pref_pago:
            line.tax_payment = True if str(pref_pago).lower() == 'si' else False
        else:
            line.tax_payment = False

    # ==========================================
    # FORMA DE PAGO default: SisExp:FormaPago
    # ==========================================
    if not line.payment_method:
        pref_forma = find_in_obj(settings, 'formapago')
        line.payment_method = pref_forma or "5"

    # ==========================================
    # SUBPARTIDAS: EsSubPartida / ContieneSubP / IncuyeSubPartidas
    # ==========================================
    fa_data: FaLineItem = getattr(line, "fa_data", None)
    if fa_data is not None:
        if fa_data.is_subitem is None:
            fa_data.is_subitem = False

        if not fa_data.is_subitem:
            # Es partida principal — subitem_number se fuerza a 0
            fa_data.subitem_number = 0

        # ContieneSubP: verificar si ya existen subitems en DB que referencian esta línea
        # (útil en updates; en create siempre será False porque la línea aún no existe)
        existing_subitems = (
            db.query(FaLineItem)
            .join(LineItem, FaLineItem.id == LineItem.id)
            .filter(
                LineItem.invoice_id == line.invoice_id,
                LineItem.line_number == line_number,
                LineItem.tenant_id == tenant_id,
                LineItem.company_id == company_id,
                FaLineItem.is_subitem == True,
                FaLineItem.subitem_number == line_number,
            )
            .count()
        )
        fa_data.contains_subitems = existing_subitems > 0

    invoice_date = db.query(InvoiceHeader.invoice_date).filter(InvoiceHeader.id == line.invoice_id, InvoiceHeader.tenant_id == tenant_id, InvoiceHeader.company_id == company_id).scalar()
        
    line.depreciation_date = invoice_date
    
    part_info = getattr(line, "part_info", None)
    if (not line.description.description_spanish and not line.description.description_english) and part_info and (part_info.description_spanish and part_info.description_english):
        line.description.description_spanish = part_info.description_spanish
        line.description.description_english = part_info.description_english
    else:
        if not line.description.description_spanish:
            class_desc = (
                db.query(Class.description_es, Class.description_en)
                .filter(Class.id == line.class_id, Class.tenant_id == tenant_id, Class.company_id == company_id)
                .first()
            )
            if class_desc:
                line.description.description_spanish = class_desc[0]
                if not line.description.description_english:
                    line.description.description_english = class_desc[1]


def calculate_values(
    db: Session,
    line: LineItem,
    tenant_id: int,
    company_id: int,
) -> None:
    """
    Calcula valores financieros y copia campos de la línea de importación referenciada.

    Traduce el CALCULOS ROUTINE de Clarion:
    - Busca la factura de importación por fa_data.search_invoice (TEM → DEF como fallback)
    - Copia clase, unidad de medida, fracción (si fa_data.discharge), país, tipo fracción,
      bultos y descripción inglés desde la línea de importación encontrada
    - Calcula valores en moneda (USD/MXN/MC) según la moneda de la factura
    """
    fa_data: FaLineItem = getattr(line, "fa_data", None)

    # ==========================================
    # BUSCAR FACTURA DE IMPORTACIÓN (TEM → DEF)
    # EqiFim:FacturaImpo = fa_data.search_invoice / EqiPim:LineaImpo = fa_data.search_line
    # ==========================================
    import_line = None

    import_invoice_number = fa_data.search_invoice if fa_data else None
    import_line_number = fa_data.search_line if fa_data else None
    

    if import_invoice_number and import_line_number:
        # 1. Intentar TEM (Importación Temporal)
        tem_invoice = (
            db.query(InvoiceHeader)
            .filter(
                InvoiceHeader.invoice_number == import_invoice_number,
                InvoiceHeader.invoice_type == "TEM",
                InvoiceHeader.tenant_id == tenant_id,
                InvoiceHeader.company_id == company_id,
            )
            .first()
        )
        if tem_invoice:
            import_line = (
                db.query(LineItem)
                .filter(
                    LineItem.invoice_id == tem_invoice.id,
                    LineItem.line_number == import_line_number,
                    LineItem.tenant_id == tenant_id,
                    LineItem.company_id == company_id,
                )
                .first()
            )

        # 2. Si no hay TEM, intentar DEF (Importación Definitiva)
        if not import_line:
            def_invoice = (
                db.query(InvoiceHeader)
                .filter(
                    InvoiceHeader.invoice_number == import_invoice_number,
                    InvoiceHeader.invoice_type == "DEF",
                    InvoiceHeader.tenant_id == tenant_id,
                    InvoiceHeader.company_id == company_id,
                )
                .first()
            )
            if def_invoice:
                import_line = (
                    db.query(LineItem)
                    .filter(
                        LineItem.invoice_id == def_invoice.id,
                        LineItem.line_number == import_line_number,
                        LineItem.tenant_id == tenant_id,
                        LineItem.company_id == company_id,
                    )
                    .first()
                )

    # ==========================================
    # COPIAR CAMPOS DESDE LÍNEA DE IMPORTACIÓN
    # EqiPex:Clase, UnidadMedida, FraccionExpo (condicional), PaisOrigen,
    # TipoFraccion, CantBultos, ClaveBultos, DescripcionE
    # ==========================================
    if import_line:
        line.class_id = import_line.class_id
        line.unit_of_measure = import_line.unit_of_measure

        # Fracción: copiar solo si fa_data.discharge == True (≡ ColumnaV != '')
        if fa_data and fa_data.discharge and import_line.customs:
            line.customs.fraction = import_line.customs.fraction

        if import_line.customs:
            line.customs.origin_country = import_line.customs.origin_country
            line.customs.fraction_type = import_line.customs.fraction_type

        if import_line.quantity:
            line.quantity.package_quantity = import_line.quantity.package_quantity
            line.quantity.package_id = import_line.quantity.package_id

        if import_line.description and not line.description.description_english:
            # Only fill from import if the user hasn't captured their own English description
            line.description.description_english = import_line.description.description_english

    # ==========================================
    # CÁLCULOS DE VALORES EN MONEDA
    # Una sola fuente: currency_type (USD/ME, MXN/MN) cuando está presente, paridad con import y CSV.
    # ==========================================
    result = (
        db.query(
            InvoiceFinancials.currency,
            InvoiceFinancials.currency_type,
            InvoiceFinancials.exchange_rate,
        )
        .filter(
            InvoiceFinancials.invoice_id == line.invoice_id,
            InvoiceFinancials.tenant_id == tenant_id,
            InvoiceFinancials.company_id == company_id,
        )
        .first()
    )
    if not result:
        return

    if not line.financial or not line.quantity:
        return

    currency, currency_type, exchange_rate = result
    if currency_type in ("USD", "ME"):
        currency = "foreign"
    elif currency_type in ("MXN", "MN"):
        currency = "local"

    # Evitar None * None / NoneType * Decimal al guardar sin cantidad o sin costo capturado
    qty = (
        line.quantity.quantity
        if line.quantity.quantity is not None
        else Decimal("0")
    )
    capture = (
        line.financial.unit_cost_capture
        if line.financial.unit_cost_capture is not None
        else Decimal("0")
    )

    if currency == "foreign":  # ME
        line.financial.unit_cost_usd = capture
        line.financial.value_usd = capture * qty
        line.financial.unit_cost_mxn = capture * (exchange_rate or 1)
        line.financial.value_mxn = line.financial.unit_cost_mxn * qty
        line.financial.value_mc = capture * qty
    elif currency == "local":  # MN
        line.financial.unit_cost_mxn = capture
        line.financial.value_mxn = line.financial.unit_cost_mxn * qty
        line.financial.unit_cost_usd = capture / (exchange_rate or 1)
        line.financial.value_usd = line.financial.unit_cost_usd * qty
        line.financial.value_mc = line.financial.unit_cost_usd * qty
    elif currency == "manual":  # MC
        line.financial.unit_cost_usd = capture / (exchange_rate or 1)
        line.financial.value_usd = line.financial.unit_cost_usd * qty
        line.financial.unit_cost_mxn = line.financial.unit_cost_usd * (exchange_rate or 1)
        line.financial.value_mxn = line.financial.unit_cost_mxn * qty
        line.financial.value_mc = capture * qty
