<?php
/**
 * HMAC document URL service.
 *
 * @package EntryFoundry
 */

namespace EntryFoundry\Core\Services\Security;

// Constructor and signing method types define this focused service contract.
// phpcs:disable Squiz.Commenting, Generic.Commenting.DocComment

/** Creates and validates tamper-resistant template/entry URLs. */
final class SignedUrlService {
	/** @var string */ private $secret;

	/** @param string $secret Site-specific signing secret. */
	public function __construct( string $secret ) {
		$this->secret = $secret; }

	/**
	 * Build a signed URL.
	 *
	 * @param string $base_url    Admin-post endpoint.
	 * @param int    $template_id Template ID.
	 * @param int    $entry_id    Entry ID.
	 * @param string $format      html or pdf.
	 * @param string $disposition view or download.
	 * @param int    $ttl         Lifetime in seconds.
	 * @return string
	 */
	public function create( string $base_url, int $template_id, int $entry_id, string $format, string $disposition, int $ttl = 3600 ): string {
		$args              = $this->create_args( $template_id, $entry_id, $format, $disposition, $ttl );
		$args['signature'] = $this->signature( $args );
		return add_query_arg( $args, $base_url );
	}

	/**
	 * Build a signed URL whose document identity is encoded in its path.
	 *
	 * @param string $base_url    Public document path.
	 * @param int    $template_id Template ID.
	 * @param int    $entry_id    Entry ID.
	 * @param string $format      html or pdf.
	 * @param string $disposition view or download.
	 * @param int    $ttl         Lifetime in seconds.
	 * @return string
	 */
	public function create_pretty( string $base_url, int $template_id, int $entry_id, string $format, string $disposition, int $ttl = 3600 ): string {
		$args      = $this->create_args( $template_id, $entry_id, $format, $disposition, $ttl );
		$signature = $this->signature( $args );
		return add_query_arg(
			array(
				'expires'   => $args['expires'],
				'signature' => $signature,
			),
			$base_url
		);
	}

	/** @return array<string,mixed> */
	private function create_args( int $template_id, int $entry_id, string $format, string $disposition, int $ttl ): array {
		return array(
			'action'      => 'entryfoundry_document',
			'template_id' => $template_id,
			'entry_id'    => $entry_id,
			'format'      => $format,
			'disposition' => $disposition,
			'expires'     => time() + max( 60, $ttl ),
		);
	}

	/** @param array<string,mixed> $args Request arguments. */
	public function validate( array $args ): bool {
		if ( empty( $args['signature'] ) || empty( $args['expires'] ) || time() > (int) $args['expires'] ) {
			return false; }
		$provided = (string) $args['signature'];
		unset( $args['signature'] );
		return hash_equals( $this->signature( $args ), $provided );
	}

	/** @param array<string,mixed> $args Signature inputs. */
	private function signature( array $args ): string {
		$keys  = array( 'action', 'template_id', 'entry_id', 'format', 'disposition', 'expires' );
		$parts = array();
		foreach ( $keys as $key ) {
			$parts[] = (string) ( $args[ $key ] ?? '' ); }
		return hash_hmac( 'sha256', implode( '|', $parts ), $this->secret );
	}
}
