<?php
namespace Stibenamm\FirmaXadesCR;

use Stibenamm\FirmaXadesCR\Contracts\FirmaXades as FirmaXadesContract;
use Stibenamm\FirmaXadesCR\Overrides\XMLSecurityDSig;
use RobRichards\XMLSecLibs\XMLSecurityKey;

class Firmador implements FirmaXadesContract
{
    const FROM_XML_STRING   = 1;
    const FROM_XML_FILE     = 2;
    const TO_BASE64_STRING  = 3;
    const TO_XML_STRING     = 4;
    const TO_XML_FILE       = 5;

    /**
     * Firma un XML con XAdES-EPES usando el certificado P12
     *
     * @param string $pfx    Ruta al archivo .p12
     * @param string $pin    PIN del certificado
     * @param string $input  XML (string) o ruta a XML
     * @param int    $output Tipo de salida (constantes TO_*)
     * @param string|null $path Ruta de guardado si se usa TO_XML_FILE
     * @return string|bool|null
     */
    public function firmarXml($pfx, $pin, $input, $output, $path = null)
    {
        // Cargar un nuevo XML para ser firmado
        $xml = new \DOMDocument();

        // Detectar si es un archivo (ruta en disco) o bien un string XML
        if (file_exists($input)) {
            $input = file_get_contents($input);
        }

        // Intentar parsear el input como XML. Caso contrario se detiene el script
        try {
            $xml->loadXML($input);
        } catch (\Exception $ex) {
            die($ex->getMessage());
        }

        // Crear un nuevo objeto de seguridad
        $objSec = new XMLSecurityDSig();

        // Mantener el primer nodo secundario original XML en memoria
        $objSec->xmlFirstChild = $xml->firstChild;

        // Establecer política de firma (usa xmlFirstChild->xmlns internamente)
        // IMPORTANTE: XMLSecurityDSig::setSignPolicy() NO debe recibir parámetros.
        $objSec->setSignPolicy();

        // Cargar la información del certificado desde el archivo *.p12
        $certInfo = $objSec->loadCertInfo($pfx, $pin);

        // Usar la canonicalización exclusiva de c14n.
        $objSec->setCanonicalMethod($objSec::C14N);

        // Cargar la clave privada del certificado
        $objKey = new XMLSecurityKey(
            XMLSecurityKey::RSA_SHA256,
            ['type' => 'private']
        );
        $objKey->loadKey($certInfo['privateKey']);

        // Agregar el certificado X509 a la firma
        $objSec->add509Cert($certInfo['publicKey'], true);

        // Agregar información de clave (KeyInfo)
        $objSec->appendKeyValue($certInfo);

        // Agregar estructura XAdES (QualifyingProperties, SignedProperties, etc.)
        $objSec->appendXades($certInfo);

        // Agregar referencia a los datos a firmar (documento raíz)
        $objSec->addReference(
            $xml,
            $objSec::SHA256,
            ['http://www.w3.org/2000/09/xmldsig#enveloped-signature'],
            [
                'id_name'   => 'Id',
                'overwrite' => false,
                'id'        => $objSec->reference0Id,
            ]
        );

        // Agregar referencia a KeyInfo
        $objSec->addReference(
            $objSec->getKeyInfoNode(),
            $objSec::SHA256,
            [],
            [
                'id_name'   => 'Id',
                'overwrite' => false,
                'id'        => $objSec->reference1Id,
            ]
        );

        // Agregar referencia a XAdES (SignedProperties)
        $objSec->addReference(
            $objSec->getXadesNode(),
            $objSec::SHA256,
            ['http://www.w3.org/2001/10/xml-exc-c14n#'],
            [
                'id_name'   => 'Id',
                'overwrite' => false,
                'id'        => $objSec->reference2Id,
            ]
        );

        // Firmar el documento (inserta <ds:Signature> con todo lo anterior)
        $objSec->sign($objKey, $xml->documentElement);

        // Agregar la firma como nodo hijo del documento
        $objSec->appendSignature($xml->documentElement);

        // Retornar el documento firmado según el tipo de salida
        if ($output == self::TO_BASE64_STRING) {
            // Devuelve archivo firmado en formato Base64
            return base64_encode($xml->saveXML());
        } elseif ($output == self::TO_XML_STRING) {
            // Devuelve el archivo xml firmado en formato string Xml
            return $xml->saveXML();
        } elseif ($output == self::TO_XML_FILE) {
            // Guarda el xml firmado en la ruta especificada y devuelve el resultado
            if (!is_null($path)) {
                return $xml->save($path);
            }
            return false;
        }

        return null;
    }
}
