<?php 

namespace App\Services\ElectronicBilling\Builders;

use App\Data\ElectronicDocument;
use DOMDocument;
use DOMElement;

abstract class AbstractXmlBuilder 
{
    protected ElectronicDocument $data;
    protected DOMDocument $doc;
    protected DOMElement $root;

    abstract protected function getRootNodeName(): string;
    abstract protected function getNamespaceUrl(): string;
    abstract protected function getSchemaUrl(): string;

    public function build(ElectronicDocument $data): string
    {
        $this->data = $data;

        $this->doc = new DOMDocument('1.0', 'utf-8');
        $this->doc->preserveWhiteSpace = false;
        $this->doc->formatOutput = true;

        $this->createRoot();

        foreach ($this->getBuildSteps() as $step) 
        { 
            $this->$step();
        }

        return $this->doc->saveXML();
    }

    protected function getBuildSteps(): array 
    {
        return [];
    }

    protected function shouldBuildReceptor(): bool 
    {
        return true;
    }

    protected function createRoot(): void
    {
        $ns = $this->getNamespaceUrl();
        $this->root = $this->doc->createElementNS($ns, $this->getRootNodeName());
        
        $this->root->setAttribute('xmlns', $ns);
        $this->root->setAttribute('xmlns:xsd', $this->getSchemaUrl());
        $this->root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');

        $this->doc->appendChild($this->root);
    }

    protected function buildHeader(): void
    {
        $this->addText('Clave', $this->data->clave);
        $this->addText('ProveedorSistemas', $this->data->proveedor_sistema);
        $this->addText('CodigoActividadEmisor', $this->data->cod_act_economica_emisor);
        $this->addText('CodigoActividadReceptor', $this->data->cod_act_economica_receptor);
        $this->addText('NumeroConsecutivo', $this->data->numero_consecutivo);
        $this->addText('FechaEmision', $this->data->fecha_emision);
    }

    protected function buildIssuer(): void
    {
        $node = $this->addChild($this->root, 'Emisor');
        $this->addPersonDetails($node, $this->data->emisor);
    }

    protected function buildReceiver(): void
    {
        if ($this->shouldBuildReceptor() && $this->data->receptor !== null) 
        {
            $node = $this->addChild($this->root, 'Receptor');
            $this->addPersonDetails($node, $this->data->receptor);
        }
    }

    protected function addPersonDetails(DOMElement $parent, $personData): void
    {
        $this->addText('Nombre', $personData->nombre, $parent);
        
        if ($personData->tipo_identificacion && $personData->numero_identificacion) {
            $nodeId = $this->addChild($parent, 'Identificacion');
            $this->addText('Tipo', $personData->tipo_identificacion, $nodeId);
            $this->addText('Numero', $personData->numero_identificacion, $nodeId);
        }

        if ($personData->nombre_comercial) {
            $this->addText('NombreComercial', $personData->nombre_comercial, $parent);
        }

        if ($personData->ubicacion) {
            $loc = $this->addChild($parent, 'Ubicacion');
            $this->addText('Provincia', $personData->ubicacion['provincia'] ?? null, $loc);
            $this->addText('Canton', $personData->ubicacion['canton'] ?? null, $loc);
            $this->addText('Distrito', $personData->ubicacion['distrito'] ?? null, $loc);
            $this->addText('Barrio', $personData->ubicacion['barrio'] ?? null, $loc);
            $this->addText('OtrasSenas', $personData->ubicacion['sennas'] ?? null, $loc);
        }

        $this->addText('CorreoElectronico', $personData->correo, $parent);
    }

    protected function buildConditionOfSale(): void
    {
        $this->addText('CondicionVenta', $this->data->condicion_venta);
        $this->addText('CondicionVentaOtros', $this->data->condicion_venta_otros);
        $this->addText('PlazoCredito', $this->data->plazo_credito);
    }

    protected function buildDetail(): void
    {
        $container = $this->addChild($this->root, 'DetalleServicio');

        foreach ($this->data->lineas as $linea) 
        {
            $row = $this->addChild($container, 'LineaDetalle');

            $this->addText('NumeroLinea', (string) $linea->numero_linea, $row);
            $this->addText('CodigoCABYS', $linea->codigo_cabys, $row);
            $this->addDecimal('Cantidad', $linea->cantidad, $row, 3);
            $this->addText('UnidadMedida', $linea->unidad_medida, $row);
            $this->addText('Detalle', $linea->detalle, $row);
            $this->addDecimal('PrecioUnitario', $linea->precio_unitario, $row);
            $this->addDecimal('MontoTotal', $linea->monto_total, $row);

            foreach (($linea->descuentos ?? []) as $desc) {
                $descNode = $this->addChild($row, 'Descuento');
                $this->addDecimal('MontoDescuento', (float) ($desc->monto_descuento ?? 0), $descNode);
                $this->addText('CodigoDescuento', $desc->codigo_descuento ?? null, $descNode);
                $this->addText('CodigoDescuentoOTRO', $desc->codigo_descuento_otros ?? null, $descNode);
                $this->addText('NaturalezaDescuento', $desc->naturaleza_descuento ?? null, $descNode);
            }

            $this->addDecimal('SubTotal', $linea->sub_total, $row);
            $this->addDecimal('BaseImponible', $linea->base_imponible, $row);

            foreach (($linea->impuestos ?? []) as $imp) {
                $impNode = $this->addChild($row, 'Impuesto');
                $this->addText('Codigo', $imp->codigo ?? null, $impNode);
                $this->addText('CodigoTarifaIVA', $imp->codigo_tarifa_iva ?? null, $impNode);
                $this->addDecimal('Tarifa', (float) ($imp->tarifa ?? 0), $impNode, 2);
                $this->addDecimal('Monto', (float) ($imp->monto ?? 0), $impNode);
            }

            $this->addDecimal('ImpuestoAsumidoEmisorFabrica', $linea->imp_asum_emisor, $row);
            $this->addDecimal('ImpuestoNeto', $linea->impuesto_neto, $row);
            $this->addDecimal('MontoTotalLinea', $linea->monto_total_linea, $row);
        }
    }

    protected function buildSummary(): void
    {
        $node = $this->addChild($this->root, 'ResumenFactura');
        $resumen = $this->data->resumen;

        $codTipMonNode = $this->addChild($node, 'CodigoTipoMoneda');
        $this->addText('CodigoMoneda', $this->data->moneda, $codTipMonNode);
        $this->addText('TipoCambio', (string) $this->data->tipo_cambio, $codTipMonNode);
      
        $this->addDecimal('TotalServGravados', $resumen['total_serv_gravados'], $node);
        $this->addDecimal('TotalServExentos', $resumen['total_serv_exentos'], $node);
        $this->addDecimal('TotalServExonerado', $resumen['total_serv_exonerados'], $node);
        $this->addDecimal('TotalServNoSujeto', $resumen['total_serv_no_sujetos'], $node);
        $this->addDecimal('TotalMercanciasGravadas', $resumen['total_merc_gravadas'], $node);
        $this->addDecimal('TotalMercanciasExentas', $resumen['total_merc_exentas'], $node);
        $this->addDecimal('TotalMercExonerada', $resumen['total_merc_exoneradas'], $node);
        $this->addDecimal('TotalMercNoSujeta', $resumen['total_merc_no_sujetas'], $node);
        $this->addDecimal('TotalGravado', $resumen['total_gravado'], $node);
        $this->addDecimal('TotalExento', $resumen['total_exento'], $node);
        $this->addDecimal('TotalExonerado', $resumen['total_exonerado'], $node);
        $this->addDecimal('TotalNoSujeto', $resumen['total_no_sujeto'], $node);
        $this->addDecimal('TotalVenta', $resumen['total_venta'], $node);
        $this->addDecimal('TotalDescuentos', $resumen['total_descuentos'], $node);
        $this->addDecimal('TotalVentaNeta', $resumen['total_venta_neta'], $node);

        foreach (($this->data->desglose_impuestos ?? []) as $impuesto) 
        {
            $desgloseImpuesto = $this->addChild($node, 'TotalDesgloseImpuesto');
            $this->addText('Codigo', $impuesto['codigo'] ?? null, $desgloseImpuesto);
            $this->addText('CodigoTarifaIVA', $impuesto['codigo_tarifa_iva'] ?? null, $desgloseImpuesto);
            $this->addDecimal('TotalMontoImpuesto', (float) ($impuesto['total_monto_impuesto'] ?? 0), $desgloseImpuesto);
        }

        $this->addDecimal('TotalImpuesto', $resumen['total_impuesto'], $node);
        $this->addDecimal('TotalImpAsumEmisorFabrica', $resumen['total_imp_asum_emi'], $node);
        $this->addDecimal('TotalIVADevuelto', $resumen['total_iva_devuelto'], $node);

        foreach (($this->data->medio_pago ?? []) as $medioPago) {
            $medPagNode = $this->addChild($node, 'MedioPago');
            $this->addText('TipoMedioPago', $medioPago['medio_pago_tipo'] ?? null, $medPagNode);
            if (($medioPago['medio_pago_tipo'] ?? null) === '99') {
                $this->addText('MedioPagoOtros', $medioPago['medio_pago_otro'] ?? null, $medPagNode);
            }
            if (array_key_exists('medio_pago_total', $medioPago) && $medioPago['medio_pago_total'] !== null && $medioPago['medio_pago_total'] !== '') {
                $this->addDecimal('TotalMedioPago', (float) $medioPago['medio_pago_total'], $medPagNode);
            }
        }

        $this->addDecimal('TotalComprobante', $resumen['total_comprobante'], $node);
    }

    protected function buildOtros(): DOMElement
    {
        $container = $this->addChild($this->root, 'Otros');
        $this->addText('OtroTexto', '--- Soluciones Arce M. arcemiranda@factucr.com ---', $container);
        return $container;
    }

    protected function addChild(DOMElement $parent, string $name): DOMElement
    {
        $el = $this->doc->createElement($name);
        $parent->appendChild($el);
        return $el;
    }

    protected function addText(string $name, ?string $value, ?DOMElement $parent = null): void
    {
        if ($value === null || $value === '') return;
        
        $el = $this->doc->createElement($name);
        $el->appendChild($this->doc->createTextNode($value));
        ($parent ?? $this->root)->appendChild($el);
    }

    protected function addDecimal(string $name, float $value, ?DOMElement $parent = null, int $decimals = 5): void
    {
        $this->addText($name, number_format($value, $decimals, '.', ''), $parent);
    }
}
