<?php

namespace App\Services\Sri;

use App\Models\Comprobante;
use App\Models\Configuracion;
use App\Models\SecuenciaSri;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

/**
 * Orquesta el ciclo de vida completo de un comprobante electrónico:
 * clave de acceso -> XML -> firma -> envío a Recepción -> Autorización.
 *
 * Es idempotente: si ya se avanzó una etapa (por ejemplo el XML ya está
 * firmado pero el envío falló por un corte de red), una nueva llamada a
 * emitir() retoma desde la siguiente etapa pendiente en vez de repetir todo
 * y quemar un secuencial nuevo cada vez.
 */
class Facturador
{
    public function __construct(
        private FacturaXmlBuilder $xmlBuilder = new FacturaXmlBuilder,
        private XmlSigner $signer = new XmlSigner,
    ) {
    }

    public function emitir(Comprobante $comprobante): Comprobante
    {
        $config = Configuracion::actual();

        if (! $config->sri_activo) {
            throw new SriException('La facturación electrónica no está activada. Actívala en Configuración → SRI.');
        }
        if (! $config->sri_listo) {
            throw new SriException('Falta completar el RUC (13 dígitos) o subir el certificado de firma (.p12) en Configuración → SRI.');
        }

        if (in_array($comprobante->estado_sri, ['autorizado', 'anulado_sri'])) {
            throw new SriException('Este comprobante ya fue autorizado/anulado ante el SRI; no puede reenviarse.');
        }

        if (in_array($comprobante->estado_sri, ['no_electronico', 'borrador', null])) {
            $this->generarClaveYXml($comprobante, $config);
        }
        if ($comprobante->estado_sri === 'generado') {
            $this->firmar($comprobante, $config);
        }
        if (in_array($comprobante->estado_sri, ['firmado', 'devuelta', 'rechazado'])) {
            $this->enviarRecepcion($comprobante, $config);
        }
        if ($comprobante->estado_sri === 'enviado') {
            $this->consultarAutorizacion($comprobante, $config);
        }

        return $comprobante->fresh();
    }

    /** Solo vuelve a consultar Autorización (para el botón "Consultar estado" cuando quedó EN_PROCESO). */
    public function reintentarAutorizacion(Comprobante $comprobante): Comprobante
    {
        $config = Configuracion::actual();

        if (! $comprobante->clave_acceso) {
            throw new SriException('El comprobante todavía no tiene clave de acceso; usa "Emitir" primero.');
        }

        $this->consultarAutorizacion($comprobante, $config);

        return $comprobante->fresh();
    }

    private function generarClaveYXml(Comprobante $comprobante, Configuracion $config): void
    {
        $ambiente = (int) $config->sri_ambiente;

        [$claveAcceso, $secuencial] = DB::transaction(function () use ($comprobante, $config, $ambiente) {
            $secuencial = SecuenciaSri::siguiente(ClaveAccesoBuilder::COD_DOC_FACTURA, $config->sri_estab, $config->sri_pto_emi);

            $clave = ClaveAccesoBuilder::generar(
                fechaEmision: Carbon::parse($comprobante->fecha),
                ruc: $config->ruc_limpio,
                ambiente: $ambiente,
                estab: $config->sri_estab,
                ptoEmi: $config->sri_pto_emi,
                secuencial: $secuencial,
            );

            $comprobante->update([
                'ambiente' => $ambiente,
                'cod_doc' => ClaveAccesoBuilder::COD_DOC_FACTURA,
                'secuencial' => str_pad((string) $secuencial, 9, '0', STR_PAD_LEFT),
                'clave_acceso' => $clave,
                'electronico' => true,
            ]);

            return [$clave, $secuencial];
        });

        $xml = $this->xmlBuilder->construir($comprobante->fresh(), $config);

        $ruta = rtrim(config('sri.rutas.generados'), '/')."/{$claveAcceso}.xml";
        Storage::disk('local')->put($ruta, $xml);

        $comprobante->update([
            'xml_generado_path' => $ruta,
            'estado_sri' => 'generado',
            'mensaje_sri' => null,
        ]);
    }

    private function firmar(Comprobante $comprobante, Configuracion $config): void
    {
        $xml = Storage::disk('local')->get($comprobante->xml_generado_path);

        $xmlFirmado = $this->signer->firmar(
            $xml,
            $config->rutaCertificadoAbsoluta(),
            $config->sri_certificado_password
        );

        $ruta = rtrim(config('sri.rutas.firmados'), '/')."/{$comprobante->clave_acceso}.xml";
        Storage::disk('local')->put($ruta, $xmlFirmado);

        $comprobante->update([
            'xml_firmado_path' => $ruta,
            'estado_sri' => 'firmado',
        ]);
    }

    private function enviarRecepcion(Comprobante $comprobante, Configuracion $config): void
    {
        $xmlFirmado = Storage::disk('local')->get($comprobante->xml_firmado_path);
        $cliente = new SriSoapClient((int) $config->sri_ambiente);

        $resultado = $cliente->enviarRecepcion($xmlFirmado);

        if (strtoupper($resultado['estado']) === 'RECIBIDA') {
            $comprobante->update(['estado_sri' => 'enviado', 'mensaje_sri' => null]);

            return;
        }

        $comprobante->update([
            'estado_sri' => 'devuelta',
            'mensaje_sri' => implode(' | ', $resultado['mensajes']) ?: 'El SRI devolvió el comprobante en Recepción.',
        ]);
    }

    private function consultarAutorizacion(Comprobante $comprobante, Configuracion $config): void
    {
        $cliente = new SriSoapClient((int) $config->sri_ambiente);
        $intentos = (int) config('sri.autorizacion_intentos', 3);
        $espera = (int) config('sri.autorizacion_espera_segundos', 3);

        $resultado = ['estado' => 'EN_PROCESO'];

        for ($i = 0; $i < $intentos; $i++) {
            $resultado = $cliente->consultarAutorizacion($comprobante->clave_acceso);

            if (strtoupper($resultado['estado']) !== 'EN_PROCESO') {
                break;
            }
            if ($i < $intentos - 1) {
                sleep($espera);
            }
        }

        $estado = strtoupper($resultado['estado']);

        if ($estado === 'AUTORIZADO') {
            $xmlAutorizado = is_string($resultado['comprobante']) ? $resultado['comprobante'] : (string) $resultado['comprobante'];
            $ruta = rtrim(config('sri.rutas.autorizados'), '/')."/{$comprobante->clave_acceso}.xml";
            Storage::disk('local')->put($ruta, $xmlAutorizado);

            $comprobante->update([
                'estado_sri' => 'autorizado',
                'numero_autorizacion' => $resultado['numeroAutorizacion'],
                'fecha_autorizacion' => $this->parsearFechaSri($resultado['fechaAutorizacion']),
                'xml_autorizado_path' => $ruta,
                'mensaje_sri' => null,
            ]);

            return;
        }

        if ($estado === 'EN_PROCESO') {
            // Se queda en "enviado": el usuario puede volver a consultar en unos minutos.
            $comprobante->update(['mensaje_sri' => 'El SRI todavía está procesando el comprobante. Vuelve a consultar en unos minutos.']);

            return;
        }

        // NO AUTORIZADO u otro estado terminal negativo.
        $comprobante->update([
            'estado_sri' => 'no_autorizado',
            'mensaje_sri' => implode(' | ', $resultado['mensajes']) ?: "El SRI respondió: {$estado}.",
        ]);
    }

    private function parsearFechaSri(?string $fecha): ?Carbon
    {
        if (! $fecha) {
            return null;
        }

        try {
            return Carbon::parse($fecha);
        } catch (\Throwable) {
            return null;
        }
    }
}
