"""Cálculo de subtotales y totales (gTotSub).

Este módulo implementa el cálculo de totales de la transacción documentada siguiendo la
lógica del `xmlgen` original (TypeScript), incluyendo redondeo SEDECO y acumulación de IVA.
"""

from __future__ import annotations

from lxml import etree

from .config import XmlgenConfig


def redondeo_sedeco(numero: float) -> float:
    """Calcula el valor de redondeo según reglas SEDECO (múltiplos de 50 Gs).

    Retorna únicamente la parte del redondeo (la diferencia), tal como se utiliza en el
    proyecto original.
    """
    parte_decimal = float(f"{numero / 100:.2f}")
    parte_entera = int(numero / 100.0)
    resta = float(f"{parte_decimal - parte_entera:.2f}")
    a_comparar = float(f"{resta * 100:.2f}")

    if a_comparar == 50:
        return 0
    if a_comparar > 50:
        return a_comparar - 50

    return a_comparar if a_comparar < 50 else 0



def build_gtotsub(parent: etree._Element, data: dict, items_summary: list[dict], config: XmlgenConfig) -> None:
    """Construye el nodo `gTotSub` y lo agrega como hijo de `parent`.

    `items_summary` proviene de `items.build_gcamitem_nodes()` e incluye valores
    pre-calculados por ítem (EA008/dTotOpeItem, E735, E736, E737).
    """
    moneda = data.get("moneda") or ("PYG" if config.default_values else None)

    d_sub_exe = 0.0
    d_sub_exo = 0.0
    d_sub_5 = 0.0
    d_sub_10 = 0.0

    d_iva5 = 0.0
    d_iva10 = 0.0
    d_base_grav5 = 0.0
    d_base_grav10 = 0.0

    d_tot_ope_sin_desc = 0.0
    d_tot_desc = 0.0
    d_tot_ant_item = 0.0
    d_tot_ant = 0.0

    for it in items_summary:
        # Totales por ítem
        d_tot_ope_item = float(it.get("d_tot_ope_item") or 0)
        d_tot_ope_sin_desc = float(it.get("d_tot_ope_sin_desc") or 0)
        cant = float(it.get("cantidad") or 0)
        
        d_tot_desc += float(it.get("d_desc_item") or 0) * cant
        d_tot_ant_item += float(it.get("d_ant_pre_uni_it") or 0) * cant
        d_tot_ant += float(it.get("d_ant_glo_pre_uni_it") or 0) * cant

        # IVA - usar valores ya calculados en items.py
        iva_tipo = int(it.get("iva_tipo") or 0)
        iva_tasa = float(it.get("iva_tasa") or 0)

        # Usar los valores pre-calculados del ítem (consistentes con gCamIVA)
        d_bas_grav_iva = float(it.get("d_bas_grav_iva") or 0)
        d_liq_iva_item = float(it.get("d_liq_iva_item") or 0)

        # Base exenta parcial
        d_bas_exe_item = float(it.get("d_bas_exe") or 0)

        # Subtotales
        if iva_tipo == 3:  # Exento
            d_sub_exe += d_tot_ope_item
        elif iva_tipo == 4:  # Parcial
            d_sub_exe += d_bas_exe_item
        elif iva_tipo == 2:  # Exonerado
            d_sub_exo += d_tot_ope_item

        if iva_tipo in (1, 4):
            # Gravado o parcial: subtotal gravado según tasa.
            # - iAfecIVA=1: EA008
            # - iAfecIVA=4: E735 + E736 (NT13)
            if iva_tasa == 5:
                if iva_tipo == 1:
                    d_sub_5 += d_tot_ope_item
                else:
                    d_sub_5 += d_bas_grav_iva + d_liq_iva_item
            elif iva_tasa == 10:
                if iva_tipo == 1:
                    d_sub_10 += d_tot_ope_item
                else:
                    d_sub_10 += d_bas_grav_iva + d_liq_iva_item

        # IVA acumulado / bases
        if data.get("tipoImpuesto") in (1,5):
            if iva_tasa == 5:
                d_iva5 += d_liq_iva_item
                d_base_grav5 += d_bas_grav_iva
            elif iva_tasa == 10:
                d_iva10 += d_liq_iva_item
                d_base_grav10 += d_bas_grav_iva

    d_tot_ope = (
                d_sub_exe +
                d_sub_exo +
                d_sub_5 +
                d_sub_10
            )

    # TS: dTotDescGloItem se toma directo del usuario (por exactitud con prorrateos)
    d_tot_desc_glotem = float(data.get("descuentoGlobal") or 0)
    d_desc_total = d_tot_desc + d_tot_desc_glotem
    d_anticipo = d_tot_ant_item + d_tot_ant

    d_redon = 0.0
    if config.redondeo_sedeco and moneda == "PYG":
        d_redon = redondeo_sedeco(d_tot_ope)

    d_tot_gral_ope = d_tot_ope - d_redon + float(data.get("comision") or 0)

    gtotsub = etree.SubElement(parent, "gTotSub")

    etree.SubElement(gtotsub, "dSubExe").text = _fmt_money(d_sub_exe, moneda, config)
    etree.SubElement(gtotsub, "dSubExo").text = _fmt_money(d_sub_exo, moneda, config)
    etree.SubElement(gtotsub, "dSub5").text = _fmt_money(d_sub_5, moneda, config)
    etree.SubElement(gtotsub, "dSub10").text = _fmt_money(d_sub_10, moneda, config)

    etree.SubElement(gtotsub, "dTotOpe").text = _fmt_money(d_tot_ope, moneda, config)

    etree.SubElement(gtotsub, "dTotDesc").text = _fmt_money(d_tot_desc, moneda, config)
    etree.SubElement(gtotsub, "dTotDescGlotem").text = _fmt_money(d_tot_desc_glotem, moneda, config)
    etree.SubElement(gtotsub, "dTotAntItem").text = _fmt_money(d_tot_ant_item, moneda, config)
    etree.SubElement(gtotsub, "dTotAnt").text = _fmt_money(d_tot_ant, moneda, config)

    d_porc_desc_total = "0"
    if d_tot_desc_glotem > 0 and d_tot_ope_sin_desc > 0:
        # TS: toFixed(8) (string)
        d_porc_desc_total = f"{(d_tot_desc_glotem * 100.0) / d_tot_ope_sin_desc:.8f}"
    etree.SubElement(gtotsub, "dPorcDescTotal").text = d_porc_desc_total
    etree.SubElement(gtotsub, "dDescTotal").text = _fmt_money(d_desc_total, moneda, config)
    etree.SubElement(gtotsub, "dAnticipo").text = _fmt_money(d_anticipo, moneda, config)

    etree.SubElement(gtotsub, "dRedon").text = _fmt_money(d_redon, moneda, config)
    etree.SubElement(gtotsub, "dTotGralOpe").text = _fmt_money(d_tot_gral_ope, moneda, config)

    # Para PYG, SIFEN valida con enteros (sin decimales) y pequeñas derivas de float
    # pueden disparar inconsistencias (ej. 999.999999999). Normalizamos antes de emitir.
    if moneda == "PYG" and config.pyg_tax_decimals == 0:
        d_iva5 = float(int(round(d_iva5)))
        d_iva10 = float(int(round(d_iva10)))
        d_base_grav5 = float(int(round(d_base_grav5)))
        d_base_grav10 = float(int(round(d_base_grav10)))

    # IVA por tasa (F015/F016) y total (F017)
    # Algunos validadores esperan que estos campos existan (aunque sean 0) cuando el impuesto es IVA.
    if data.get("tipoImpuesto") in (1, 5):
        etree.SubElement(gtotsub, "dIVA5").text = _fmt_tax(d_iva5, moneda, config)
        etree.SubElement(gtotsub, "dIVA10").text = _fmt_tax(d_iva10, moneda, config)
        # dLiqTotIVA5/10 son para ajustes por redondeo SEDECO, normalmente 0
        etree.SubElement(gtotsub, "dLiqTotIVA5").text = "0"
        etree.SubElement(gtotsub, "dLiqTotIVA10").text = "0"

        d_tot_iva = d_iva5 + d_iva10
        etree.SubElement(gtotsub, "dTotIVA").text = _fmt_tax(d_tot_iva, moneda, config)
    etree.SubElement(gtotsub, "dBaseGrav5").text = _fmt_tax(d_base_grav5, moneda, config)
    etree.SubElement(gtotsub, "dBaseGrav10").text = _fmt_tax(d_base_grav10, moneda, config)

    d_t_bas_gra_iva = 0.0
    if d_base_grav5 > 0 or d_base_grav10 > 0:
        d_t_bas_gra_iva = d_base_grav5 + d_base_grav10
    etree.SubElement(gtotsub, "dTBasGraIVA").text = _fmt_tax(d_t_bas_gra_iva, moneda, config)


def _fmt_money(value: float, moneda: str | None, config: XmlgenConfig) -> str:
    if moneda == "PYG":
        decimals = config.pyg_decimals
    else:
        decimals = config.decimals

    if decimals == 0:
        return str(int(round(value)))
    return f"{float(value):.{decimals}f}"


def _fmt_tax(value: float, moneda: str | None, config: XmlgenConfig) -> str:
    if moneda == "PYG":
        decimals = config.pyg_tax_decimals
    else:
        decimals = config.tax_decimals

    if decimals == 0:
        return str(int(round(value)))
    return f"{float(value):.{decimals}f}"
