<?php
/**
 * What this site knows about its own receipt endpoint.
 *
 * @package TextTango
 */

declare( strict_types=1 );

namespace TextTango\Webhook;

use TextTango\Support\Clock;

defined( 'ABSPATH' ) || exit;

/**
 * Remembers whether the delivery-receipt loop has ever actually closed (spec §20).
 *
 * Registering a URL and holding a signing secret proves nothing on its own: the
 * URL can be wrong, the secret can be stale, a security plugin can be blocking
 * the REST route. The only fact worth showing a merchant is that a signed request
 * from TextTango **arrived here and verified**, so that is what is recorded — the
 * moment a test event landed, and the moment a real receipt did.
 *
 * Written by {@see Handler} at the receiving end rather than by whatever asked
 * for the test, because the whole point is to observe the far end of the loop
 * instead of trusting the near end.
 */
final class WebhookState {

	/**
	 * Option holding the state. Small, and never autoloaded with the settings.
	 */
	public const OPTION = 'texttango_webhook_state';

	/**
	 * The whole record, with every key present.
	 *
	 * @return array{registered_url: string, registered_at: int, last_test_at: int, last_receipt_at: int, rotated_at: int}
	 */
	public static function all(): array {
		$stored = get_option( self::OPTION, array() );

		return array_merge(
			array(
				'registered_url'  => '',
				'registered_at'   => 0,
				'last_test_at'    => 0,
				'last_receipt_at' => 0,
				'rotated_at'      => 0,
			),
			is_array( $stored ) ? $stored : array()
		);
	}

	/**
	 * One field.
	 *
	 * @param string $key Field name.
	 * @return mixed
	 */
	public static function get( string $key ) {
		return self::all()[ $key ] ?? null;
	}

	/**
	 * Merge in a change.
	 *
	 * @param array<string, mixed> $values Fields to set.
	 */
	public static function set( array $values ): void {
		update_option( self::OPTION, array_merge( self::all(), $values ), false );
	}

	/**
	 * Record that a signed event arrived and verified.
	 *
	 * @param string $kind Either 'test' or 'receipt'.
	 */
	public static function record_arrival( string $kind ): void {
		self::set(
			array(
				'test' === $kind ? 'last_test_at' : 'last_receipt_at' => Clock::now(),
			)
		);
	}

	/**
	 * Whether the loop has ever demonstrably closed.
	 *
	 * A real receipt counts as much as a test — better, in fact, since it means
	 * the thing the endpoint exists for is working.
	 */
	public static function is_verified(): bool {
		$state = self::all();

		return $state['last_test_at'] > 0 || $state['last_receipt_at'] > 0;
	}

	/**
	 * The most recent moment anything arrived, or 0.
	 */
	public static function last_arrival(): int {
		$state = self::all();

		return max( (int) $state['last_test_at'], (int) $state['last_receipt_at'] );
	}

	/**
	 * Forget everything, for when the endpoint is no longer the one registered.
	 */
	public static function clear(): void {
		delete_option( self::OPTION );
	}
}
