<?php

namespace Gaco\SnapBi\Services\Signature;

use Gaco\SnapBi\Contracts\ConfigInterface;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Date;

/**
 * Auth Signature (B2B Access Token - Asymmetric RSA SHA256)
 * 
 * Algorithm: SHA256withRSA
 * Key Size: 512 bit
 * Format: X.509 (Base64 encoded DER)
 * 
 * String to Sign: client_ID + "|" + X-TIMESTAMP
 */
class Auth
{
    protected ?ConfigInterface $config = null;

    public function __construct(?ConfigInterface $config = null)
    {
        $this->config = $config;
    }

    /**
     * Create asymmetric signature for B2B Access Token
     * 
     * Process:
     * 1. stringToSign = client_ID + "|" + X-TIMESTAMP
     * 2. signature = SHA256withRSA(privateKey, stringToSign)
     * 3. X-SIGNATURE = Base64.encode(signature)
     * 
     * @param string $clientId Client ID (from X-CLIENT-KEY header)
     * @param string|null $timestamp Timestamp (ISO format, will be generated if not provided)
     * @return string Base64 encoded signature
     * @throws \InvalidArgumentException If private key is not available
     */
    public function create(string $clientId, ?string $timestamp = null): string
    {
        if (!$this->config) {
            throw new \InvalidArgumentException('Configuration is required for signature creation');
        }

        // Generate timestamp if not provided
        if ($timestamp === null) {
            $timestamp = Date::now()->utc()->format('Y-m-d\TH:i:s.000\Z');
        }

        // Build string to sign: client_ID + "|" + X-TIMESTAMP
        $stringToSign = $clientId . '|' . $timestamp;

        // Get private key
        $privateKeyPem = $this->config->get('private_key');
        if (!$privateKeyPem) {
            throw new \InvalidArgumentException('Private key is required for signature creation');
        }

        // Load private key
        $privateKey = openssl_pkey_get_private($privateKeyPem);
        if ($privateKey === false) {
            throw new \InvalidArgumentException('Invalid private key format');
        }

        // Sign with SHA256withRSA
        $signature = '';
        $success = openssl_sign($stringToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256);

        // Free key resource
        if (is_resource($privateKey)) {
            openssl_free_key($privateKey);
        }

        if (!$success) {
            throw new \RuntimeException('Failed to create signature');
        }

        // Encode signature to Base64
        return base64_encode($signature);
    }

    /**
     * Verify asymmetric signature for B2B Access Token
     * 
     * Process:
     * 1. stringToSign = client_ID + "|" + X-TIMESTAMP
     * 2. signature = Base64.decode(X-SIGNATURE)
     * 3. isValid = SHA256withRSA.verify(publicKey, stringToSign, signature)
     * 
     * @param Request $request HTTP request
     * @param string $signature Base64 encoded signature from X-SIGNATURE header
     * @return bool True if signature is valid
     * @throws \InvalidArgumentException If required headers or public key are missing
     */
    public function verify(Request $request, string $signature): bool
    {
        if (!$this->config) {
            throw new \InvalidArgumentException('Configuration is required for signature verification');
        }

        // Get required headers
        $clientId = $request->header('X-CLIENT-KEY');
        $timestamp = $request->header('X-TIMESTAMP');

        if (!$clientId || !$timestamp) {
            throw new \InvalidArgumentException('X-CLIENT-KEY and X-TIMESTAMP headers are required');
        }

        // Build string to sign: client_ID + "|" + X-TIMESTAMP
        $stringToSign = $clientId . '|' . $timestamp;

        // Get public key
        $publicKeyPem = $this->config->get('public_key');
        if (!$publicKeyPem) {
            throw new \InvalidArgumentException('Public key is required for signature verification');
        }

        // Load public key (supports both PEM and Base64 X.509 format)
        $publicKey = $this->loadPublicKey($publicKeyPem);
        if ($publicKey === false) {
            throw new \InvalidArgumentException('Invalid public key format');
        }

        // Decode signature from Base64
        $signatureBytes = base64_decode($signature, true);
        if ($signatureBytes === false) {
            return false;
        }

        // Verify signature
        $result = openssl_verify($stringToSign, $signatureBytes, $publicKey, OPENSSL_ALGO_SHA256);

        // Free key resource
        if (is_resource($publicKey)) {
            openssl_free_key($publicKey);
        }

        return $result === 1;
    }

    /**
     * Load public key from various formats
     * 
     * Supports:
     * 1. PEM format: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n"
     * 2. Base64 X.509 format: Base64 encoded DER (X.509)
     * 
     * @param string $publicKeyPem Public key in PEM or Base64 X.509 format
     * @return \OpenSSLAsymmetricKey|resource|false
     */
    protected function loadPublicKey(string $publicKeyPem)
    {
        // Try PEM format first
        $publicKey = openssl_pkey_get_public($publicKeyPem);
        if ($publicKey !== false) {
            return $publicKey;
        }

        // Try Base64 X.509 format
        $decoded = base64_decode($publicKeyPem, true);
        if ($decoded !== false) {
            // Convert DER to PEM format
            $pem = "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($decoded), 64, "\n") . "-----END PUBLIC KEY-----\n";
            return openssl_pkey_get_public($pem);
        }

        return false;
    }
}
