# /home/Autopartes/pos/services/cfdi_builder.py
"""CFDI 4.0 XML builder using lxml.

Builds unsigned CFDI XML documents for:
- Ingreso (sale invoice)
- Egreso (credit note / refund)
- Pago (payment complement for credit sales)

The unsigned XML is sent to Horux360 for CSD signing and PAC timbrado.
All XML follows the SAT CFDI 4.0 schema:
    http://www.sat.gob.mx/cfd/4

CFDI 4.0 mandatory fields handled:
- Version="4.0"
- Exportacion="01" (no aplica)
- ObjetoImp="02" (si, objeto de impuesto) on each Concepto
- InformacionGlobal for publico general (RFC: XAXX010101000)
- Emisor: Rfc, Nombre, RegimenFiscal
- Receptor: Rfc, Nombre, RegimenFiscalReceptor, UsoCFDI, DomicilioFiscalReceptor
"""

from datetime import datetime
from decimal import Decimal, ROUND_HALF_UP
from lxml import etree

# SAT XML namespaces
CFDI_NS = 'http://www.sat.gob.mx/cfd/4'
PAGO_NS = 'http://www.sat.gob.mx/Pagos20'
XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance'

CFDI_SCHEMA_LOCATION = (
    'http://www.sat.gob.mx/cfd/4 '
    'http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd'
)
PAGO_SCHEMA_LOCATION = (
    'http://www.sat.gob.mx/Pagos20 '
    'http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd'
)

# RFC for publico en general
RFC_PUBLICO_GENERAL = 'XAXX010101000'
# RFC for foreign customers
RFC_EXTRANJERO = 'XEXX010101000'


def _to_dec(val):
    if val is None:
        return Decimal('0')
    return Decimal(str(val))


TWO = Decimal('0.01')
SIX = Decimal('0.000001')


def _format_amount(val):
    """Format a Decimal to 2 decimal places as string."""
    return str(_to_dec(val).quantize(TWO, ROUND_HALF_UP))


def _format_rate(val):
    """Format a tax rate to 6 decimal places as string."""
    return str(_to_dec(val).quantize(SIX, ROUND_HALF_UP))


def _make_element(parent, tag, attribs=None, ns=CFDI_NS):
    """Create a subelement with the given namespace."""
    elem = etree.SubElement(parent, f'{{{ns}}}{tag}')
    if attribs:
        for k, v in attribs.items():
            if v is not None:
                elem.set(k, str(v))
    return elem


def build_ingreso_xml(sale, tenant_config, customer=None):
    """Build CFDI 4.0 XML for a sale (Comprobante tipo Ingreso).

    Args:
        sale: dict with keys:
            id, subtotal, discount_total, tax_total, total, created_at,
            metodo_pago_sat ('PUE'|'PPD'), forma_pago_sat ('01'|'03'|'04'|'99'),
            items: [{part_number, name, quantity, unit_price, discount_amount,
                     tax_rate, tax_amount, subtotal,
                     clave_prod_serv, clave_unidad}]
        tenant_config: dict with keys:
            rfc, razon_social, regimen_fiscal, cp (codigo postal),
            serie (optional), nombre_comercial (optional)
        customer: dict or None with keys:
            rfc, razon_social, regimen_fiscal, uso_cfdi, cp
            If None, generates factura a publico general.

    Returns:
        str: XML string (unsigned, ready for Horux)
    """
    nsmap = {
        'cfdi': CFDI_NS,
        'xsi': XSI_NS,
    }

    # Root: Comprobante
    root = etree.Element(f'{{{CFDI_NS}}}Comprobante', nsmap=nsmap)
    root.set(f'{{{XSI_NS}}}schemaLocation', CFDI_SCHEMA_LOCATION)
    root.set('Version', '4.0')
    root.set('Serie', tenant_config.get('serie', 'A'))
    root.set('Folio', str(sale['id']))
    root.set('Fecha', datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))
    root.set('FormaPago', sale.get('forma_pago_sat', '01'))
    root.set('SubTotal', _format_amount(sale['subtotal']))

    discount_total = _to_dec(sale.get('discount_total', 0))
    if discount_total > 0:
        root.set('Descuento', _format_amount(discount_total))

    sale_currency = sale.get('currency', 'MXN')
    sale_rate = sale.get('exchange_rate', 1.0)
    if sale_currency != 'MXN':
        # SAT requires MXN; convert and show exchange rate
        root.set('Moneda', 'MXN')
        root.set('TipoCambio', str(_to_dec(sale_rate).quantize(SIX, ROUND_HALF_UP)))
        root.set('Total', _format_amount(_to_dec(sale['total']) * _to_dec(sale_rate)))
    else:
        root.set('Moneda', 'MXN')
        root.set('Total', _format_amount(sale['total']))
    root.set('TipoDeComprobante', 'I')  # Ingreso
    root.set('Exportacion', '01')  # No aplica
    root.set('MetodoPago', sale.get('metodo_pago_sat', 'PUE'))
    root.set('LugarExpedicion', tenant_config.get('cp', '00000'))

    # InformacionGlobal (required for publico general)
    is_publico_general = (customer is None or
                          customer.get('rfc') in (None, '', RFC_PUBLICO_GENERAL))

    if is_publico_general:
        info_global = _make_element(root, 'InformacionGlobal')
        info_global.set('Periodicidad', '01')  # Diario
        now = datetime.now()
        info_global.set('Meses', f'{now.month:02d}')
        info_global.set('Anio', str(now.year))

    # Emisor
    emisor = _make_element(root, 'Emisor')
    emisor.set('Rfc', tenant_config['rfc'])
    emisor.set('Nombre', tenant_config['razon_social'])
    emisor.set('RegimenFiscal', tenant_config.get('regimen_fiscal', '601'))

    # Receptor
    receptor = _make_element(root, 'Receptor')
    if is_publico_general:
        receptor.set('Rfc', RFC_PUBLICO_GENERAL)
        receptor.set('Nombre', 'PUBLICO EN GENERAL')
        receptor.set('DomicilioFiscalReceptor', tenant_config.get('cp', '00000'))
        receptor.set('RegimenFiscalReceptor', '616')  # Sin obligaciones fiscales
        receptor.set('UsoCFDI', 'S01')  # Sin efectos fiscales
    else:
        receptor.set('Rfc', customer['rfc'])
        receptor.set('Nombre', customer.get('razon_social', customer.get('name', '')))
        receptor.set('DomicilioFiscalReceptor', customer.get('cp', '00000'))
        receptor.set('RegimenFiscalReceptor', customer.get('regimen_fiscal', '616'))
        receptor.set('UsoCFDI', customer.get('uso_cfdi', 'G03'))

    # Conceptos
    conceptos = _make_element(root, 'Conceptos')

    for item in sale.get('items', []):
        qty = int(item.get('quantity', 1))
        unit_price = _to_dec(item.get('unit_price', 0))
        discount_amount = _to_dec(item.get('discount_amount', 0))
        tax_rate = _to_dec(item.get('tax_rate', '0.16'))
        tax_amount = _to_dec(item.get('tax_amount', 0))

        # Importe = qty * unit_price (before discount)
        importe = (unit_price * qty).quantize(TWO, ROUND_HALF_UP)
        # Base for tax = importe - discount
        base = (importe - discount_amount).quantize(TWO, ROUND_HALF_UP)

        concepto = _make_element(conceptos, 'Concepto')
        concepto.set('ClaveProdServ', item.get('clave_prod_serv') or '25174800')
        concepto.set('NoIdentificacion', item.get('part_number') or '')
        concepto.set('Cantidad', str(qty))
        concepto.set('ClaveUnidad', item.get('clave_unidad') or 'H87')
        concepto.set('Unidad', 'PZA')
        concepto.set('Descripcion', item.get('name') or 'Autoparte')
        concepto.set('ValorUnitario', _format_amount(unit_price))
        concepto.set('Importe', _format_amount(importe))
        concepto.set('ObjetoImp', '02')  # Si objeto de impuesto

        if discount_amount > 0:
            concepto.set('Descuento', _format_amount(discount_amount))

        # Impuestos del concepto
        impuestos_concepto = _make_element(concepto, 'Impuestos')
        traslados_concepto = _make_element(impuestos_concepto, 'Traslados')
        traslado = _make_element(traslados_concepto, 'Traslado')
        traslado.set('Base', _format_amount(base))
        traslado.set('Impuesto', '002')  # IVA
        traslado.set('TipoFactor', 'Tasa')
        traslado.set('TasaOCuota', _format_rate(tax_rate))
        traslado.set('Importe', _format_amount(tax_amount))

    # Impuestos totales
    impuestos = _make_element(root, 'Impuestos')
    impuestos.set('TotalImpuestosTrasladados', _format_amount(sale['tax_total']))
    traslados = _make_element(impuestos, 'Traslados')
    traslado_total = _make_element(traslados, 'Traslado')
    traslado_total.set('Base', _format_amount(sale['subtotal']))
    traslado_total.set('Impuesto', '002')
    traslado_total.set('TipoFactor', 'Tasa')
    traslado_total.set('TasaOCuota', '0.160000')
    traslado_total.set('Importe', _format_amount(sale['tax_total']))

    return etree.tostring(root, xml_declaration=True, encoding='UTF-8',
                          pretty_print=True).decode('utf-8')


def build_egreso_xml(sale, tenant_config, customer, original_uuid):
    """Build CFDI 4.0 XML for a credit note (Comprobante tipo Egreso).

    Used for cancellations or returns that require a nota de credito.

    Args:
        sale: same as build_ingreso_xml
        tenant_config: same as build_ingreso_xml
        customer: same as build_ingreso_xml
        original_uuid: str UUID of the original CFDI being credited

    Returns:
        str: XML string
    """
    nsmap = {
        'cfdi': CFDI_NS,
        'xsi': XSI_NS,
    }

    root = etree.Element(f'{{{CFDI_NS}}}Comprobante', nsmap=nsmap)
    root.set(f'{{{XSI_NS}}}schemaLocation', CFDI_SCHEMA_LOCATION)
    root.set('Version', '4.0')
    root.set('Serie', 'NC')
    root.set('Folio', str(sale['id']))
    root.set('Fecha', datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))
    root.set('FormaPago', sale.get('forma_pago_sat', '01'))
    root.set('SubTotal', _format_amount(sale['subtotal']))

    discount_total = _to_dec(sale.get('discount_total', 0))
    if discount_total > 0:
        root.set('Descuento', _format_amount(discount_total))

    sale_currency = sale.get('currency', 'MXN')
    sale_rate = sale.get('exchange_rate', 1.0)
    if sale_currency != 'MXN':
        root.set('Moneda', 'MXN')
        root.set('TipoCambio', str(_to_dec(sale_rate).quantize(SIX, ROUND_HALF_UP)))
        root.set('Total', _format_amount(_to_dec(sale['total']) * _to_dec(sale_rate)))
    else:
        root.set('Moneda', 'MXN')
        root.set('Total', _format_amount(sale['total']))
    root.set('TipoDeComprobante', 'E')  # Egreso
    root.set('Exportacion', '01')
    root.set('MetodoPago', 'PUE')
    root.set('LugarExpedicion', tenant_config.get('cp', '00000'))

    # CfdiRelacionados - references the original CFDI
    cfdi_relacionados = _make_element(root, 'CfdiRelacionados')
    cfdi_relacionados.set('TipoRelacion', '01')  # Nota de credito
    cfdi_relacionado = _make_element(cfdi_relacionados, 'CfdiRelacionado')
    cfdi_relacionado.set('UUID', original_uuid)

    # Emisor
    emisor = _make_element(root, 'Emisor')
    emisor.set('Rfc', tenant_config['rfc'])
    emisor.set('Nombre', tenant_config['razon_social'])
    emisor.set('RegimenFiscal', tenant_config.get('regimen_fiscal', '601'))

    # Receptor
    is_publico_general = (customer is None or
                          customer.get('rfc') in (None, '', RFC_PUBLICO_GENERAL))
    receptor = _make_element(root, 'Receptor')
    if is_publico_general:
        receptor.set('Rfc', RFC_PUBLICO_GENERAL)
        receptor.set('Nombre', 'PUBLICO EN GENERAL')
        receptor.set('DomicilioFiscalReceptor', tenant_config.get('cp', '00000'))
        receptor.set('RegimenFiscalReceptor', '616')
        receptor.set('UsoCFDI', 'S01')
    else:
        receptor.set('Rfc', customer['rfc'])
        receptor.set('Nombre', customer.get('razon_social', customer.get('name', '')))
        receptor.set('DomicilioFiscalReceptor', customer.get('cp', '00000'))
        receptor.set('RegimenFiscalReceptor', customer.get('regimen_fiscal', '616'))
        receptor.set('UsoCFDI', customer.get('uso_cfdi', 'G03'))

    # Conceptos (same as ingreso but for the credited amounts)
    conceptos = _make_element(root, 'Conceptos')

    for item in sale.get('items', []):
        qty = int(item.get('quantity', 1))
        unit_price = _to_dec(item.get('unit_price', 0))
        discount_amount = _to_dec(item.get('discount_amount', 0))
        tax_rate = _to_dec(item.get('tax_rate', '0.16'))
        tax_amount = _to_dec(item.get('tax_amount', 0))
        importe = (unit_price * qty).quantize(TWO, ROUND_HALF_UP)
        base = (importe - discount_amount).quantize(TWO, ROUND_HALF_UP)

        concepto = _make_element(conceptos, 'Concepto')
        concepto.set('ClaveProdServ', item.get('clave_prod_serv') or '25174800')
        concepto.set('NoIdentificacion', item.get('part_number') or '')
        concepto.set('Cantidad', str(qty))
        concepto.set('ClaveUnidad', item.get('clave_unidad') or 'H87')
        concepto.set('Unidad', 'PZA')
        concepto.set('Descripcion', item.get('name') or 'Autoparte')
        concepto.set('ValorUnitario', _format_amount(unit_price))
        concepto.set('Importe', _format_amount(importe))
        concepto.set('ObjetoImp', '02')

        if discount_amount > 0:
            concepto.set('Descuento', _format_amount(discount_amount))

        impuestos_concepto = _make_element(concepto, 'Impuestos')
        traslados_concepto = _make_element(impuestos_concepto, 'Traslados')
        traslado = _make_element(traslados_concepto, 'Traslado')
        traslado.set('Base', _format_amount(base))
        traslado.set('Impuesto', '002')
        traslado.set('TipoFactor', 'Tasa')
        traslado.set('TasaOCuota', _format_rate(tax_rate))
        traslado.set('Importe', _format_amount(tax_amount))

    # Impuestos totales
    impuestos = _make_element(root, 'Impuestos')
    impuestos.set('TotalImpuestosTrasladados', _format_amount(sale['tax_total']))
    traslados = _make_element(impuestos, 'Traslados')
    traslado_total = _make_element(traslados, 'Traslado')
    traslado_total.set('Base', _format_amount(sale['subtotal']))
    traslado_total.set('Impuesto', '002')
    traslado_total.set('TipoFactor', 'Tasa')
    traslado_total.set('TasaOCuota', '0.160000')
    traslado_total.set('Importe', _format_amount(sale['tax_total']))

    return etree.tostring(root, xml_declaration=True, encoding='UTF-8',
                          pretty_print=True).decode('utf-8')


def build_pago_xml(payment, tenant_config, customer, original_uuid):
    """Build CFDI 4.0 XML with Complemento de Pago 2.0.

    Used for credit sale payments (MetodoPago PPD). When a customer
    pays an outstanding credit sale, this generates the payment complement.

    Args:
        payment: dict with keys:
            id, amount, payment_method ('efectivo'|'transferencia'|'tarjeta'),
            date (ISO string), reference (optional)
        tenant_config: same as build_ingreso_xml
        customer: dict with RFC data
        original_uuid: str UUID of the original CFDI (Ingreso with PPD)

    Returns:
        str: XML string
    """
    nsmap = {
        'cfdi': CFDI_NS,
        'pago20': PAGO_NS,
        'xsi': XSI_NS,
    }

    forma_pago_map = {
        'efectivo': '01', 'transferencia': '03', 'tarjeta': '04', 'mixto': '99'
    }

    root = etree.Element(f'{{{CFDI_NS}}}Comprobante', nsmap=nsmap)
    root.set(f'{{{XSI_NS}}}schemaLocation',
             f'{CFDI_SCHEMA_LOCATION} {PAGO_SCHEMA_LOCATION}')
    root.set('Version', '4.0')
    root.set('Serie', 'P')
    root.set('Folio', str(payment.get('id', '')))
    root.set('Fecha', datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))
    root.set('SubTotal', '0')
    root.set('Moneda', 'XXX')  # Required for Pago type
    root.set('Total', '0')
    root.set('TipoDeComprobante', 'P')  # Pago
    root.set('Exportacion', '01')
    root.set('LugarExpedicion', tenant_config.get('cp', '00000'))

    # Emisor
    emisor = _make_element(root, 'Emisor')
    emisor.set('Rfc', tenant_config['rfc'])
    emisor.set('Nombre', tenant_config['razon_social'])
    emisor.set('RegimenFiscal', tenant_config.get('regimen_fiscal', '601'))

    # Receptor
    receptor = _make_element(root, 'Receptor')
    receptor.set('Rfc', customer['rfc'])
    receptor.set('Nombre', customer.get('razon_social', customer.get('name', '')))
    receptor.set('DomicilioFiscalReceptor', customer.get('cp', '00000'))
    receptor.set('RegimenFiscalReceptor', customer.get('regimen_fiscal', '616'))
    receptor.set('UsoCFDI', 'CP01')  # Pagos

    # Conceptos (mandatory placeholder for Pago type)
    conceptos = _make_element(root, 'Conceptos')
    concepto = _make_element(conceptos, 'Concepto')
    concepto.set('ClaveProdServ', '84111506')  # Servicios de facturacion
    concepto.set('Cantidad', '1')
    concepto.set('ClaveUnidad', 'ACT')  # Actividad
    concepto.set('Descripcion', 'Pago')
    concepto.set('ValorUnitario', '0')
    concepto.set('Importe', '0')
    concepto.set('ObjetoImp', '01')  # No objeto de impuesto

    # Complemento de Pago 2.0
    complemento = _make_element(root, 'Complemento')
    pagos_elem = etree.SubElement(complemento, f'{{{PAGO_NS}}}Pagos')
    pagos_elem.set('Version', '2.0')

    # Totales
    totales = etree.SubElement(pagos_elem, f'{{{PAGO_NS}}}Totales')
    amount = _to_dec(payment['amount'])
    # Calculate base and IVA from total (total includes 16% IVA)
    base_pago = (amount / Decimal('1.16')).quantize(TWO, ROUND_HALF_UP)
    iva_pago = (amount - base_pago).quantize(TWO, ROUND_HALF_UP)
    totales.set('TotalTrasladosBaseIVA16', _format_amount(base_pago))
    totales.set('TotalTrasladosImpuestoIVA16', _format_amount(iva_pago))
    totales.set('MontoTotalPagos', _format_amount(amount))

    # Pago
    pago = etree.SubElement(pagos_elem, f'{{{PAGO_NS}}}Pago')
    payment_date = payment.get('date', datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))
    pago.set('FechaPago', payment_date if 'T' in str(payment_date)
             else f'{payment_date}T12:00:00')
    pago.set('FormaDePagoP', forma_pago_map.get(payment.get('payment_method', 'efectivo'), '01'))
    pago.set('MonedaP', 'MXN')
    pago.set('Monto', _format_amount(amount))

    # DoctoRelacionado (the original invoice being paid)
    docto = etree.SubElement(pago, f'{{{PAGO_NS}}}DoctoRelacionado')
    docto.set('IdDocumento', original_uuid)
    docto.set('Serie', 'A')
    docto.set('Folio', str(payment.get('sale_id', '')))
    docto.set('MonedaDR', 'MXN')
    docto.set('NumParcialidad', str(payment.get('num_parcialidad', 1)))
    docto.set('ImpSaldoAnt', _format_amount(payment.get('saldo_anterior', amount)))
    docto.set('ImpPagado', _format_amount(amount))
    saldo_insoluto = _to_dec(payment.get('saldo_anterior', amount)) - amount
    docto.set('ImpSaldoInsoluto', _format_amount(max(saldo_insoluto, Decimal('0'))))
    docto.set('ObjetoImpDR', '02')
    docto.set('EquivalenciaDR', '1')

    # ImpuestosDR
    impuestos_dr = etree.SubElement(docto, f'{{{PAGO_NS}}}ImpuestosDR')
    traslados_dr = etree.SubElement(impuestos_dr, f'{{{PAGO_NS}}}TrasladosDR')
    traslado_dr = etree.SubElement(traslados_dr, f'{{{PAGO_NS}}}TrasladoDR')
    traslado_dr.set('BaseDR', _format_amount(base_pago))
    traslado_dr.set('ImpuestoDR', '002')
    traslado_dr.set('TipoFactorDR', 'Tasa')
    traslado_dr.set('TasaOCuotaDR', '0.160000')
    traslado_dr.set('ImporteDR', _format_amount(iva_pago))

    # ImpuestosP (pago-level taxes)
    impuestos_p = etree.SubElement(pago, f'{{{PAGO_NS}}}ImpuestosP')
    traslados_p = etree.SubElement(impuestos_p, f'{{{PAGO_NS}}}TrasladosP')
    traslado_p = etree.SubElement(traslados_p, f'{{{PAGO_NS}}}TrasladoP')
    traslado_p.set('BaseP', _format_amount(base_pago))
    traslado_p.set('ImpuestoP', '002')
    traslado_p.set('TipoFactorP', 'Tasa')
    traslado_p.set('TasaOCuotaP', '0.160000')
    traslado_p.set('ImporteP', _format_amount(iva_pago))

    return etree.tostring(root, xml_declaration=True, encoding='UTF-8',
                          pretty_print=True).decode('utf-8')


def build_global_invoice_xml(sales, tenant_config, year, month):
    """Build CFDI 4.0 XML for a monthly global invoice (Factura Global).

    Groups multiple cash sales (PUE, <= $2,000 each, no individual CFDI)
    into a single CFDI tipo Ingreso with InformacionGlobal.

    Args:
        sales: list of dicts with keys:
            id, subtotal, discount_total, tax_total, total,
            items: [{name, quantity, unit_price, discount_amount,
                     tax_rate, tax_amount, subtotal,
                     clave_prod_serv, clave_unidad}]
        tenant_config: dict with keys:
            rfc, razon_social, regimen_fiscal, cp, serie (optional)
        year: int, e.g. 2026
        month: int, e.g. 6

    Returns:
        str: XML string (unsigned, ready for Horux)
    """
    nsmap = {
        'cfdi': CFDI_NS,
        'xsi': XSI_NS,
    }

    # Aggregate totals
    total_subtotal = Decimal('0')
    total_discount = Decimal('0')
    total_tax = Decimal('0')
    total_total = Decimal('0')
    for sale in sales:
        total_subtotal += _to_dec(sale.get('subtotal', 0))
        total_discount += _to_dec(sale.get('discount_total', 0))
        total_tax += _to_dec(sale.get('tax_total', 0))
        total_total += _to_dec(sale.get('total', 0))

    root = etree.Element(f'{{{CFDI_NS}}}Comprobante', nsmap=nsmap)
    root.set(f'{{{XSI_NS}}}schemaLocation', CFDI_SCHEMA_LOCATION)
    root.set('Version', '4.0')
    root.set('Serie', tenant_config.get('serie', 'FG'))
    root.set('Folio', f'{year}{month:02d}')
    root.set('Fecha', datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))
    root.set('FormaPago', '01')  # Efectivo (most common for global)
    root.set('SubTotal', _format_amount(total_subtotal))

    if total_discount > 0:
        root.set('Descuento', _format_amount(total_discount))

    root.set('Moneda', 'MXN')
    root.set('Total', _format_amount(total_total))
    root.set('TipoDeComprobante', 'I')  # Ingreso
    root.set('Exportacion', '01')
    root.set('MetodoPago', 'PUE')
    root.set('LugarExpedicion', tenant_config.get('cp', '00000'))

    # InformacionGlobal (monthly global invoice)
    info_global = _make_element(root, 'InformacionGlobal')
    info_global.set('Periodicidad', '04')  # Mensual
    info_global.set('Meses', f'{month:02d}')
    info_global.set('Anio', str(year))

    # Emisor
    emisor = _make_element(root, 'Emisor')
    emisor.set('Rfc', tenant_config['rfc'])
    emisor.set('Nombre', tenant_config['razon_social'])
    emisor.set('RegimenFiscal', tenant_config.get('regimen_fiscal', '601'))

    # Receptor: Publico en general
    receptor = _make_element(root, 'Receptor')
    receptor.set('Rfc', RFC_PUBLICO_GENERAL)
    receptor.set('Nombre', 'PUBLICO EN GENERAL')
    receptor.set('DomicilioFiscalReceptor', tenant_config.get('cp', '00000'))
    receptor.set('RegimenFiscalReceptor', '616')
    receptor.set('UsoCFDI', 'S01')

    # Conceptos: one per sale item (simplified)
    conceptos = _make_element(root, 'Conceptos')

    for sale in sales:
        for item in sale.get('items', []):
            qty = int(item.get('quantity', 1))
            unit_price = _to_dec(item.get('unit_price', 0))
            discount_amount = _to_dec(item.get('discount_amount', 0))
            tax_rate = _to_dec(item.get('tax_rate', '0.16'))
            tax_amount = _to_dec(item.get('tax_amount', 0))

            importe = (unit_price * qty).quantize(TWO, ROUND_HALF_UP)
            base = (importe - discount_amount).quantize(TWO, ROUND_HALF_UP)

            concepto = _make_element(conceptos, 'Concepto')
            concepto.set('ClaveProdServ', item.get('clave_prod_serv') or '25174800')
            concepto.set('NoIdentificacion', item.get('part_number') or str(sale['id']))
            concepto.set('Cantidad', str(qty))
            concepto.set('ClaveUnidad', item.get('clave_unidad') or 'H87')
            concepto.set('Unidad', 'PZA')
            concepto.set('Descripcion', item.get('name') or 'Autoparte')
            concepto.set('ValorUnitario', _format_amount(unit_price))
            concepto.set('Importe', _format_amount(importe))
            concepto.set('ObjetoImp', '02')

            if discount_amount > 0:
                concepto.set('Descuento', _format_amount(discount_amount))

            impuestos_concepto = _make_element(concepto, 'Impuestos')
            traslados_concepto = _make_element(impuestos_concepto, 'Traslados')
            traslado = _make_element(traslados_concepto, 'Traslado')
            traslado.set('Base', _format_amount(base))
            traslado.set('Impuesto', '002')
            traslado.set('TipoFactor', 'Tasa')
            traslado.set('TasaOCuota', _format_rate(tax_rate))
            traslado.set('Importe', _format_amount(tax_amount))

    # Impuestos totales
    impuestos = _make_element(root, 'Impuestos')
    impuestos.set('TotalImpuestosTrasladados', _format_amount(total_tax))
    traslados = _make_element(impuestos, 'Traslados')
    traslado_total = _make_element(traslados, 'Traslado')
    traslado_total.set('Base', _format_amount(total_subtotal))
    traslado_total.set('Impuesto', '002')
    traslado_total.set('TipoFactor', 'Tasa')
    traslado_total.set('TasaOCuota', '0.160000')
    traslado_total.set('Importe', _format_amount(total_tax))

    return etree.tostring(root, xml_declaration=True, encoding='UTF-8',
                          pretty_print=True).decode('utf-8')
