<?php
/**
 * SwapAds HMAC Signer
 *
 * Shared library for HMAC-SHA256 request signing between client and server.
 * Used by all API calls.
 *
 * @package SwapAds\Shared
 * @since   1.0.0
 */

declare(strict_types=1);

namespace SwapAds\Shared\Hmac;

/**
 * Class Signer
 *
 * Implements HMAC-SHA256 signing for SwapAds API requests.
 *
 * Signature format:
 *   signature = hmac_sha256(
 *       secret_key = license_key + server_secret,
 *       message = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + BODY_SHA256
 *   )
 *
 * @since 1.0.0
 */
final class Signer
{
    /**
     * Signing algorithm
     */
    private const ALGORITHM = 'sha256';

    /**
     * Constructor
     *
     * @param string $secret Server secret (loaded from server config).
     *
     * @since 1.0.0
     */
    public function __construct(
        private readonly string $secret
    ) {
    }

    /**
     * Sign a request
     *
     * Generates an HMAC signature for the given request parameters.
     *
     * @param string $method    HTTP method (GET, POST, PUT, DELETE).
     * @param string $path      Request path (e.g., /v1/license/activate).
     * @param string $timestamp Unix timestamp (must be within 5 minutes of server time).
     * @param string $nonce     Random nonce (32+ chars, must be unique per request).
     * @param string $body      Request body (empty string for GET).
     *
     * @return string HMAC-SHA256 signature (lowercase hex).
     *
     * @since 1.0.0
     */
    public function sign(
        string $method,
        string $path,
        string $timestamp,
        string $nonce,
        string $body = ''
    ): string {
        $message = $this->buildMessage($method, $path, $timestamp, $nonce, $body);
        return hash_hmac(self::ALGORITHM, $message, $this->secret);
    }

    /**
     * Verify a signature
     *
     * Validates that a provided signature matches what we would generate
     * for the given request parameters.
     *
     * @param string $signature Provided signature to verify.
     * @param string $method    HTTP method.
     * @param string $path      Request path.
     * @param string $timestamp Unix timestamp.
     * @param string $nonce     Nonce.
     * @param string $body      Request body.
     *
     * @return bool True if signature is valid, false otherwise.
     *
     * @since 1.0.0
     */
    public function verify(
        string $signature,
        string $method,
        string $path,
        string $timestamp,
        string $nonce,
        string $body = ''
    ): bool {
        $expected = $this->sign($method, $path, $timestamp, $nonce, $body);
        return hash_equals($expected, $signature);
    }

    /**
     * Build the message to sign
     *
     * Constructs the canonical message string for HMAC signing.
     *
     * @param string $method    HTTP method.
     * @param string $path      Request path.
     * @param string $timestamp Unix timestamp.
     * @param string $nonce     Nonce.
     * @param string $body      Request body.
     *
     * @return string Canonical message.
     *
     * @since 1.0.0
     */
    private function buildMessage(
        string $method,
        string $path,
        string $timestamp,
        string $nonce,
        string $body
    ): string {
        $bodyHash = hash('sha256', $body);
        return strtoupper($method) . "\n" . $path . "\n" . $timestamp . "\n" . $nonce . "\n" . $bodyHash;
    }

    /**
     * Generate a random nonce
     *
     * Creates a cryptographically secure random nonce for replay protection.
     *
     * @param int $length Length in bytes (default: 32 = 64 hex chars).
     *
     * @return string Random nonce (hex).
     *
     * @since 1.0.0
     */
    public static function generateNonce(int $length = 32): string
    {
        return bin2hex(random_bytes($length));
    }
}
