<?php
/**
 * Standalone HMAC signer for direct Freemius API calls.
 *
 * Byte-compatible with Freemius_Api_WordPress::GenerateAuthorizationParams()
 * (vendor/freemius/wordpress-sdk/includes/sdk/FreemiusWordPress.php), but
 * deliberately self-contained: this class MUST NOT depend on any vendor
 * Freemius code. The whole point is to keep signing functional when another
 * plugin's dev-version SDK (v999.99.99) hijacks the in-process Freemius
 * singleton. See support sprint Group A / SDK hijack defense.
 *
 * Algorithm verified against:
 *   - FreemiusWordPress.php:231-278 (GenerateAuthorizationParams)
 *   - FreemiusBase.php:197-215      (Base64UrlEncode — strips `=` padding)
 *
 * @package AIPT\Core\Licensing
 * @since   2.6.0
 */

namespace AIPT\Core\Licensing;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

final class HmacSigner {

	/**
	 * Build the Date + Authorization headers for a Freemius API request.
	 *
	 * @param string     $method     HTTP method (e.g. 'GET', 'POST', 'PUT').
	 * @param string     $path       Resource path beginning with '/', e.g. '/v1/plugins/15570/installs/99.json'.
	 * @param int|string $scope_id   Scope entity ID (install_id, license_id, user_id).
	 * @param string     $public_key Scope public key.
	 * @param string     $secret_key Scope secret key.
	 * @param string     $body       Raw request body (only relevant for POST/PUT).
	 * @param int|null   $now        Optional fixed unix timestamp (for testing). Defaults to time().
	 * @return array{
	 *     date: string,
	 *     authorization: string,
	 *     content_md5: string,
	 *     content_type: string,
	 *     string_to_sign: string
	 * }
	 */
	public static function sign(
		string $method,
		string $path,
		$scope_id,
		string $public_key,
		string $secret_key,
		string $body = '',
		?int $now = null
	): array {
		$method = strtoupper( $method );
		$now    = $now ?? time();
		$date   = gmdate( 'D, d M Y H:i:s', $now ) . ' +0000';

		$content_md5  = '';
		$content_type = '';
		if ( in_array( $method, array( 'POST', 'PUT' ), true ) ) {
			$content_type = 'application/json';
			if ( '' !== $body ) {
				$content_md5 = md5( $body );
			}
		}

		$string_to_sign = implode(
			"\n",
			array(
				$method,
				$content_md5,
				$content_type,
				$date,
				$path,
			)
		);

		$auth_type = ( $secret_key === $public_key ) ? 'FSP' : 'FS';

		$signature = self::base64UrlEncode( hash_hmac( 'sha256', $string_to_sign, $secret_key, true ) );

		$authorization = sprintf( '%s %s:%s:%s', $auth_type, (string) $scope_id, $public_key, $signature );

		return array(
			'date'           => $date,
			'authorization'  => $authorization,
			'content_md5'    => $content_md5,
			'content_type'   => $content_type,
			'string_to_sign' => $string_to_sign,
		);
	}

	/**
	 * URL-safe base64 (RFC 4648 §5) with stripped '=' padding.
	 *
	 * Mirrors Freemius's FreemiusBase.php:Base64UrlEncode exactly.
	 *
	 * @param string $input Raw binary data.
	 * @return string URL-safe base64 without padding.
	 */
	private static function base64UrlEncode( string $input ): string {
		return str_replace( '=', '', strtr( base64_encode( $input ), '+/', '-_' ) );
	}
}
