<?php
/**
 * affilun Cloud license verifier.
 *
 * @package Affilun\Application\Cloud
 */

declare(strict_types=1);

namespace Affilun\Application\Cloud;

use Affilun\Application\Settings\CloudSettings;
use InvalidArgumentException;
use RuntimeException;

/**
 * Verifies a site license through a signed affilun Cloud request.
 */
final class CloudLicenseVerifier {

	/**
	 * @var callable
	 */
	private $transport;

	/**
	 * Request signer.
	 */
	private CloudRequestSigner $signer;

	/**
	 * @param callable|null           $transport Transport callback.
	 * @param CloudRequestSigner|null $signer    Request signer.
	 *
	 * @phpstan-param (callable(string, array<string,string>, string): array<string,mixed>)|null $transport
	 */
	public function __construct( ?callable $transport = null, ?CloudRequestSigner $signer = null ) {
		$this->transport = $transport ?? self::defaultTransport();
		$this->signer    = $signer ?? new CloudRequestSigner();
	}

	/**
	 * Verify license.
	 *
	 * @param CloudSettings $settings Cloud settings.
	 * @param int           $timestamp Unix timestamp.
	 * @param string        $nonce     Nonce.
	 *
	 * @throws InvalidArgumentException When cloud settings are incomplete.
	 * @throws RuntimeException         When transport/response fails.
	 */
	public function verify( CloudSettings $settings, int $timestamp, string $nonce ): CloudLicenseVerificationResult {
		if ( ! $settings->isConfigured() ) {
			throw new InvalidArgumentException( 'affilun Cloud settings are not configured.' );
		}

		$path    = '/license/verify';
		$payload = array(
			'site_id' => $settings->siteId(),
		);
		if ( function_exists( 'wp_json_encode' ) ) {
			$body = wp_json_encode( $payload );
		} else {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- Pure-unit fallback when WordPress is unavailable.
			$body = json_encode( $payload );
		}
		if ( ! is_string( $body ) ) {
			throw new RuntimeException( 'Failed to encode affilun Cloud verification request.' );
		}

		$headers                 = $this->signer->sign(
			$settings->siteId(),
			$settings->apiKey(),
			'POST',
			$path,
			$body,
			$timestamp,
			$nonce
		);
		$headers['Content-Type'] = 'application/json';

		$response = ( $this->transport )( $settings->endpoint() . $path, $headers, $body );
		$status   = isset( $response['status'] ) ? (int) $response['status'] : 0;
		$data     = isset( $response['data'] ) && is_array( $response['data'] ) ? $response['data'] : null;
		if ( $status < 200 || $status >= 300 || $data === null ) {
			throw new RuntimeException( 'affilun Cloud license verification failed.' );
		}

		return CloudLicenseVerificationResult::fromArray( $data );
	}

	/**
	 * Default WordPress HTTP transport.
	 *
	 * @return callable
	 *
	 * @phpstan-return callable(string, array<string,string>, string): array<string,mixed>
	 */
	private static function defaultTransport(): callable {
		return static function ( string $url, array $headers, string $body ): array {
			$response = CloudHttpOptions::safePost(
				$url,
				array(
					'timeout' => 5,
					'headers' => $headers,
					'body'    => $body,
				)
			);
			if ( is_wp_error( $response ) ) {
				throw new RuntimeException( 'affilun Cloud request failed.' );
			}

			$code = (int) wp_remote_retrieve_response_code( $response );
			$raw  = (string) wp_remote_retrieve_body( $response );
			$data = json_decode( $raw, true );

			return array(
				'status' => $code,
				'data'   => is_array( $data ) ? $data : null,
			);
		};
	}
}
