<?php
/**
 * ZATCA XML Signer - FIXED VERSION
 * Properly handles XAdES signing time and certificate digest
 */

require_once __DIR__ . '/chilkat/chilkat.php';
class ZATCASigner {
    private $config;
    private $cert;
    private $privKey;
    
    public function __construct($config) {
        $this->config = $config;
        
        // Initialize Chilkat (if license exists)
        if (!empty($config['chilkat_license'])) {
            $glob = new CkGlobal();
            @$glob->UnlockBundle($config['chilkat_license']);
        }
        
        $this->loadCertificate();
    }
    
    private function loadCertificate() {
        $this->cert = new CkCert();
        
        if (!$this->cert->LoadFromFile($this->config['certificate_path'])) {
            throw new Exception('Failed to load certificate');
        }
        
        $this->privKey = new CkPrivateKey();
        if (!$this->privKey->LoadPemFile($this->config['private_key_path'])) {
            throw new Exception('Failed to load private key');
        }
        
        if (!$this->cert->SetPrivateKey($this->privKey)) {
            throw new Exception('Failed to associate private key');
        }
    }
    
    /**
     * Sign XML document - returns signed XML with signature embedded
     */
    public function signXML($xmlString) {
        $gen = new CkXmlDSigGen();
        
        // Set signature location (inside the UBLExtensions structure)
        $gen->put_SigLocation('Invoice|ext:UBLExtensions|ext:UBLExtension|ext:ExtensionContent|sig:UBLDocumentSignatures|sac:SignatureInformation');
        $gen->put_SigLocationMod(0);
        $gen->put_SigId('signature');
        $gen->put_SigNamespacePrefix('ds');
        $gen->put_SigNamespaceUri('http://www.w3.org/2000/09/xmldsig#');
        $gen->put_SignedInfoCanonAlg('C14N_11');
        $gen->put_SignedInfoDigestMethod('sha256');
        
        // Create XAdES Object with proper signing time
        $xadesObj = $this->createXAdESObject();
        $gen->AddObject('', $xadesObj->getXml(), '', '');
        
        // Add References
        $this->addReferences($gen);
        
        // Set certificate
        if (!$gen->SetX509Cert($this->cert, true)) {
            throw new Exception('Failed to set certificate');
        }
        
        $gen->put_KeyInfoType('X509Data');
        $gen->put_X509Type('Certificate');
        
        // ZATCA specific behaviors
        $gen->put_Behaviors('IndentedSignature,TransformSignatureXPath,ZATCA');
        
        // Sign the XML
        $sbXml = new CkStringBuilder();
        $sbXml->Append($xmlString);
        
        if (!$gen->CreateXmlDSigSb($sbXml)) {
            throw new Exception('Failed to create signature: ' . $gen->lastErrorText());
        }
        
        $signedXml = $sbXml->getAsString();
        
        // Fix QR ID attribute (ZATCA requires quotes)
        $signedXml = str_replace('=QR', "='QR'", $signedXml);
        
        return [
            'signed_xml' => $signedXml,
            'generator' => $gen
        ];
    }
    
    /**
     * Create XAdES Qualifying Properties
     * CRITICAL: Chilkat will auto-fill signing time in ISO 8601 format
     */
    private function createXAdESObject() {
        $obj = new CkXml();
        $obj->put_Tag('xades:QualifyingProperties');
        $obj->AddAttribute('xmlns:xades', 'http://uri.etsi.org/01903/v1.3.2#');
        $obj->AddAttribute('Target', 'signature');
        
        $obj->UpdateAttrAt('xades:SignedProperties', true, 'Id', 'xadesSignedProperties');
        
        // Signing Time - Let Chilkat generate this automatically
        // It will use ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ
        $obj->UpdateChildContent('xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningTime', 
            'TO BE GENERATED BY CHILKAT');
        
        // Certificate Digest - Let Chilkat calculate SHA256 of certificate
        $obj->UpdateAttrAt('xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningCertificate|xades:Cert|xades:CertDigest|ds:DigestMethod', 
            true, 'Algorithm', 'http://www.w3.org/2001/04/xmlenc#sha256');
        $obj->UpdateChildContent('xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningCertificate|xades:Cert|xades:CertDigest|ds:DigestValue', 
            'TO BE GENERATED BY CHILKAT');
        
        // Issuer Serial - Let Chilkat fill these
        $obj->UpdateChildContent('xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningCertificate|xades:Cert|xades:IssuerSerial|ds:X509IssuerName', 
            'TO BE GENERATED BY CHILKAT');
        $obj->UpdateChildContent('xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningCertificate|xades:Cert|xades:IssuerSerial|ds:X509SerialNumber', 
            'TO BE GENERATED BY CHILKAT');
        
        // Add namespace for ds elements
        $obj->UpdateAttrAt('xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningCertificate|xades:Cert|xades:IssuerSerial|ds:X509IssuerName', 
            true, 'xmlns:ds', 'http://www.w3.org/2000/09/xmldsig#');
        $obj->UpdateAttrAt('xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningCertificate|xades:Cert|xades:IssuerSerial|ds:X509SerialNumber', 
            true, 'xmlns:ds', 'http://www.w3.org/2000/09/xmldsig#');
        
        return $obj;
    }
    
    /**
     * Add signature references with transforms
     */
    private function addReferences($gen) {
        // Reference 1: Document with transforms
        $xml1 = new CkXml();
        $xml1->put_Tag('ds:Transforms');
        
        // Transform 1: Exclude UBLExtensions
        $xml1->UpdateAttrAt('ds:Transform', true, 'Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
        $xml1->UpdateChildContent('ds:Transform|ds:XPath', 'not(//ancestor-or-self::ext:UBLExtensions)');
        
        // Transform 2: Exclude Signature
        $xml1->UpdateAttrAt('ds:Transform[1]', true, 'Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
        $xml1->UpdateChildContent('ds:Transform[1]|ds:XPath', 'not(//ancestor-or-self::cac:Signature)');
        
        // Transform 3: Exclude QR code
        $xml1->UpdateAttrAt('ds:Transform[2]', true, 'Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
        $xml1->UpdateChildContent('ds:Transform[2]|ds:XPath', 'not(//ancestor-or-self::cac:AdditionalDocumentReference[cbc:ID=\'QR\'])');
        
        // Transform 4: Canonicalization
        $xml1->UpdateAttrAt('ds:Transform[3]', true, 'Algorithm', 'http://www.w3.org/2006/12/xml-c14n11');
        
        $gen->AddSameDocRef2('', 'sha256', $xml1, '');
        $gen->SetRefIdAttr('', 'invoiceSignedData');
        
        // Reference 2: XAdES Signed Properties
        $gen->AddObjectRef('xadesSignedProperties', 'sha256', '', '', 
            'http://www.w3.org/2000/09/xmldsig#SignatureProperties');
    }
    
    /**
     * Extract digest value from signed XML (this is the invoice hash)
     */
    public function extractDigestValue($signedXml) {
        $xml = new CkXml();
        if (!$xml->LoadXml($signedXml)) {
            throw new Exception('Failed to parse signed XML');
        }
        
        // Get digest value from first reference
        $digestValue = $xml->getChildContent(
            'ext:UBLExtensions|ext:UBLExtension|ext:ExtensionContent|sig:UBLDocumentSignatures|sac:SignatureInformation|ds:Signature|ds:SignedInfo|ds:Reference[0]|ds:DigestValue'
        );
        
        if (empty($digestValue)) {
            throw new Exception('DigestValue not found in signed XML');
        }
        
        // Remove any whitespace
        $digestValue = trim($digestValue);
        
        return $digestValue;
    }
    
    /**
     * Extract signature value from signed XML (for QR code)
     */
    public function extractSignatureValue($signedXml) {
        $xml = new CkXml();
        if (!$xml->LoadXml($signedXml)) {
            throw new Exception('Failed to parse signed XML');
        }
        
        $signatureValue = $xml->getChildContent(
            'ext:UBLExtensions|ext:UBLExtension|ext:ExtensionContent|sig:UBLDocumentSignatures|sac:SignatureInformation|ds:Signature|ds:SignatureValue'
        );
        
        if (empty($signatureValue)) {
            throw new Exception('SignatureValue not found in signed XML');
        }
        
        // Remove whitespace and newlines
        $signatureValue = preg_replace('/\s+/', '', $signatureValue);
        
        return $signatureValue;
    }
}
?>