<?php

namespace App\Services\Sri;

use App\Models\Comprobante;
use App\Models\Configuracion;
use DOMDocument;
use DOMElement;

/**
 * Arma el XML de una factura electrónica (codDoc 01, versión de esquema 1.1.0)
 * a partir de un Comprobante ya calculado. No firma ni envía nada; solo
 * construye el documento tal como lo exige la ficha técnica del SRI.
 */
class FacturaXmlBuilder
{
    /** Catálogo de tarifas de IVA vigentes -> código de porcentaje del SRI. */
    private const CODIGOS_PORCENTAJE_IVA = [
        0.0 => '0',
        5.0 => '5',
        8.0 => '6',
        12.0 => '2',
        13.0 => '10',
        14.0 => '3',
        15.0 => '4',
    ];

    private const FORMAS_PAGO = [
        'efectivo' => '01',
        'tarjeta' => '19',
        'transferencia' => '20',
        'yape' => '20',
        'deposito' => '20',
    ];

    public function construir(Comprobante $comprobante, Configuracion $config): string
    {
        $dom = new DOMDocument('1.0', 'UTF-8');
        $dom->formatOutput = false;

        $factura = $dom->createElement('factura');
        $factura->setAttribute('id', 'comprobante');
        $factura->setAttribute('version', '1.1.0');
        $dom->appendChild($factura);

        $factura->appendChild($this->infoTributaria($dom, $comprobante, $config));
        $factura->appendChild($this->infoFactura($dom, $comprobante, $config));
        $factura->appendChild($this->detalles($dom, $comprobante, $config));
        $factura->appendChild($this->infoAdicional($dom, $comprobante, $config));

        return $dom->saveXML();
    }

    private function infoTributaria(DOMDocument $dom, Comprobante $c, Configuracion $config): DOMElement
    {
        $el = $dom->createElement('infoTributaria');
        $el->appendChild($dom->createElement('ambiente', (string) $c->ambiente));
        $el->appendChild($dom->createElement('tipoEmision', (string) $config->sri_tipo_emision));
        $el->appendChild($this->cdataOTexto($dom, 'razonSocial', $config->razon_social ?: $config->empresa));

        if ($config->nombre_comercial) {
            $el->appendChild($this->cdataOTexto($dom, 'nombreComercial', $config->nombre_comercial));
        }

        $el->appendChild($dom->createElement('ruc', $config->ruc_limpio));
        $el->appendChild($dom->createElement('claveAcceso', $c->clave_acceso));
        $el->appendChild($dom->createElement('codDoc', $c->cod_doc ?: ClaveAccesoBuilder::COD_DOC_FACTURA));
        $el->appendChild($dom->createElement('estab', $config->sri_estab));
        $el->appendChild($dom->createElement('ptoEmi', $config->sri_pto_emi));
        $el->appendChild($dom->createElement('secuencial', $c->secuencial));
        $el->appendChild($this->cdataOTexto($dom, 'dirMatriz', $config->direccion ?: 'S/N'));

        if ($config->sri_contribuyente_especial) {
            $el->appendChild($dom->createElement('contribuyenteEspecial', $config->sri_contribuyente_especial));
        }

        return $el;
    }

    private function infoFactura(DOMDocument $dom, Comprobante $c, Configuracion $config): DOMElement
    {
        $el = $dom->createElement('infoFactura');
        $el->appendChild($dom->createElement('fechaEmision', $c->fecha->format('d/m/Y')));
        $el->appendChild($this->cdataOTexto($dom, 'dirEstablecimiento', $config->dir_establecimiento ?: ($config->direccion ?: 'S/N')));

        if ($config->sri_contribuyente_especial) {
            $el->appendChild($dom->createElement('contribuyenteEspecial', $config->sri_contribuyente_especial));
        }

        $el->appendChild($dom->createElement('obligadoContabilidad', $config->sri_obligado_contabilidad ?: 'NO'));

        $el->appendChild($dom->createElement('tipoIdentificacionComprador', $c->tipo_identificacion_comprador ?: '07'));
        $el->appendChild($this->cdataOTexto($dom, 'razonSocialComprador', $c->razon_social_comprador ?: 'CONSUMIDOR FINAL'));
        $el->appendChild($dom->createElement('identificacionComprador', $c->identificacion_comprador ?: '9999999999999'));

        if ($c->direccion_comprador) {
            $el->appendChild($this->cdataOTexto($dom, 'direccionComprador', $c->direccion_comprador));
        }

        $grupos = $this->gruposImpuestoPorTarifa($c, $config);
        $totalSinImpuestos = array_sum(array_column($grupos, 'base'));

        $el->appendChild($dom->createElement('totalSinImpuestos', number_format($totalSinImpuestos, 2, '.', '')));
        // El descuento ya viene neteado en subtotal/base imponible (así lo calcula
        // FacturacionController y PosController), por eso aquí siempre reportamos 0.
        $el->appendChild($dom->createElement('totalDescuento', '0.00'));

        $totalConImpuestos = $dom->createElement('totalConImpuestos');
        foreach ($grupos as $g) {
            $ti = $dom->createElement('totalImpuesto');
            $ti->appendChild($dom->createElement('codigo', '2')); // 2 = IVA (único código de impuesto vigente)
            $ti->appendChild($dom->createElement('codigoPorcentaje', $g['codigoPorcentaje']));
            $ti->appendChild($dom->createElement('baseImponible', number_format($g['base'], 2, '.', '')));
            $ti->appendChild($dom->createElement('valor', number_format($g['valor'], 2, '.', '')));
            $totalConImpuestos->appendChild($ti);
        }
        $el->appendChild($totalConImpuestos);

        $el->appendChild($dom->createElement('propina', '0.00'));
        $el->appendChild($dom->createElement('importeTotal', number_format((float) $c->total, 2, '.', '')));
        $el->appendChild($dom->createElement('moneda', 'DOLAR'));

        $pagos = $dom->createElement('pagos');
        $pago = $dom->createElement('pago');
        $pago->appendChild($dom->createElement('formaPago', self::FORMAS_PAGO[$c->metodo_pago] ?? '20'));
        $pago->appendChild($dom->createElement('total', number_format((float) $c->total, 2, '.', '')));
        $pagos->appendChild($pago);
        $el->appendChild($pagos);

        return $el;
    }

    private function detalles(DOMDocument $dom, Comprobante $c, Configuracion $config): DOMElement
    {
        $el = $dom->createElement('detalles');

        foreach ($this->items($c, $config) as $item) {
            $detalle = $dom->createElement('detalle');
            $detalle->appendChild($dom->createElement('codigoPrincipal', $item['codigo'] ?: 'SRV'));
            $detalle->appendChild($this->cdataOTexto($dom, 'descripcion', $item['descripcion']));
            $detalle->appendChild($dom->createElement('cantidad', number_format($item['cantidad'], 6, '.', '')));
            $detalle->appendChild($dom->createElement('precioUnitario', number_format($item['precioUnitario'], 6, '.', '')));
            $detalle->appendChild($dom->createElement('descuento', number_format($item['descuento'], 2, '.', '')));
            $detalle->appendChild($dom->createElement('precioTotalSinImpuesto', number_format($item['base'], 2, '.', '')));

            $impuestos = $dom->createElement('impuestos');
            $impuesto = $dom->createElement('impuesto');
            $impuesto->appendChild($dom->createElement('codigo', '2'));
            $impuesto->appendChild($dom->createElement('codigoPorcentaje', $this->codigoPorcentaje($item['tarifa'])));
            $impuesto->appendChild($dom->createElement('tarifa', number_format($item['tarifa'], 2, '.', '')));
            $impuesto->appendChild($dom->createElement('baseImponible', number_format($item['base'], 2, '.', '')));
            $impuesto->appendChild($dom->createElement('valor', number_format($item['base'] * $item['tarifa'] / 100, 2, '.', '')));
            $impuestos->appendChild($impuesto);
            $detalle->appendChild($impuestos);

            $el->appendChild($detalle);
        }

        return $el;
    }

    private function infoAdicional(DOMDocument $dom, Comprobante $c, Configuracion $config): DOMElement
    {
        $el = $dom->createElement('infoAdicional');

        if ($c->cliente && $c->cliente->email) {
            $campo = $dom->createElement('campoAdicional');
            $campo->setAttribute('nombre', 'Email');
            $campo->appendChild($dom->createTextNode($c->cliente->email));
            $el->appendChild($campo);
        }
        if ($c->cliente && $c->cliente->telefono) {
            $campo = $dom->createElement('campoAdicional');
            $campo->setAttribute('nombre', 'Telefono');
            $campo->appendChild($dom->createTextNode($c->cliente->telefono));
            $el->appendChild($campo);
        }

        return $el;
    }

    /**
     * Normaliza los ítems de la Orden o la Venta asociada al comprobante a un
     * arreglo común: codigo, descripcion, cantidad, precioUnitario, descuento,
     * base (precioTotalSinImpuesto) y tarifa (%).
     */
    private function items(Comprobante $c, Configuracion $config): array
    {
        $items = [];

        if ($c->venta_id && $c->venta) {
            foreach ($c->venta->detalles as $d) {
                $base = round(((float) $d->cantidad * (float) $d->precio_unitario) - (float) $d->descuento, 2);
                $items[] = [
                    'codigo' => $d->codigo,
                    'descripcion' => $d->descripcion,
                    'cantidad' => (float) $d->cantidad,
                    'precioUnitario' => (float) $d->precio_unitario,
                    'descuento' => (float) $d->descuento,
                    'base' => $base,
                    'tarifa' => (float) $d->porcentaje_iva,
                ];
            }

            return $items;
        }

        if ($c->orden_id && $c->orden) {
            $tarifa = (float) ($config->igv ?: 15);

            foreach ($c->orden->servicios as $s) {
                $items[] = $this->itemDeOrden($s->descripcion, $s->cantidad, $s->precio, $tarifa);
            }
            foreach ($c->orden->repuestos as $r) {
                $items[] = $this->itemDeOrden($r->descripcion, $r->cantidad, $r->precio, $tarifa);
            }

            return $items;
        }

        // Comprobante suelto sin orden ni venta asociada (caso de respaldo).
        $items[] = [
            'codigo' => null,
            'descripcion' => 'Venta según comprobante '.$c->serie_numero,
            'cantidad' => 1,
            'precioUnitario' => (float) $c->subtotal,
            'descuento' => 0,
            'base' => (float) $c->subtotal,
            'tarifa' => 15,
        ];

        return $items;
    }

    private function itemDeOrden(string $descripcion, $cantidad, $precio, float $tarifa): array
    {
        $cantidad = (float) $cantidad;
        $precio = (float) $precio;

        return [
            'codigo' => null,
            'descripcion' => $descripcion,
            'cantidad' => $cantidad,
            'precioUnitario' => $precio,
            'descuento' => 0,
            'base' => round($cantidad * $precio, 2),
            'tarifa' => $tarifa,
        ];
    }

    /** Agrupa los ítems por tarifa de IVA para armar <totalConImpuestos>. */
    private function gruposImpuestoPorTarifa(Comprobante $c, Configuracion $config): array
    {
        $grupos = [];

        foreach ($this->items($c, $config) as $item) {
            $key = number_format($item['tarifa'], 2, '.', '');
            $grupos[$key]['base'] = ($grupos[$key]['base'] ?? 0) + $item['base'];
            $grupos[$key]['codigoPorcentaje'] = $this->codigoPorcentaje($item['tarifa']);
        }

        foreach ($grupos as $key => $g) {
            $grupos[$key]['valor'] = round($g['base'] * (float) $key / 100, 2);
        }

        return array_values($grupos);
    }

    private function codigoPorcentaje(float $tarifa): string
    {
        return self::CODIGOS_PORCENTAJE_IVA[$tarifa] ?? (
            $tarifa <= 0 ? '0' : '4' // por defecto, la tarifa general vigente
        );
    }

    /** Usa CDATA cuando el texto trae caracteres que puedan romper el XML "a mano". */
    private function cdataOTexto(DOMDocument $dom, string $tag, string $valor): DOMElement
    {
        $el = $dom->createElement($tag);
        $el->appendChild($dom->createTextNode($valor));

        return $el;
    }
}
