<?php
/**
 * SriService.php
 * 
 * Clase principal para la gestión de facturación electrónica SRI
 * Autor: Gemini Antigravity
 */

require_once __DIR__ . '/XmlSigner.php';
require_once __DIR__ . '/SriLogger.php';

class SriService
{
    private $ambiente; // 1: Pruebas, 2: Producción
    private $wsdlRecepcion;
    private $wsdlAutorizacion;
    private $rutaCertificado;
    private $passwordCertificado;
    private $rutaXmlGenerados;
    private $rutaXmlFirmados;

    // Configuración por defecto
    const TIPO_EMISION = '1'; // 1: Emisión Normal
    const URL_RECEPCION_PRUEBAS = 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantesOffline?wsdl';
    const URL_AUTORIZACION_PRUEBAS = 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantesOffline?wsdl';
    const URL_RECEPCION_PROD = 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantesOffline?wsdl';
    const URL_AUTORIZACION_PROD = 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantesOffline?wsdl';

    public function getRutaGenerado($clave)
    {
        return $this->rutaXmlGenerados . $clave . '.xml';
    }

    public function __construct($ambiente = '1', $rutaCert, $passCert)
    {
        $this->ambiente = $ambiente;
        $this->rutaCertificado = $rutaCert;
        $this->passwordCertificado = $passCert;

        // Rutas de archivos (asumiendo estructura ../files/sri/)
        $baseDir = __DIR__ . '/../files/sri';
        $this->rutaXmlGenerados = $baseDir . '/generados/';
        $this->rutaXmlFirmados = $baseDir . '/firmados/';

        // Crear directorios si no existen
        if (!file_exists($this->rutaXmlGenerados))
            mkdir($this->rutaXmlGenerados, 0755, true);
        if (!file_exists($this->rutaXmlFirmados))
            mkdir($this->rutaXmlFirmados, 0755, true);

        if ($this->ambiente == '1') {
            $this->wsdlRecepcion = self::URL_RECEPCION_PRUEBAS;
            $this->wsdlAutorizacion = self::URL_AUTORIZACION_PRUEBAS;
        } else {
            $this->wsdlRecepcion = self::URL_RECEPCION_PROD;
            $this->wsdlAutorizacion = self::URL_AUTORIZACION_PROD;
        }
    }

    /**
     * Genera, Firma y Envía una Factura
     */
    public function procesarFactura($datosFactura)
    {
        try {
            // 1. Generar Clave de Acceso
            $claveAcceso = $this->generarClaveAcceso($datosFactura);
            $datosFactura['infoTributaria']['claveAcceso'] = $claveAcceso;

            // 2. Generar XML
            $xmlContent = $this->construirXmlFactura($datosFactura);
            $archivoXml = $this->rutaXmlGenerados . $claveAcceso . '.xml';
            file_put_contents($archivoXml, $xmlContent);

            // 3. Firmar XML
            $signer = new XmlSigner();
            $xmlFirmado = $signer->firmarXml($xmlContent, $this->rutaCertificado, $this->passwordCertificado);
            $archivoFirmado = $this->rutaXmlFirmados . $claveAcceso . '.xml';
            file_put_contents($archivoFirmado, $xmlFirmado);

            // 4. Enviar al SRI
            $resEnvio = $this->enviarComprobante($archivoFirmado);

            if ($resEnvio['estado'] === 'RECIBIDA') {
                // 5. Autorizar 
                // Implementamos reintentos porque el SRI a veces tarda en replicar el estado
                $intentos = 0;
                $maxIntentos = 20; // Aumentamos a 20 intentos (aprox 40 seg) para dar tiempo al SRI
                $resAutorizacion = null;
                $estadoAutorizacion = '';
                $mensajesError = [];

                while ($intentos < $maxIntentos) {
                    sleep(2);
                    $resAutorizacion = $this->autorizarComprobante($claveAcceso);

                    // Analizar respuesta 
                    if (is_array($resAutorizacion)) {
                        $estadoAutorizacion = isset($resAutorizacion['estado']) ? $resAutorizacion['estado'] : '';
                    } elseif (is_object($resAutorizacion)) {
                        if (isset($resAutorizacion->autorizaciones->autorizacion)) {
                            $auth = $resAutorizacion->autorizaciones->autorizacion;
                            if (is_array($auth))
                                $auth = end($auth);
                            $estadoAutorizacion = isset($auth->estado) ? $auth->estado : '';
                        }
                    }

                    if ($estadoAutorizacion == 'AUTORIZADO' || $estadoAutorizacion == 'NO AUTORIZADO' || $estadoAutorizacion == 'RECHAZADA') {
                        break;
                    }

                    $intentos++;
                }

                // Si salimos del loop y aun no hay estado (vacío) O está EN_PROCESO explícito
                // Es porque el SRI sigue procesando
                if ((empty($estadoAutorizacion) || $estadoAutorizacion == 'EN_PROCESO') && $resEnvio['estado'] == 'RECIBIDA') {
                    // CASO ESPECIAL: RECIBIDA PERO NO RESPONDIO AUTORIZACION A TIEMPO
                    return [
                        'success' => true, // Lo marcamos como success para que se guarde y active polling
                        'etapa' => 'AUTORIZACION_PENDIENTE',
                        'claveAcceso' => $claveAcceso,
                        'mensaje' => 'Factura RECIBIDA. El SRI está procesando la autorización. Puede verificar el estado en unos segundos desde el listado.',
                        'resultado' => $resAutorizacion
                    ];
                }

                // Evaluar resultado final normal
                if ($estadoAutorizacion === 'AUTORIZADO') {
                    return [
                        'success' => true,
                        'etapa' => 'AUTORIZACION',
                        'claveAcceso' => $claveAcceso,
                        'resultado' => $resAutorizacion
                    ];
                } else {
                    // Construir mensaje de error detallado
                    $errorData = is_array($resAutorizacion) ? $resAutorizacion : (array) $resAutorizacion;
                    return [
                        'success' => false,
                        'etapa' => 'AUTORIZACION',
                        'claveAcceso' => $claveAcceso,
                        'message' => "SRI Respondió: $estadoAutorizacion",
                        'error' => $errorData,
                        'debug_recepcion' => $resEnvio
                    ];
                }

            } else {
                return [
                    'success' => false,
                    'etapa' => 'ENVIO',
                    'claveAcceso' => $claveAcceso,
                    'error' => $resEnvio
                ];
            }

        } catch (Exception $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    public function enviarComprobante($rutaXmlFirmado)
    {
        $logger = new SriLogger();
        
        // Enviar XML firmado en Base64
        $xmlContent = file_get_contents($rutaXmlFirmado);
        $params = ['xml' => $xmlContent];

        // Configuración SSL para evitar errores de certificado
        $soapOptions = [
            'trace' => 1,
            'exceptions' => true,
            'connection_timeout' => 10, // Timeout de conexión de 10s
            'stream_context' => stream_context_create([
                'ssl' => [
                    'verify_peer' => false,
                    'verify_peer_name' => false,
                    'allow_self_signed' => true
                ]
            ])
        ];

        // Reintentos en caso de error de red
        $maxRetries = 3;
        $retryDelay = 2; // sender

        for ($intento = 1; $intento <= $maxRetries; $intento++) {
            try {
                $logger->log('INFO', "Intento envío recepción ($intento/$maxRetries) a " . $this->wsdlRecepcion);
                
                $client = new SoapClient($this->wsdlRecepcion, $soapOptions);
                $result = $client->validarComprobante($params);

                // Log request/response raw
                //$logger->log('bg-info', 'Request Header: ' . $client->__getLastRequestHeaders());
                //$logger->log('bg-info', 'Request XML: ' . $client->__getLastRequest());
                $logger->log('INFO', 'SRI Recepción Response: ', $result);

                // Parsear resultado SOAP
                $estado = '';
                if (isset($result->estado)) {
                    $estado = $result->estado;
                } elseif (isset($result->RespuestaRecepcionComprobante->estado)) {
                    $estado = $result->RespuestaRecepcionComprobante->estado;
                }

                if ($estado == 'RECIBIDA') {
                    return ['estado' => 'RECIBIDA'];
                } else {
                    // Verificar si es error 70: CLAVE DE ACCESO EN PROCESAMIENTO
                    $mensajes = json_encode($result);
                    if (strpos($mensajes, 'PROCESAMIENTO') !== false || strpos($mensajes, '70') !== false || strpos($mensajes, 'REGISTRADA') !== false || strpos($mensajes, '43') !== false) {
                        $logger->log('WARNING', 'Factura ya estaba registrada o en procesamiento (Error 43/70), asumiendo RECIBIDA');
                        return ['estado' => 'RECIBIDA'];
                    }

                    return ['estado' => 'DEVUELTA', 'mensajes' => $result];
                }

            } catch (SoapFault $e) {
                $logger->log('ERROR', "Fallo SOAP Recepción ($intento): " . $e->getMessage());
                
                // Si es el último intento, lanzar el error
                if ($intento == $maxRetries) {
                    throw new Exception("Error SOAP Envio (después de $maxRetries intentos): " . $e->getMessage());
                }
                sleep($retryDelay);
            } catch (Exception $e) {
                $logger->log('ERROR', "Excepción General Recepción: " . $e->getMessage());
                throw $e;
            }
        }
    }

    public function autorizarComprobante($claveAcceso)
    {
        $logger = new SriLogger();
        
        // Configuración SSL para evitar errores de certificado
        $soapOptions = [
            'trace' => 1,
            'exceptions' => true,
            'connection_timeout' => 10,
            'stream_context' => stream_context_create([
                'ssl' => [
                    'verify_peer' => false,
                    'verify_peer_name' => false,
                    'allow_self_signed' => true
                ]
            ])
        ];

        try {
            $logger->log('INFO', "Consultando Autorización para: $claveAcceso en " . $this->wsdlAutorizacion);
            
            $client = new SoapClient($this->wsdlAutorizacion, $soapOptions);
            $params = ['claveAccesoComprobante' => $claveAcceso];
            $result = $client->autorizacionComprobante($params);

            //$logger->log('INFO', 'SRI Autorización Response RAW: ', $result);

            // Limpiar la respuesta
            $autorizaciones = null;
            if (isset($result->autorizaciones)) {
                $autorizaciones = $result->autorizaciones;
            } elseif (isset($result->RespuestaAutorizacionComprobante->autorizaciones)) {
                $autorizaciones = $result->RespuestaAutorizacionComprobante->autorizaciones;
            }

            if (isset($autorizaciones->autorizacion)) {
                $auth = $autorizaciones->autorizacion;
                
                // Si hay multiples, usualmente es un array. Tomamos el último (el más reciente)
                // OJO: A veces el SRI devuelve el array en orden inverso o mezclado si hubo reintentos.
                // Lo ideal es buscar el que diga AUTORIZADO.
                if (is_array($auth)) {
                    $authFound = end($auth); // Default al último
                    foreach ($auth as $a) {
                        if (isset($a->estado) && $a->estado == 'AUTORIZADO') {
                            $authFound = $a;
                            break;
                        }
                    }
                    $auth = $authFound;
                }

                $estadoAuth = isset($auth->estado) ? $auth->estado : 'DESCONOCIDO';
                $logger->log('INFO', "Estado encontrado: $estadoAuth");

                // Construir respuesta limpia
                $respuestaLimpia = [
                    'estado' => $estadoAuth,
                    'numeroAutorizacion' => isset($auth->numeroAutorizacion) ? $auth->numeroAutorizacion : '',
                    'fechaAutorizacion' => isset($auth->fechaAutorizacion) ? $auth->fechaAutorizacion : '',
                    'ambiente' => isset($auth->ambiente) ? $auth->ambiente : '',
                ];

                // Extraer mensajes de error si existen
                if (isset($auth->mensajes)) {
                    $respuestaLimpia['mensajes'] = $auth->mensajes;
                    $logger->log('WARNING', 'Mensajes en autorización:', $auth->mensajes);
                }

                return $respuestaLimpia;
            } else {
                // Caso: Respuesta válida pero sin autorización (vacío). Significa que aun no procesan.
                $logger->log('INFO', 'SRI respondió pero no hay objeto autorización (Aun no procesado o rechazado limpio)');
                return ['estado' => 'EN_PROCESO', 'mensaje' => 'No se encontró autorización aún'];
            }

        } catch (SoapFault $e) {
            $logger->log('ERROR', "Error SOAP Autorizacion: " . $e->getMessage());
            throw new Exception("Error SOAP Autorizacion: " . $e->getMessage());
        }
    }

    /**
     * Algoritmo Módulo 11 para Clave de Acceso
     */
    public function generarClaveAcceso($datos)
    {
        // PADDING ESTRICTO Y LIMPIEZA DE DATOS
        $fechaEmision = isset($datos['infoTributaria']['fechaEmision']) ? $datos['infoTributaria']['fechaEmision'] : date('dmY');
        $ruc = isset($datos['infoTributaria']['ruc']) ? $datos['infoTributaria']['ruc'] : '9999999999999';
        $tipoComprobante = isset($datos['infoTributaria']['codDoc']) ? $datos['infoTributaria']['codDoc'] : '01';
        $ambiente = isset($datos['infoTributaria']['ambiente']) ? $datos['infoTributaria']['ambiente'] : '1';
        $estab = isset($datos['infoTributaria']['estab']) ? $datos['infoTributaria']['estab'] : '001';
        $ptoEmi = isset($datos['infoTributaria']['ptoEmi']) ? $datos['infoTributaria']['ptoEmi'] : '001';
        $secuencial = isset($datos['infoTributaria']['secuencial']) ? $datos['infoTributaria']['secuencial'] : '000000001';

        // 1. Limpieza
        $fecha = preg_replace('/\D/', '', (string) $fechaEmision);
        $ruc = preg_replace('/\D/', '', (string) $ruc);

        // 2. Padding Estricto (Crucial para evitar error de longitud)
        $ruc = str_pad($ruc, 13, '0', STR_PAD_LEFT); // RUC siempre 13
        $estab = str_pad($estab, 3, '0', STR_PAD_LEFT); // Estab siempre 3
        $ptoEmi = str_pad($ptoEmi, 3, '0', STR_PAD_LEFT); // PtoEmi siempre 3
        $secuencial = str_pad($secuencial, 9, '0', STR_PAD_LEFT); // Secuencial siempre 9

        $serie = $estab . $ptoEmi;
        $codigoNumerico = '12345678';
        $tipoEmision = '1';

        $clave = $fecha . $tipoComprobante . $ruc . $ambiente . $serie . $secuencial . $codigoNumerico . $tipoEmision;

        // Asegurar que solo hay digitos
        if (!ctype_digit($clave)) {
            $clave = preg_replace('/\D/', '', $clave);
        }

        // Modulo 11
        $digits = str_split(strrev($clave));
        $total = 0;
        $factor = 2;

        foreach ($digits as $digit) {
            $total += $digit * $factor;
            $factor = ($factor == 7) ? 2 : $factor + 1;
        }

        $dv = 11 - ($total % 11);
        if ($dv == 11)
            $dv = 0;
        if ($dv == 10)
            $dv = 1;

        return $clave . $dv;
    }

    /**
     * Construye el XML crudo (sin firmar)
     */
    public function construirXmlFactura($datos)
    {
        // Esta función debe construir el XML string según XSD v2.1.0 del SRI
        // Se recomienda usar XMLWriter o DOMDocument
        // Por brevedad, aquí retornamos un esqueleto

        $xml = new DOMDocument('1.0', 'UTF-8');
        // $xml->xmlStandalone = true; // REMOVED: Puede afectar C14N
        $xml->formatOutput = false; // NO indentación para evitar problemas de hash


        $factura = $xml->createElement('factura');
        $factura->setAttribute('id', 'comprobante'); // REQUIRED by SRI signature validator
        $factura->setAttribute('version', '2.1.0');
        $xml->appendChild($factura);

        // infoTributaria
        $infoTrib = $xml->createElement('infoTributaria');
        $factura->appendChild($infoTrib);

        $this->addNode($xml, $infoTrib, 'ambiente', $datos['infoTributaria']['ambiente']);
        $this->addNode($xml, $infoTrib, 'tipoEmision', '1');
        $this->addNode($xml, $infoTrib, 'razonSocial', $datos['infoTributaria']['razonSocial']);
        $this->addNode($xml, $infoTrib, 'ruc', $datos['infoTributaria']['ruc']);
        $this->addNode($xml, $infoTrib, 'claveAcceso', $datos['infoTributaria']['claveAcceso']);
        $this->addNode($xml, $infoTrib, 'codDoc', '01');
        $this->addNode($xml, $infoTrib, 'estab', $datos['infoTributaria']['estab']);
        $this->addNode($xml, $infoTrib, 'ptoEmi', $datos['infoTributaria']['ptoEmi']);
        $this->addNode($xml, $infoTrib, 'secuencial', $datos['infoTributaria']['secuencial']);
        $this->addNode($xml, $infoTrib, 'dirMatriz', $datos['infoTributaria']['dirMatriz']);

        // infoFactura
        $infoFact = $xml->createElement('infoFactura');
        $factura->appendChild($infoFact);
        $this->addNode($xml, $infoFact, 'fechaEmision', date('d/m/Y')); // Formato dd/mm/aaaa
        $this->addNode($xml, $infoFact, 'dirEstablecimiento', $datos['infoFactura']['dirEstablecimiento']);
        $this->addNode($xml, $infoFact, 'obligadoContabilidad', 'NO');
        $this->addNode($xml, $infoFact, 'tipoIdentificacionComprador', $datos['infoFactura']['tipoIdentificacionComprador']);
        $this->addNode($xml, $infoFact, 'razonSocialComprador', $datos['infoFactura']['razonSocialComprador']);
        $this->addNode($xml, $infoFact, 'identificacionComprador', $datos['infoFactura']['identificacionComprador']);

        $this->addNode($xml, $infoFact, 'totalSinImpuestos', $datos['infoFactura']['totalSinImpuestos']);
        $this->addNode($xml, $infoFact, 'totalDescuento', '0.00');

        // totalConImpuestos (Dinámico)
        $totalConImpuestos = $xml->createElement('totalConImpuestos');
        $infoFact->appendChild($totalConImpuestos);

        if (isset($datos['infoFactura']['totalConImpuestos']) && is_array($datos['infoFactura']['totalConImpuestos'])) {
            foreach ($datos['infoFactura']['totalConImpuestos'] as $impuestoData) {
                $totalImpuesto = $xml->createElement('totalImpuesto');
                $totalConImpuestos->appendChild($totalImpuesto);
                $this->addNode($xml, $totalImpuesto, 'codigo', $impuestoData['codigo']);
                $this->addNode($xml, $totalImpuesto, 'codigoPorcentaje', $impuestoData['codigoPorcentaje']);
                $this->addNode($xml, $totalImpuesto, 'baseImponible', number_format($impuestoData['baseImponible'], 2, '.', ''));
                $this->addNode($xml, $totalImpuesto, 'valor', number_format($impuestoData['valor'], 2, '.', ''));
            }
        }

        $this->addNode($xml, $infoFact, 'propina', '0.00');
        $this->addNode($xml, $infoFact, 'importeTotal', number_format($datos['infoFactura']['importeTotal'], 2, '.', ''));
        $this->addNode($xml, $infoFact, 'moneda', 'DOLAR');

        // Pagos
        $pagos = $xml->createElement('pagos');
        $infoFact->appendChild($pagos);
        $pago = $xml->createElement('pago');
        $pagos->appendChild($pago);
        $this->addNode($xml, $pago, 'formaPago', '01'); // Sin uso sist. financiero
        $this->addNode($xml, $pago, 'total', number_format($datos['infoFactura']['importeTotal'], 2, '.', ''));

        // Detalles
        $detalles = $xml->createElement('detalles');
        $factura->appendChild($detalles);

        foreach ($datos['detalles'] as $item) {
            $detalle = $xml->createElement('detalle');
            $detalles->appendChild($detalle);
            $this->addNode($xml, $detalle, 'codigoPrincipal', $item['codigo']);
            $this->addNode($xml, $detalle, 'descripcion', $item['descripcion']);
            $this->addNode($xml, $detalle, 'cantidad', number_format($item['cantidad'], 2, '.', ''));
            $this->addNode($xml, $detalle, 'precioUnitario', number_format($item['precioUnitario'], 2, '.', ''));
            $this->addNode($xml, $detalle, 'descuento', number_format($item['descuento'], 2, '.', ''));
            $this->addNode($xml, $detalle, 'precioTotalSinImpuesto', number_format($item['totalSinImpuesto'], 2, '.', ''));

            $impuestos = $xml->createElement('impuestos');
            $detalle->appendChild($impuestos);

            if (isset($item['impuestos']) && is_array($item['impuestos'])) {
                foreach ($item['impuestos'] as $impItem) {
                    $impuesto = $xml->createElement('impuesto');
                    $impuestos->appendChild($impuesto);
                    $this->addNode($xml, $impuesto, 'codigo', $impItem['codigo']);
                    $this->addNode($xml, $impuesto, 'codigoPorcentaje', $impItem['codigoPorcentaje']);
                    $this->addNode($xml, $impuesto, 'tarifa', $impItem['tarifa']);
                    $this->addNode($xml, $impuesto, 'baseImponible', number_format($impItem['baseImponible'], 2, '.', ''));
                    $this->addNode($xml, $impuesto, 'valor', number_format($impItem['valor'], 2, '.', ''));
                }
            }
        }

        // infoAdicional (Correcto lugar para el correo según estándar)
        if (isset($datos['infoFactura']['emailComprador']) && !empty($datos['infoFactura']['emailComprador'])) {
            $infoAdicional = $xml->createElement('infoAdicional');
            $factura->appendChild($infoAdicional);
            
            $campoAdicional = $xml->createElement('campoAdicional', $datos['infoFactura']['emailComprador']);
            $campoAdicional->setAttribute('nombre', 'Email');
            $infoAdicional->appendChild($campoAdicional);
        }

        return $xml->saveXML();
    }

    private function addNode($dom, $parent, $name, $value)
    {
        // Fix for deprecated passing null to parameter #2
        $valStr = isset($value) ? (string) $value : '';
        $node = $dom->createElement($name, $valStr);
        $parent->appendChild($node);
    }
}
?>