"""
ASIGNA_VALORES_PARTIDAS_ASIGNA_PESOS
Resets and recalculates unit costs, export values
(KGS ↔ LBS) for every line item of an export invoice.

Two cost-assignment strategies (controlled by SisExp:ValFactTC):
  TCE  → bulk SQL UPDATE using the invoice-level exchange rate (Loc:TipoCambio).
  else → per-line loop that resolves each line's exchange rate from its
         source import invoice (TEM → QFacImp, DEF → QFacImpDef).

After costs are assigned the routine always:
  1. Calls REVISA_UMA for each line.
"""

from decimal import Decimal
from typing import List

from sqlalchemy.orm import Session

from api.v1.modules.a76.invoices.models import Currency, InvoiceHeader
from api.v1.modules.a76.invoices.common.process.review_uma import revisa_uma
from api.v1.modules.a76.items.models import LineItem
from core.exceptions import ErrorCollector

_KGS_TO_LBS = Decimal("2.204624")


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------

def _assign_costs_tce(
    lines: List[LineItem],
    currency: Currency,
    tc: Decimal,
    tc_mm: Decimal,
) -> None:
    """
    Bulk-style cost assignment when SisExp:ValFactTC = 'TCE'.
    Uses the single invoice-level exchange rate for all lines.

    Legacy equivalent (branch 1 of the IF SisExp:ValFactTC):
        UPDATE QEqeMaq SET CostoUnitarioDlls = ..., CostoUnitarioPesos = ...,
               ValorExpoMN = ..., ValorExpoME = ..., ValorExpoMC = ...
        WHERE Consecutivo = <invoice.id>
    """
    for line in lines:
        if line.financial is None or line.quantity is None:
            continue

        capture = line.financial.unit_cost_capture or Decimal(0)
        qty = line.quantity.quantity or Decimal(0)

        if currency == Currency.FOREIGN:  # ME
            line.financial.unit_cost_usd = capture
            line.financial.unit_cost_mxn = capture * tc
            line.financial.value_mxn = qty * capture * tc
            line.financial.value_usd = qty * capture
            line.financial.value_mc = qty * capture

        elif currency == Currency.LOCAL:  # MN
            line.financial.unit_cost_mxn = capture
            line.financial.unit_cost_usd = (capture / tc) if tc else Decimal(0)
            line.financial.value_mxn = qty * capture
            line.financial.value_usd = (qty * capture / tc) if tc else Decimal(0)
            line.financial.value_mc = qty * capture

        elif currency == Currency.MANUAL:  # MC
            cost_usd = capture * tc_mm
            line.financial.unit_cost_usd = cost_usd
            line.financial.unit_cost_mxn = cost_usd * tc
            line.financial.value_mxn = qty * cost_usd * tc
            line.financial.value_usd = qty * cost_usd
            line.financial.value_mc = qty * capture


def _assign_costs_per_line(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    currency: Currency,
    tc_mm: Decimal,
    errors: ErrorCollector,
) -> None:
    """
    Per-line cost assignment when SisExp:ValFactTC != 'TCE'.
    Each line resolves the exchange rate from its source import invoice
    (TEM → QFacImp header,  DEF → QFacImpDef header).

    Legacy equivalent (ELSE branch – LOOP QEqeMaq):
        If TipoMovImpo = 'TEM' → ACCESS:QFacImp.TryFetch(EqiFim:FKFacturaImpo)
        Else                   → ACCESS:QFacImpDef.TryFetch(EqiFID:FKFacImpoDef)
        then assign CostoUnitarioDlls / CostoUnitarioPesos / ValorExpoMN/ME/MC
    """
    from api.v1.modules.a76.invoices.models import InvoiceHeader as InvHeader

    for line in lines:
        if line.financial is None or line.quantity is None:
            continue

        capture = line.financial.unit_cost_capture or Decimal(0)
        qty = line.quantity.quantity or Decimal(0)

        # Resolve the exchange rate from the source import invoice
        line_tc = _get_source_invoice_tc(db, invoice, line, errors)

        if currency == Currency.FOREIGN:  # ME
            line.financial.unit_cost_usd = capture
            line.financial.unit_cost_mxn = capture * line_tc

        elif currency == Currency.LOCAL:  # MN
            line.financial.unit_cost_usd = (capture / line_tc) if line_tc else Decimal(0)
            line.financial.unit_cost_mxn = capture

        elif currency == Currency.MANUAL:  # MC
            cost_usd = capture * tc_mm
            line.financial.unit_cost_usd = cost_usd
            line.financial.unit_cost_mxn = cost_usd * line_tc

        # Values are always: cost x qty
        line.financial.value_mxn = (line.financial.unit_cost_mxn or Decimal(0)) * qty
        line.financial.value_usd = (line.financial.unit_cost_usd or Decimal(0)) * qty
        line.financial.value_mc = capture * qty


def _get_source_invoice_tc(
    db: Session,
    invoice: InvoiceHeader,
    line: LineItem,
    errors: ErrorCollector,
) -> Decimal:
    """
    Returns the exchange rate of the import invoice linked to this export line.

    Movement type 'TEM' → look up QFacImp (temporary import header).
    Any other type      → look up QFacImpDef (definitive import header).

    Falls back to the export invoice's own exchange rate if the source invoice
    is not found, and records a warning-level error.

    Legacy fields:
        EqiPex:TipoMovImpo  → line.customs.origin_procedure
        EqiPex:FacturaImpo  → line.fa_data.search_invoice
    """
    fallback_tc = Decimal(str(invoice.financials.exchange_rate or 0))

    movement_type = (line.customs.origin_procedure or "").strip().upper() if line.customs else ""
    import_invoice_number = (line.fa_data.search_invoice if getattr(line, "fa_data", None) else None) or ""

    if not import_invoice_number:
        return fallback_tc

    from api.v1.modules.a76.invoices.models import InvoiceHeader as InvHeader

    if movement_type == "TEM":
        source = (
            db.query(InvHeader)
            .filter(
                InvHeader.invoice_number == import_invoice_number,
                InvHeader.tenant_id == invoice.tenant_id,
                InvHeader.company_id == invoice.company_id,
            )
            .first()
        )
    else:
        # DEF: definitive import
        source = (
            db.query(InvHeader)
            .filter(
                InvHeader.invoice_number == import_invoice_number,
                InvHeader.tenant_id == invoice.tenant_id,
                InvHeader.company_id == invoice.company_id,
            )
            .first()
        )

    if source is None or source.financials is None:
        errors.add_error(
            field=f"line[{line.line_number}].import_invoice",
            message=(
                f"No se encontró la factura de importación '{import_invoice_number}' "
                f"referenciada en la partida {line.line_number}."
            ),
            solution=[
                "Verificar el número de factura de importación en la partida.",
            ],
            code="SOURCE_INVOICE_NOT_FOUND",
        )
        return fallback_tc

    return Decimal(str(source.financials.exchange_rate or 0)) or fallback_tc

# ---------------------------------------------------------------------------
# Main entry point
# ---------------------------------------------------------------------------

def assign_values(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    tenant_id: str,
    company_id: str,
    cal_val_base_tc: int,
    errors: ErrorCollector,
) -> None:
    """
    ASIGNA_VALORES_PARTIDAS_ASIGNA_PESOS

    1. Assigns unit costs and export values to every line (ME / MN / MC).
       Strategy A (TCE): bulk assignment using the invoice exchange rate.
       Strategy B (per-line): resolves exchange rate per source import invoice.
    2. Calls REVISA_UMA for each line.    
    """
    currency = invoice.financials.currency
    tc = Decimal(str(invoice.financials.exchange_rate or 0))
    tc_mm = Decimal(str(invoice.financials.exchange_rate_mm or 0))
    

    # --- Step 1: Assign costs / values ---------------------------------------
    # Resolve Settings for Strategy
    from api.v1.modules.a76.app_settings.service import AppSettingsService
    settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id))
    
    inv_type = (invoice.invoice_type or "").strip().upper()
    op_type = "exp"
    
    # Hierarchical resolve: invoices.types.exp.{type}.ssisgen.ssimpFormData
    inv_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(inv_type, {})
    form_data = inv_map.get("ssisgen", {}).get("ssimpFormData", {}) or inv_map.get("qsisgen", {}).get("ssimpFormData", {}) or settings.get("ssisexpo", {})
    
    # Parameter ValFactTC: TCE (Encabezado) vs PER_LINE (Partida)
    val_fact_tc = (form_data.get("valfacttc") or form_data.get("ValFactTC") or "").strip().upper()
    
    # Fallback/Auto-resolve:
    # Si la configuración indica usar el TC del Pedimento (cal_val_base_tc = 1),
    # usualmente forzamos TCE porque el encabezado ya fue nivelado al pago del pedimento.
    if cal_val_base_tc == 1:
        val_fact_tc = "TCE"
    
    # Default if empty
    if not val_fact_tc:
        val_fact_tc = "PER_LINE"

    if val_fact_tc == "TCE":
        _assign_costs_tce(lines, currency, tc, tc_mm)
    else:
        _assign_costs_per_line(db, invoice, lines, currency, tc_mm, errors)

    # --- Step 2: REVISA_UMA --------------------------------------------------
    for line in lines:
        revisa_uma(db=db, line=line, tenant_id=tenant_id, company_id=company_id, errors=errors)

    # --- Step 3: Assign weights ----------------------------------------------
    # In anexo76 will be calculated in realtime based in weight type by conversion factor
    
