<?php
/**
 * Affiliate routing template.
 *
 * @package Affilun\Application\Affiliate
 */

declare(strict_types=1);

namespace Affilun\Application\Affiliate;

use InvalidArgumentException;

/**
 * Safe opt-in URL template for ASP-style link routing.
 */
final class AffiliateRoutingTemplate {

	/**
	 * Allowed template variables.
	 *
	 * @var list<string>
	 */
	private const ALLOWED_VARIABLES = array( 'url', 'encoded_url', 'provider', 'product_id' );

	/**
	 * Template string.
	 */
	private string $template;

	/**
	 * @param string $template Template string.
	 *
	 * @throws InvalidArgumentException When the template is invalid.
	 */
	public function __construct( string $template ) {
		$this->template = self::normalizeTemplate( $template );
	}

	/**
	 * Return template string.
	 */
	public function template(): string {
		return $this->template;
	}

	/**
	 * Apply routing to a destination URL.
	 *
	 * @param string   $url       Destination URL.
	 * @param string   $provider  Provider key.
	 * @param int|null $productId Product id.
	 */
	public function apply( string $url, string $provider, ?int $productId = null ): string {
		$rendered = strtr(
			$this->template,
			array(
				'{url}'         => $url,
				'{encoded_url}' => rawurlencode( $url ),
				'{provider}'    => rawurlencode( $provider ),
				'{product_id}'  => $productId === null ? '' : (string) $productId,
			)
		);

		if ( ! self::isSafeHttpUrl( $rendered ) ) {
			return $url;
		}

		return $rendered;
	}

	/**
	 * Normalize and validate template.
	 *
	 * @param string $template Template.
	 *
	 * @throws InvalidArgumentException When invalid.
	 */
	private static function normalizeTemplate( string $template ): string {
		$template = trim( $template );
		if ( $template === '' ) {
			throw new InvalidArgumentException( 'Affiliate routing template must be non-empty.' );
		}

		if ( preg_match_all( '/\{([a-z_]+)\}/', $template, $matches ) === 1 ) {
			foreach ( $matches[1] as $variable ) {
				if ( ! in_array( $variable, self::ALLOWED_VARIABLES, true ) ) {
					throw new InvalidArgumentException( 'Unknown affiliate routing template variable.' );
				}
			}
		}

		$sample = strtr(
			$template,
			array(
				'{url}'         => 'https://example.test/product',
				'{encoded_url}' => 'https%3A%2F%2Fexample.test%2Fproduct',
				'{provider}'    => 'amazon',
				'{product_id}'  => '123',
			)
		);
		if ( ! self::isSafeHttpUrl( $sample ) ) {
			throw new InvalidArgumentException( 'Affiliate routing template must render to an HTTP(S) URL.' );
		}

		return $template;
	}

	/**
	 * Check absolute HTTP(S) URL.
	 *
	 * @param string $url URL.
	 */
	private static function isSafeHttpUrl( string $url ): bool {
		return preg_match( '#\Ahttps?://#i', $url ) === 1;
	}
}
