<?php

namespace App\Services;

use App\Models\Empresa;
use App\Models\ComprobanteElectronico;
use App\Models\ComprobanteDetalle;
use App\Models\PuntoEmision;
use App\Models\Venta;
use Carbon\Carbon;
use Illuminate\Support\Facades\Storage;
use SoapClient;

/**
 * Servicio de facturación electrónica del SRI (Ecuador) - Facturación directa.
 *
 * Responsabilidades:
 *  - Generar la clave de acceso (49 dígitos)
 *  - Construir el XML de Factura, Nota de Crédito, Nota de Débito y
 *    Comprobante de Retención según los esquemas del SRI
 *  - Firmar el comprobante con XAdES-BES
 *  - Enviarlo a los web services de Recepción y Autorización del SRI
 *  - Registrar el resultado y los archivos generados
 */
class SriService
{
    /** Endpoints de los web services del SRI */
    private const ENDPOINTS = [
        '1' => [ // Pruebas
            'recepcion'    => 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantesOffline?wsdl',
            'autorizacion' => 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantesOffline?wsdl',
        ],
        '2' => [ // Producción
            'recepcion'    => 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantesOffline?wsdl',
            'autorizacion' => 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantesOffline?wsdl',
        ],
    ];

    protected ?Empresa $empresa;
    protected float $tarifaIva;

    public function __construct()
    {
        $this->empresa = Empresa::first();
        $this->tarifaIva = (float) ($this->empresa->impuesto ?? 15);
        if ($this->tarifaIva <= 0) {
            $this->tarifaIva = 15;
        }
    }

    /* =====================================================================
     |  EMISIÓN DE COMPROBANTES
     ===================================================================== */

    /**
     * Crea una Factura electrónica a partir de una venta.
     */
    public function emitirFactura(Venta $venta): ComprobanteElectronico
    {
        $this->validarEmpresa();
        $venta->load(['cliente', 'detalles.producto']);

        $num = PuntoEmision::siguienteSecuencial('01');
        $receptor = $this->datosReceptor($venta->cliente);

        $comprobante = new ComprobanteElectronico([
            'venta_id' => $venta->id,
            'tipo_documento' => '01',
            'ambiente' => $this->empresa->sri_ambiente ?: '1',
            'tipo_emision' => $this->empresa->sri_tipo_emision ?: '1',
            'estab' => $num['estab'],
            'pto_emi' => $num['pto_emi'],
            'secuencial' => $num['secuencial'],
            'numero_completo' => $num['numero_completo'],
            'emisor_ruc' => $this->empresa->ruc_nit,
            'emisor_razon_social' => $this->empresa->razon_social,
            'emisor_nombre_comercial' => $this->empresa->nombre_comercial,
            'emisor_dir_matriz' => $this->empresa->dir_matriz ?: $this->empresa->direccion,
            'emisor_dir_establecimiento' => $this->empresa->dir_establecimiento ?: $this->empresa->direccion,
            'obligado_contabilidad' => $this->empresa->obligado_contabilidad ? 'SI' : 'NO',
            'contribuyente_especial' => $this->empresa->contribuyente_especial,
            'receptor_tipo_identificacion' => $receptor['tipo'],
            'receptor_identificacion' => $receptor['identificacion'],
            'receptor_razon_social' => $receptor['razon_social'],
            'receptor_direccion' => $receptor['direccion'],
            'receptor_email' => $receptor['email'],
            'receptor_telefono' => $receptor['telefono'],
            'fecha_emision' => $venta->fecha_venta->toDateString(),
            'moneda' => 'DOLAR',
            'estado_sri' => 'CREADO',
            'user_id' => auth()->id() ?? $venta->user_id,
        ]);

        // Calcular totales y detalles
        $calculo = $this->calcularDetalles($venta);
        $comprobante->total_sin_impuestos = $calculo['total_sin_impuestos'];
        $comprobante->total_descuento = $calculo['total_descuento'];
        $comprobante->total_iva = $calculo['total_iva'];
        $comprobante->importe_total = $calculo['importe_total'];
        $comprobante->save();

        foreach ($calculo['lineas'] as $linea) {
            $comprobante->detalles()->create($linea);
        }

        $comprobante->clave_acceso = $this->generarClaveAcceso($comprobante);
        $comprobante->save();

        $venta->update(['comprobante_electronico_id' => $comprobante->id]);

        return $comprobante;
    }

    /**
     * Crea una Nota de Crédito que anula/modifica una factura existente.
     */
    public function emitirNotaCredito(ComprobanteElectronico $factura, string $motivo): ComprobanteElectronico
    {
        $this->validarEmpresa();

        if ($factura->tipo_documento !== '01') {
            throw new \RuntimeException('La Nota de Crédito solo puede emitirse sobre una Factura.');
        }

        $num = PuntoEmision::siguienteSecuencial('04');

        $nc = ComprobanteElectronico::create([
            'venta_id' => $factura->venta_id,
            'tipo_documento' => '04',
            'ambiente' => $this->empresa->sri_ambiente ?: '1',
            'tipo_emision' => $this->empresa->sri_tipo_emision ?: '1',
            'estab' => $num['estab'],
            'pto_emi' => $num['pto_emi'],
            'secuencial' => $num['secuencial'],
            'numero_completo' => $num['numero_completo'],
            'emisor_ruc' => $this->empresa->ruc_nit,
            'emisor_razon_social' => $this->empresa->razon_social,
            'emisor_nombre_comercial' => $this->empresa->nombre_comercial,
            'emisor_dir_matriz' => $this->empresa->dir_matriz ?: $this->empresa->direccion,
            'emisor_dir_establecimiento' => $this->empresa->dir_establecimiento ?: $this->empresa->direccion,
            'obligado_contabilidad' => $this->empresa->obligado_contabilidad ? 'SI' : 'NO',
            'contribuyente_especial' => $this->empresa->contribuyente_especial,
            'receptor_tipo_identificacion' => $factura->receptor_tipo_identificacion,
            'receptor_identificacion' => $factura->receptor_identificacion,
            'receptor_razon_social' => $factura->receptor_razon_social,
            'receptor_direccion' => $factura->receptor_direccion,
            'receptor_email' => $factura->receptor_email,
            'fecha_emision' => now()->toDateString(),
            'moneda' => 'DOLAR',
            'cod_doc_modificado' => '01',
            'num_doc_modificado' => $factura->numero_completo,
            'fecha_emision_doc_sustento' => $factura->fecha_emision,
            'motivo' => $motivo,
            'total_sin_impuestos' => $factura->total_sin_impuestos,
            'total_iva' => $factura->total_iva,
            'valor_modificacion' => $factura->importe_total,
            'importe_total' => $factura->importe_total,
            'estado_sri' => 'CREADO',
            'user_id' => auth()->id(),
        ]);

        // Copiar las líneas de la factura de origen
        foreach ($factura->detalles as $d) {
            $nc->detalles()->create([
                'codigo_principal' => $d->codigo_principal,
                'descripcion' => $d->descripcion,
                'cantidad' => $d->cantidad,
                'precio_unitario' => $d->precio_unitario,
                'descuento' => $d->descuento,
                'precio_total_sin_impuesto' => $d->precio_total_sin_impuesto,
                'impuesto_codigo' => $d->impuesto_codigo,
                'impuesto_codigo_porcentaje' => $d->impuesto_codigo_porcentaje,
                'impuesto_tarifa' => $d->impuesto_tarifa,
                'impuesto_base' => $d->impuesto_base,
                'impuesto_valor' => $d->impuesto_valor,
            ]);
        }

        $nc->clave_acceso = $this->generarClaveAcceso($nc);
        $nc->save();

        return $nc;
    }

    /**
     * Crea una Nota de Débito sobre una factura existente.
     */
    public function emitirNotaDebito(ComprobanteElectronico $factura, string $motivo, float $valor): ComprobanteElectronico
    {
        $this->validarEmpresa();

        if ($factura->tipo_documento !== '01') {
            throw new \RuntimeException('La Nota de Débito solo puede emitirse sobre una Factura.');
        }

        $num = PuntoEmision::siguienteSecuencial('05');

        $base = round($valor / (1 + $this->tarifaIva / 100), 2);
        $iva = round($base * $this->tarifaIva / 100, 2);

        $nd = ComprobanteElectronico::create([
            'venta_id' => $factura->venta_id,
            'tipo_documento' => '05',
            'ambiente' => $this->empresa->sri_ambiente ?: '1',
            'tipo_emision' => $this->empresa->sri_tipo_emision ?: '1',
            'estab' => $num['estab'],
            'pto_emi' => $num['pto_emi'],
            'secuencial' => $num['secuencial'],
            'numero_completo' => $num['numero_completo'],
            'emisor_ruc' => $this->empresa->ruc_nit,
            'emisor_razon_social' => $this->empresa->razon_social,
            'emisor_nombre_comercial' => $this->empresa->nombre_comercial,
            'emisor_dir_matriz' => $this->empresa->dir_matriz ?: $this->empresa->direccion,
            'emisor_dir_establecimiento' => $this->empresa->dir_establecimiento ?: $this->empresa->direccion,
            'obligado_contabilidad' => $this->empresa->obligado_contabilidad ? 'SI' : 'NO',
            'contribuyente_especial' => $this->empresa->contribuyente_especial,
            'receptor_tipo_identificacion' => $factura->receptor_tipo_identificacion,
            'receptor_identificacion' => $factura->receptor_identificacion,
            'receptor_razon_social' => $factura->receptor_razon_social,
            'receptor_direccion' => $factura->receptor_direccion,
            'receptor_email' => $factura->receptor_email,
            'fecha_emision' => now()->toDateString(),
            'moneda' => 'DOLAR',
            'cod_doc_modificado' => '01',
            'num_doc_modificado' => $factura->numero_completo,
            'fecha_emision_doc_sustento' => $factura->fecha_emision,
            'motivo' => $motivo,
            'total_sin_impuestos' => $base,
            'total_iva' => $iva,
            'valor_modificacion' => round($base + $iva, 2),
            'importe_total' => round($base + $iva, 2),
            'estado_sri' => 'CREADO',
            'user_id' => auth()->id(),
        ]);

        $nd->detalles()->create([
            'descripcion' => $motivo,
            'cantidad' => 1,
            'precio_unitario' => $base,
            'precio_total_sin_impuesto' => $base,
            'impuesto_codigo' => '2',
            'impuesto_codigo_porcentaje' => $this->codigoPorcentajeIva($this->tarifaIva),
            'impuesto_tarifa' => $this->tarifaIva,
            'impuesto_base' => $base,
            'impuesto_valor' => $iva,
        ]);

        $nd->clave_acceso = $this->generarClaveAcceso($nd);
        $nd->save();

        return $nd;
    }

    /**
     * Crea un Comprobante de Retención.
     *
     * @param  array  $data  Estructura con datos del sujeto retenido,
     *                       documento de sustento y líneas de retención.
     */
    public function emitirRetencion(array $data): ComprobanteElectronico
    {
        $this->validarEmpresa();

        $num = PuntoEmision::siguienteSecuencial('07');

        $totalRetenido = 0;
        foreach ($data['retenciones'] as $r) {
            $totalRetenido += round($r['base_imponible'] * $r['porcentaje'] / 100, 2);
        }

        $retencion = ComprobanteElectronico::create([
            'tipo_documento' => '07',
            'ambiente' => $this->empresa->sri_ambiente ?: '1',
            'tipo_emision' => $this->empresa->sri_tipo_emision ?: '1',
            'estab' => $num['estab'],
            'pto_emi' => $num['pto_emi'],
            'secuencial' => $num['secuencial'],
            'numero_completo' => $num['numero_completo'],
            'emisor_ruc' => $this->empresa->ruc_nit,
            'emisor_razon_social' => $this->empresa->razon_social,
            'emisor_nombre_comercial' => $this->empresa->nombre_comercial,
            'emisor_dir_matriz' => $this->empresa->dir_matriz ?: $this->empresa->direccion,
            'emisor_dir_establecimiento' => $this->empresa->dir_establecimiento ?: $this->empresa->direccion,
            'obligado_contabilidad' => $this->empresa->obligado_contabilidad ? 'SI' : 'NO',
            'contribuyente_especial' => $this->empresa->contribuyente_especial,
            'receptor_tipo_identificacion' => $data['sujeto_tipo_identificacion'],
            'receptor_identificacion' => $data['sujeto_identificacion'],
            'receptor_razon_social' => $data['sujeto_razon_social'],
            'fecha_emision' => $data['fecha_emision'] ?? now()->toDateString(),
            'moneda' => 'DOLAR',
            'periodo_fiscal' => $data['periodo_fiscal'],
            'doc_sustento_tipo' => $data['doc_sustento_tipo'],
            'doc_sustento_numero' => $data['doc_sustento_numero'],
            'doc_sustento_fecha' => $data['doc_sustento_fecha'],
            'total_retenido' => round($totalRetenido, 2),
            'importe_total' => round($totalRetenido, 2),
            'estado_sri' => 'CREADO',
            'user_id' => auth()->id(),
        ]);

        foreach ($data['retenciones'] as $r) {
            $valor = round($r['base_imponible'] * $r['porcentaje'] / 100, 2);
            $retencion->detalles()->create([
                'descripcion' => $r['descripcion'] ?? 'Retención',
                'es_retencion' => true,
                'ret_codigo' => $r['codigo'],
                'ret_codigo_porcentaje' => $r['codigo_porcentaje'],
                'ret_base_imponible' => $r['base_imponible'],
                'ret_porcentaje' => $r['porcentaje'],
                'ret_valor_retenido' => $valor,
            ]);
        }

        $retencion->clave_acceso = $this->generarClaveAcceso($retencion);
        $retencion->save();

        return $retencion;
    }

    /* =====================================================================
     |  ENVÍO AL SRI (firma + recepción + autorización)
     ===================================================================== */

    /**
     * Procesa el envío completo de un comprobante al SRI:
     * firma el XML, lo envía a Recepción y consulta la Autorización.
     */
    public function procesarEnvio(ComprobanteElectronico $comprobante): array
    {
        try {
            // 1. Generar y firmar el XML
            $xml = $this->generarXml($comprobante);
            $xmlFirmado = $this->firmar($xml);

            $rutaXml = $this->guardarArchivo($comprobante, 'firmado', $xmlFirmado);
            $comprobante->xml_path = $rutaXml;
            $comprobante->estado_sri = 'FIRMADO';
            $comprobante->save();

            // 2. Enviar a Recepción
            $recepcion = $this->enviarRecepcion($xmlFirmado, $comprobante->ambiente);
            $comprobante->fecha_envio = now();
            $comprobante->intentos_envio++;

            if ($recepcion['estado'] !== 'RECIBIDA') {
                $comprobante->estado_sri = 'DEVUELTA';
                $comprobante->mensaje_sri = json_encode($recepcion['mensajes'], JSON_UNESCAPED_UNICODE);
                $comprobante->save();

                return ['success' => false, 'estado' => 'DEVUELTA', 'mensajes' => $recepcion['mensajes']];
            }

            $comprobante->estado_sri = 'RECIBIDA';
            $comprobante->save();

            // 3. Consultar Autorización
            $autorizacion = $this->consultarAutorizacion($comprobante->clave_acceso, $comprobante->ambiente);

            if ($autorizacion['estado'] === 'AUTORIZADO') {
                $comprobante->estado_sri = 'AUTORIZADO';
                $comprobante->numero_autorizacion = $autorizacion['numero_autorizacion'];
                $comprobante->fecha_autorizacion = $autorizacion['fecha_autorizacion'];
                $comprobante->mensaje_sri = json_encode($autorizacion['mensajes'], JSON_UNESCAPED_UNICODE);

                if (!empty($autorizacion['comprobante_xml'])) {
                    $rutaAut = $this->guardarArchivo($comprobante, 'autorizado', $autorizacion['comprobante_xml']);
                    $comprobante->xml_autorizado_path = $rutaAut;
                }
                $comprobante->save();

                return ['success' => true, 'estado' => 'AUTORIZADO', 'mensajes' => $autorizacion['mensajes']];
            }

            $comprobante->estado_sri = 'NO_AUTORIZADO';
            $comprobante->mensaje_sri = json_encode($autorizacion['mensajes'], JSON_UNESCAPED_UNICODE);
            $comprobante->save();

            return ['success' => false, 'estado' => $comprobante->estado_sri, 'mensajes' => $autorizacion['mensajes']];
        } catch (\Throwable $e) {
            $comprobante->estado_sri = 'EXCEPCION';
            $comprobante->mensaje_sri = json_encode([['mensaje' => $e->getMessage()]], JSON_UNESCAPED_UNICODE);
            $comprobante->intentos_envio++;
            $comprobante->save();

            return ['success' => false, 'estado' => 'EXCEPCION', 'mensajes' => [['mensaje' => $e->getMessage()]]];
        }
    }

    /** Firma un XML con el certificado .p12 configurado */
    public function firmar(string $xml): string
    {
        $ruta = $this->empresa->sri_certificado_path;
        if (!$ruta || !Storage::exists($ruta)) {
            throw new \RuntimeException('No se encontró el certificado de firma. Súbalo en Configuración → SRI.');
        }

        $signer = new XadesBesSigner(
            Storage::get($ruta),
            (string) $this->empresa->sri_certificado_clave
        );

        return $signer->sign($xml);
    }

    /** Envía el XML firmado al web service de Recepción del SRI */
    public function enviarRecepcion(string $xmlFirmado, string $ambiente): array
    {
        $client = $this->soapClient(self::ENDPOINTS[$ambiente]['recepcion']);
        $respuesta = $client->validarComprobante(['xml' => $xmlFirmado]);

        $resultado = $respuesta->RespuestaRecepcionComprobante ?? null;
        $estado = $resultado->estado ?? 'DEVUELTA';

        return [
            'estado' => $estado,
            'mensajes' => $this->extraerMensajesRecepcion($resultado),
        ];
    }

    /** Consulta la Autorización del comprobante en el SRI */
    public function consultarAutorizacion(string $claveAcceso, string $ambiente, int $reintentos = 3): array
    {
        $client = $this->soapClient(self::ENDPOINTS[$ambiente]['autorizacion']);

        for ($intento = 1; $intento <= $reintentos; $intento++) {
            sleep($intento === 1 ? 3 : 5);

            $respuesta = $client->autorizacionComprobante(['claveAccesoComprobante' => $claveAcceso]);
            $resultado = $respuesta->RespuestaAutorizacionComprobante ?? null;

            $autorizaciones = $resultado->autorizaciones->autorizacion ?? null;
            if (!$autorizaciones) {
                continue;
            }
            // El SRI puede devolver una o varias autorizaciones
            $aut = is_array($autorizaciones) ? $autorizaciones[0] : $autorizaciones;
            $estado = $aut->estado ?? 'EN PROCESAMIENTO';

            if ($estado === 'EN PROCESAMIENTO') {
                continue;
            }

            return [
                'estado' => $estado,
                'numero_autorizacion' => $aut->numeroAutorizacion ?? $claveAcceso,
                'fecha_autorizacion' => isset($aut->fechaAutorizacion)
                    ? Carbon::parse($aut->fechaAutorizacion) : now(),
                'comprobante_xml' => $aut->comprobante ?? null,
                'mensajes' => $this->extraerMensajesAutorizacion($aut),
            ];
        }

        return [
            'estado' => 'EN PROCESAMIENTO',
            'numero_autorizacion' => null,
            'fecha_autorizacion' => null,
            'comprobante_xml' => null,
            'mensajes' => [['mensaje' => 'El SRI aún está procesando el comprobante. Reintente la autorización en unos minutos.']],
        ];
    }

    /* =====================================================================
     |  GENERACIÓN DE XML
     ===================================================================== */

    /** Despacha la generación de XML según el tipo de comprobante */
    public function generarXml(ComprobanteElectronico $c): string
    {
        $c->loadMissing('detalles');

        return match ($c->tipo_documento) {
            '01' => $this->buildXmlFactura($c),
            '04' => $this->buildXmlNotaCredito($c),
            '05' => $this->buildXmlNotaDebito($c),
            '07' => $this->buildXmlRetencion($c),
            default => throw new \RuntimeException("Tipo de comprobante no soportado: {$c->tipo_documento}"),
        };
    }

    private function buildXmlFactura(ComprobanteElectronico $c): string
    {
        $x  = '<?xml version="1.0" encoding="UTF-8"?>';
        $x .= '<factura id="comprobante" version="1.1.0">';
        $x .= $this->bloqueInfoTributaria($c, '01');

        $x .= '<infoFactura>';
        $x .= '<fechaEmision>' . $c->fecha_emision->format('d/m/Y') . '</fechaEmision>';
        $x .= '<dirEstablecimiento>' . $this->e($c->emisor_dir_establecimiento) . '</dirEstablecimiento>';
        if ($c->contribuyente_especial) {
            $x .= '<contribuyenteEspecial>' . $this->e($c->contribuyente_especial) . '</contribuyenteEspecial>';
        }
        $x .= '<obligadoContabilidad>' . $c->obligado_contabilidad . '</obligadoContabilidad>';
        $x .= '<tipoIdentificacionComprador>' . $c->receptor_tipo_identificacion . '</tipoIdentificacionComprador>';
        $x .= '<razonSocialComprador>' . $this->e($c->receptor_razon_social) . '</razonSocialComprador>';
        $x .= '<identificacionComprador>' . $this->e($c->receptor_identificacion) . '</identificacionComprador>';
        if ($c->receptor_direccion) {
            $x .= '<direccionComprador>' . $this->e($c->receptor_direccion) . '</direccionComprador>';
        }
        $x .= '<totalSinImpuestos>' . $this->n($c->total_sin_impuestos) . '</totalSinImpuestos>';
        $x .= '<totalDescuento>' . $this->n($c->total_descuento) . '</totalDescuento>';
        $x .= $this->bloqueTotalConImpuestos($c);
        $x .= '<propina>' . $this->n($c->propina) . '</propina>';
        $x .= '<importeTotal>' . $this->n($c->importe_total) . '</importeTotal>';
        $x .= '<moneda>' . $c->moneda . '</moneda>';
        $x .= '<pagos><pago>';
        $x .= '<formaPago>' . $this->formaPago($c) . '</formaPago>';
        $x .= '<total>' . $this->n($c->importe_total) . '</total>';
        $x .= '</pago></pagos>';
        $x .= '</infoFactura>';

        $x .= '<detalles>';
        foreach ($c->detalles as $d) {
            $x .= $this->bloqueDetalle($d);
        }
        $x .= '</detalles>';

        $x .= $this->bloqueInfoAdicional($c);
        $x .= '</factura>';

        return $x;
    }

    private function buildXmlNotaCredito(ComprobanteElectronico $c): string
    {
        $x  = '<?xml version="1.0" encoding="UTF-8"?>';
        $x .= '<notaCredito id="comprobante" version="1.1.0">';
        $x .= $this->bloqueInfoTributaria($c, '04');

        $x .= '<infoNotaCredito>';
        $x .= '<fechaEmision>' . $c->fecha_emision->format('d/m/Y') . '</fechaEmision>';
        $x .= '<dirEstablecimiento>' . $this->e($c->emisor_dir_establecimiento) . '</dirEstablecimiento>';
        $x .= '<tipoIdentificacionComprador>' . $c->receptor_tipo_identificacion . '</tipoIdentificacionComprador>';
        $x .= '<razonSocialComprador>' . $this->e($c->receptor_razon_social) . '</razonSocialComprador>';
        $x .= '<identificacionComprador>' . $this->e($c->receptor_identificacion) . '</identificacionComprador>';
        if ($c->contribuyente_especial) {
            $x .= '<contribuyenteEspecial>' . $this->e($c->contribuyente_especial) . '</contribuyenteEspecial>';
        }
        $x .= '<obligadoContabilidad>' . $c->obligado_contabilidad . '</obligadoContabilidad>';
        $x .= '<codDocModificado>' . $c->cod_doc_modificado . '</codDocModificado>';
        $x .= '<numDocModificado>' . $c->num_doc_modificado . '</numDocModificado>';
        $x .= '<fechaEmisionDocSustento>' . $c->fecha_emision_doc_sustento->format('d/m/Y') . '</fechaEmisionDocSustento>';
        $x .= '<totalSinImpuestos>' . $this->n($c->total_sin_impuestos) . '</totalSinImpuestos>';
        $x .= '<valorModificacion>' . $this->n($c->valor_modificacion) . '</valorModificacion>';
        $x .= '<moneda>' . $c->moneda . '</moneda>';
        $x .= $this->bloqueTotalConImpuestos($c);
        $x .= '<motivo>' . $this->e($c->motivo) . '</motivo>';
        $x .= '</infoNotaCredito>';

        $x .= '<detalles>';
        foreach ($c->detalles as $d) {
            $x .= $this->bloqueDetalle($d);
        }
        $x .= '</detalles>';

        $x .= $this->bloqueInfoAdicional($c);
        $x .= '</notaCredito>';

        return $x;
    }

    private function buildXmlNotaDebito(ComprobanteElectronico $c): string
    {
        $x  = '<?xml version="1.0" encoding="UTF-8"?>';
        $x .= '<notaDebito id="comprobante" version="1.0.0">';
        $x .= $this->bloqueInfoTributaria($c, '05');

        $x .= '<infoNotaDebito>';
        $x .= '<fechaEmision>' . $c->fecha_emision->format('d/m/Y') . '</fechaEmision>';
        $x .= '<dirEstablecimiento>' . $this->e($c->emisor_dir_establecimiento) . '</dirEstablecimiento>';
        $x .= '<tipoIdentificacionComprador>' . $c->receptor_tipo_identificacion . '</tipoIdentificacionComprador>';
        $x .= '<razonSocialComprador>' . $this->e($c->receptor_razon_social) . '</razonSocialComprador>';
        $x .= '<identificacionComprador>' . $this->e($c->receptor_identificacion) . '</identificacionComprador>';
        if ($c->contribuyente_especial) {
            $x .= '<contribuyenteEspecial>' . $this->e($c->contribuyente_especial) . '</contribuyenteEspecial>';
        }
        $x .= '<obligadoContabilidad>' . $c->obligado_contabilidad . '</obligadoContabilidad>';
        $x .= '<codDocModificado>' . $c->cod_doc_modificado . '</codDocModificado>';
        $x .= '<numDocModificado>' . $c->num_doc_modificado . '</numDocModificado>';
        $x .= '<fechaEmisionDocSustento>' . $c->fecha_emision_doc_sustento->format('d/m/Y') . '</fechaEmisionDocSustento>';
        $x .= '<totalSinImpuestos>' . $this->n($c->total_sin_impuestos) . '</totalSinImpuestos>';

        $x .= '<impuestos>';
        foreach ($c->detalles as $d) {
            $x .= '<impuesto>';
            $x .= '<codigo>' . $d->impuesto_codigo . '</codigo>';
            $x .= '<codigoPorcentaje>' . $d->impuesto_codigo_porcentaje . '</codigoPorcentaje>';
            $x .= '<tarifa>' . $this->n($d->impuesto_tarifa) . '</tarifa>';
            $x .= '<baseImponible>' . $this->n($d->impuesto_base) . '</baseImponible>';
            $x .= '<valor>' . $this->n($d->impuesto_valor) . '</valor>';
            $x .= '</impuesto>';
        }
        $x .= '</impuestos>';

        $x .= '<motivos><motivo>';
        $x .= '<razon>' . $this->e($c->motivo) . '</razon>';
        $x .= '<valor>' . $this->n($c->valor_modificacion) . '</valor>';
        $x .= '</motivo></motivos>';
        $x .= '<valorTotal>' . $this->n($c->importe_total) . '</valorTotal>';
        $x .= '</infoNotaDebito>';

        $x .= $this->bloqueInfoAdicional($c);
        $x .= '</notaDebito>';

        return $x;
    }

    private function buildXmlRetencion(ComprobanteElectronico $c): string
    {
        $x  = '<?xml version="1.0" encoding="UTF-8"?>';
        $x .= '<comprobanteRetencion id="comprobante" version="1.0.0">';
        $x .= $this->bloqueInfoTributaria($c, '07');

        $x .= '<infoCompRetencion>';
        $x .= '<fechaEmision>' . $c->fecha_emision->format('d/m/Y') . '</fechaEmision>';
        $x .= '<dirEstablecimiento>' . $this->e($c->emisor_dir_establecimiento) . '</dirEstablecimiento>';
        if ($c->contribuyente_especial) {
            $x .= '<contribuyenteEspecial>' . $this->e($c->contribuyente_especial) . '</contribuyenteEspecial>';
        }
        $x .= '<obligadoContabilidad>' . $c->obligado_contabilidad . '</obligadoContabilidad>';
        $x .= '<tipoIdentificacionSujetoRetenido>' . $c->receptor_tipo_identificacion . '</tipoIdentificacionSujetoRetenido>';
        $x .= '<razonSocialSujetoRetenido>' . $this->e($c->receptor_razon_social) . '</razonSocialSujetoRetenido>';
        $x .= '<identificacionSujetoRetenido>' . $this->e($c->receptor_identificacion) . '</identificacionSujetoRetenido>';
        $x .= '<periodoFiscal>' . $c->periodo_fiscal . '</periodoFiscal>';
        $x .= '</infoCompRetencion>';

        $x .= '<impuestos>';
        foreach ($c->detalles as $d) {
            $x .= '<impuesto>';
            $x .= '<codigo>' . $d->ret_codigo . '</codigo>';
            $x .= '<codigoRetencion>' . $d->ret_codigo_porcentaje . '</codigoRetencion>';
            $x .= '<baseImponible>' . $this->n($d->ret_base_imponible) . '</baseImponible>';
            $x .= '<porcentajeRetener>' . $this->n($d->ret_porcentaje) . '</porcentajeRetener>';
            $x .= '<valorRetenido>' . $this->n($d->ret_valor_retenido) . '</valorRetenido>';
            $x .= '<codDocSustento>' . $c->doc_sustento_tipo . '</codDocSustento>';
            $x .= '<numDocSustento>' . str_replace('-', '', $c->doc_sustento_numero) . '</numDocSustento>';
            $x .= '<fechaEmisionDocSustento>' . $c->doc_sustento_fecha->format('d/m/Y') . '</fechaEmisionDocSustento>';
            $x .= '</impuesto>';
        }
        $x .= '</impuestos>';

        $x .= $this->bloqueInfoAdicional($c);
        $x .= '</comprobanteRetencion>';

        return $x;
    }

    /* ----------------- Bloques de XML reutilizables ----------------- */

    private function bloqueInfoTributaria(ComprobanteElectronico $c, string $codDoc): string
    {
        $x  = '<infoTributaria>';
        $x .= '<ambiente>' . $c->ambiente . '</ambiente>';
        $x .= '<tipoEmision>' . $c->tipo_emision . '</tipoEmision>';
        $x .= '<razonSocial>' . $this->e($c->emisor_razon_social) . '</razonSocial>';
        if ($c->emisor_nombre_comercial) {
            $x .= '<nombreComercial>' . $this->e($c->emisor_nombre_comercial) . '</nombreComercial>';
        }
        $x .= '<ruc>' . $c->emisor_ruc . '</ruc>';
        $x .= '<claveAcceso>' . $c->clave_acceso . '</claveAcceso>';
        $x .= '<codDoc>' . $codDoc . '</codDoc>';
        $x .= '<estab>' . $c->estab . '</estab>';
        $x .= '<ptoEmi>' . $c->pto_emi . '</ptoEmi>';
        $x .= '<secuencial>' . $c->secuencial . '</secuencial>';
        $x .= '<dirMatriz>' . $this->e($c->emisor_dir_matriz) . '</dirMatriz>';
        if ($this->empresa->agente_retencion) {
            $x .= '<agenteRetencion>' . $this->e($this->empresa->agente_retencion) . '</agenteRetencion>';
        }
        $x .= '</infoTributaria>';

        return $x;
    }

    /** Agrupa los impuestos de las líneas por código de porcentaje */
    private function bloqueTotalConImpuestos(ComprobanteElectronico $c): string
    {
        $grupos = [];
        foreach ($c->detalles as $d) {
            $clave = $d->impuesto_codigo . '|' . $d->impuesto_codigo_porcentaje;
            if (!isset($grupos[$clave])) {
                $grupos[$clave] = [
                    'codigo' => $d->impuesto_codigo,
                    'codigoPorcentaje' => $d->impuesto_codigo_porcentaje,
                    'base' => 0,
                    'valor' => 0,
                ];
            }
            $grupos[$clave]['base'] += (float) $d->impuesto_base;
            $grupos[$clave]['valor'] += (float) $d->impuesto_valor;
        }

        $x = '<totalConImpuestos>';
        foreach ($grupos as $g) {
            $x .= '<totalImpuesto>';
            $x .= '<codigo>' . $g['codigo'] . '</codigo>';
            $x .= '<codigoPorcentaje>' . $g['codigoPorcentaje'] . '</codigoPorcentaje>';
            $x .= '<baseImponible>' . $this->n($g['base']) . '</baseImponible>';
            $x .= '<valor>' . $this->n($g['valor']) . '</valor>';
            $x .= '</totalImpuesto>';
        }
        $x .= '</totalConImpuestos>';

        return $x;
    }

    private function bloqueDetalle(ComprobanteDetalle $d): string
    {
        $x  = '<detalle>';
        if ($d->codigo_principal) {
            $x .= '<codigoPrincipal>' . $this->e($d->codigo_principal) . '</codigoPrincipal>';
        }
        if ($d->codigo_auxiliar) {
            $x .= '<codigoAuxiliar>' . $this->e($d->codigo_auxiliar) . '</codigoAuxiliar>';
        }
        $x .= '<descripcion>' . $this->e($d->descripcion) . '</descripcion>';
        $x .= '<cantidad>' . $this->n($d->cantidad, 6) . '</cantidad>';
        $x .= '<precioUnitario>' . $this->n($d->precio_unitario, 6) . '</precioUnitario>';
        $x .= '<descuento>' . $this->n($d->descuento) . '</descuento>';
        $x .= '<precioTotalSinImpuesto>' . $this->n($d->precio_total_sin_impuesto) . '</precioTotalSinImpuesto>';
        $x .= '<impuestos><impuesto>';
        $x .= '<codigo>' . $d->impuesto_codigo . '</codigo>';
        $x .= '<codigoPorcentaje>' . $d->impuesto_codigo_porcentaje . '</codigoPorcentaje>';
        $x .= '<tarifa>' . $this->n($d->impuesto_tarifa) . '</tarifa>';
        $x .= '<baseImponible>' . $this->n($d->impuesto_base) . '</baseImponible>';
        $x .= '<valor>' . $this->n($d->impuesto_valor) . '</valor>';
        $x .= '</impuesto></impuestos>';
        $x .= '</detalle>';

        return $x;
    }

    private function bloqueInfoAdicional(ComprobanteElectronico $c): string
    {
        $campos = [];
        if ($c->receptor_direccion) {
            $campos['Dirección'] = $c->receptor_direccion;
        }
        if ($c->receptor_telefono) {
            $campos['Teléfono'] = $c->receptor_telefono;
        }
        if ($c->receptor_email) {
            $campos['Email'] = $c->receptor_email;
        }
        if (empty($campos)) {
            return '';
        }

        $x = '<infoAdicional>';
        foreach ($campos as $nombre => $valor) {
            $x .= '<campoAdicional nombre="' . $this->e($nombre) . '">' . $this->e($valor) . '</campoAdicional>';
        }
        $x .= '</infoAdicional>';

        return $x;
    }

    /* =====================================================================
     |  CÁLCULOS Y UTILIDADES
     ===================================================================== */

    /**
     * Calcula las líneas de detalle y los totales de una venta.
     * Los precios de la venta incluyen IVA, por lo que se desglosan.
     */
    private function calcularDetalles(Venta $venta): array
    {
        $factor = 1 + $this->tarifaIva / 100;
        $lineas = [];
        $totalSinImpuestos = 0;
        $totalIva = 0;

        // Descuento global de la venta, distribuido proporcionalmente (sin IVA)
        $descuentoGlobalSinIva = $venta->descuento > 0
            ? round($venta->descuento / $factor, 2) : 0;
        $sumaBruta = 0;
        foreach ($venta->detalles as $d) {
            $sumaBruta += (float) $d->cantidad * (float) $d->precio_unitario;
        }

        $acumDescuento = 0;
        $cantidadLineas = count($venta->detalles);
        $i = 0;

        foreach ($venta->detalles as $d) {
            $i++;
            $aplicaIva = !$d->producto || $d->producto->aplica_impuesto;
            $tarifa = $aplicaIva ? $this->tarifaIva : 0.0;
            $codigoPorcentaje = $this->codigoPorcentajeIva($tarifa);
            $factorLinea = $aplicaIva ? $factor : 1;

            $cantidad = (float) $d->cantidad;
            $brutoLinea = $cantidad * (float) $d->precio_unitario;
            $precioUnitarioSinIva = round((float) $d->precio_unitario / $factorLinea, 6);
            $baseLinea = round($cantidad * $precioUnitarioSinIva, 2);

            // Reparto proporcional del descuento global
            if ($descuentoGlobalSinIva > 0 && $sumaBruta > 0) {
                $descuentoLinea = ($i === $cantidadLineas)
                    ? round($descuentoGlobalSinIva - $acumDescuento, 2)
                    : round($descuentoGlobalSinIva * ($brutoLinea / $sumaBruta), 2);
                $acumDescuento += $descuentoLinea;
            } else {
                $descuentoLinea = 0;
            }

            $precioTotalSinImpuesto = round($baseLinea - $descuentoLinea, 2);
            $ivaLinea = round($precioTotalSinImpuesto * $tarifa / 100, 2);

            $totalSinImpuestos += $precioTotalSinImpuesto;
            $totalIva += $ivaLinea;

            $lineas[] = [
                'codigo_principal' => $d->codigo,
                'descripcion' => $d->descripcion,
                'cantidad' => $cantidad,
                'precio_unitario' => $precioUnitarioSinIva,
                'descuento' => $descuentoLinea,
                'precio_total_sin_impuesto' => $precioTotalSinImpuesto,
                'impuesto_codigo' => '2',
                'impuesto_codigo_porcentaje' => $codigoPorcentaje,
                'impuesto_tarifa' => $tarifa,
                'impuesto_base' => $precioTotalSinImpuesto,
                'impuesto_valor' => $ivaLinea,
            ];
        }

        return [
            'lineas' => $lineas,
            'total_sin_impuestos' => round($totalSinImpuestos, 2),
            'total_descuento' => round($acumDescuento, 2),
            'total_iva' => round($totalIva, 2),
            'importe_total' => round($totalSinImpuestos + $totalIva, 2),
        ];
    }

    /**
     * Genera la clave de acceso de 49 dígitos del SRI.
     *
     * Estructura: fecha(8) + tipoComprobante(2) + ruc(13) + ambiente(1)
     *  + serie(6) + secuencial(9) + códigoNumérico(8) + tipoEmisión(1)
     *  + dígitoVerificador(1)
     */
    public function generarClaveAcceso(ComprobanteElectronico $c): string
    {
        if (!$c->codigo_numerico) {
            $c->codigo_numerico = str_pad((string) random_int(0, 99999999), 8, '0', STR_PAD_LEFT);
        }

        $cadena = $c->fecha_emision->format('dmY')
            . $c->tipo_documento
            . str_pad($c->emisor_ruc, 13, '0', STR_PAD_LEFT)
            . $c->ambiente
            . $c->estab . $c->pto_emi
            . $c->secuencial
            . $c->codigo_numerico
            . $c->tipo_emision;

        return $cadena . $this->digitoVerificadorModulo11($cadena);
    }

    /** Dígito verificador módulo 11 para la clave de acceso */
    private function digitoVerificadorModulo11(string $cadena): string
    {
        $factor = 2;
        $suma = 0;
        for ($i = strlen($cadena) - 1; $i >= 0; $i--) {
            $suma += (int) $cadena[$i] * $factor;
            $factor = $factor === 7 ? 2 : $factor + 1;
        }
        $verificador = 11 - ($suma % 11);
        if ($verificador === 11) {
            $verificador = 0;
        } elseif ($verificador === 10) {
            $verificador = 1;
        }

        return (string) $verificador;
    }

    /** Devuelve el código de porcentaje de IVA del SRI según la tarifa */
    public function codigoPorcentajeIva(float $tarifa): string
    {
        return match ((int) round($tarifa)) {
            0 => '0',
            12 => '2',
            14 => '3',
            15 => '4',
            5 => '5',
            default => '4',
        };
    }

    /** Determina los datos del receptor a partir del cliente de la venta */
    private function datosReceptor($cliente): array
    {
        if (!$cliente) {
            return [
                'tipo' => '07',
                'identificacion' => '9999999999999',
                'razon_social' => 'CONSUMIDOR FINAL',
                'direccion' => null,
                'email' => null,
                'telefono' => null,
            ];
        }

        $tipo = \App\Helpers\CedulaRucValidator::detectarTipoSri($cliente->documento);

        return [
            'tipo' => $tipo,
            'identificacion' => $tipo === '07'
                ? '9999999999999'
                : preg_replace('/\D/', '', (string) $cliente->documento),
            'razon_social' => $cliente->nombre_completo ?: 'CONSUMIDOR FINAL',
            'direccion' => $cliente->direccion,
            'email' => $cliente->email,
            'telefono' => $cliente->telefono,
        ];
    }

    /** Mapea la forma de pago de la venta al catálogo del SRI */
    private function formaPago(ComprobanteElectronico $c): string
    {
        $forma = $c->venta?->forma_pago;

        return match ($forma) {
            'tarjeta' => '19',       // Tarjeta de crédito
            'transferencia' => '20', // Otros con utilización del sistema financiero
            'credito' => '09',       // Tarjeta de crédito (a crédito de la empresa)
            default => '01',         // Sin utilización del sistema financiero
        };
    }

    /* ----------------- SOAP / archivos ----------------- */

    private function soapClient(string $wsdl): SoapClient
    {
        $contexto = stream_context_create([
            'ssl' => [
                'verify_peer' => false,
                'verify_peer_name' => false,
            ],
        ]);

        return new SoapClient($wsdl, [
            'soap_version' => SOAP_1_1,
            'trace' => true,
            'exceptions' => true,
            'connection_timeout' => 30,
            'cache_wsdl' => WSDL_CACHE_NONE,
            'stream_context' => $contexto,
        ]);
    }

    private function extraerMensajesRecepcion($resultado): array
    {
        $mensajes = [];
        $comprobantes = $resultado->comprobantes->comprobante ?? null;
        if ($comprobantes) {
            $comprobantes = is_array($comprobantes) ? $comprobantes : [$comprobantes];
            foreach ($comprobantes as $comp) {
                $lista = $comp->mensajes->mensaje ?? [];
                $lista = is_array($lista) ? $lista : [$lista];
                foreach ($lista as $m) {
                    $mensajes[] = [
                        'identificador' => $m->identificador ?? '',
                        'mensaje' => $m->mensaje ?? '',
                        'informacion' => $m->informacionAdicional ?? '',
                        'tipo' => $m->tipo ?? '',
                    ];
                }
            }
        }

        return $mensajes ?: [['mensaje' => 'Comprobante recibido por el SRI.']];
    }

    private function extraerMensajesAutorizacion($autorizacion): array
    {
        $mensajes = [];
        $lista = $autorizacion->mensajes->mensaje ?? [];
        if ($lista) {
            $lista = is_array($lista) ? $lista : [$lista];
            foreach ($lista as $m) {
                $mensajes[] = [
                    'identificador' => $m->identificador ?? '',
                    'mensaje' => $m->mensaje ?? '',
                    'informacion' => $m->informacionAdicional ?? '',
                    'tipo' => $m->tipo ?? '',
                ];
            }
        }

        return $mensajes;
    }

    /** Guarda un archivo XML del comprobante en storage */
    private function guardarArchivo(ComprobanteElectronico $c, string $tipo, string $contenido): string
    {
        $dir = 'sri/' . $tipo . '/' . date('Y/m');
        Storage::makeDirectory($dir);
        $ruta = $dir . '/' . $c->clave_acceso . '.xml';
        Storage::put($ruta, $contenido);

        return $ruta;
    }

    private function validarEmpresa(): void
    {
        if (!$this->empresa) {
            throw new \RuntimeException('No hay datos de empresa configurados.');
        }
        if (!$this->empresa->ruc_nit || strlen(preg_replace('/\D/', '', $this->empresa->ruc_nit)) !== 13) {
            throw new \RuntimeException('El RUC de la empresa no es válido (debe tener 13 dígitos).');
        }
    }

    /** Escapa texto para XML */
    private function e(?string $texto): string
    {
        return htmlspecialchars(trim((string) $texto), ENT_XML1 | ENT_QUOTES, 'UTF-8');
    }

    /** Formatea un número con la cantidad de decimales indicada */
    private function n($valor, int $decimales = 2): string
    {
        return number_format((float) $valor, $decimales, '.', '');
    }
}
