<?php

function {{phpPrefix}}_base64url_encode( $value ) {
	return rtrim( strtr( base64_encode( $value ), '+/', '-_' ), '=' );
}

function {{phpPrefix}}_base64url_decode( $value ) {
	if ( ! is_string( $value ) || '' === $value ) {
		return false;
	}

	$padding = strlen( $value ) % 4;
	if ( $padding > 0 ) {
		$value .= str_repeat( '=', 4 - $padding );
	}

	return base64_decode( strtr( $value, '-_', '+/' ), true );
}

function {{phpPrefix}}_get_public_write_action() {
	return '{{namespace}}/{{slugKebabCase}}/state/write';
}

function {{phpPrefix}}_get_public_write_client_subject() {
	$remote_addr = isset( $_SERVER['REMOTE_ADDR'] ) && is_string( $_SERVER['REMOTE_ADDR'] )
		? wp_unslash( $_SERVER['REMOTE_ADDR'] )
		: '';
	$user_agent  = isset( $_SERVER['HTTP_USER_AGENT'] ) && is_string( $_SERVER['HTTP_USER_AGENT'] )
		? wp_unslash( $_SERVER['HTTP_USER_AGENT'] )
		: '';

	return md5( $remote_addr . '|' . $user_agent );
}

function {{phpPrefix}}_get_public_write_rate_limit_key( $post_id, $resource_key ) {
	return '{{phpPrefix}}_public_write_rl_' . (int) $post_id . '_' . md5(
		(string) $resource_key . '|' . {{phpPrefix}}_get_public_write_client_subject()
	);
}

function {{phpPrefix}}_get_public_write_request_replay_key( $post_id, $resource_key, $request_id ) {
	return '{{phpPrefix}}_public_write_req_' . (int) $post_id . '_' . md5(
		(string) $resource_key . '|' . (string) $request_id
	);
}

function {{phpPrefix}}_get_public_write_lock_key( $post_id, $resource_key, $scope, $lock_subject = '' ) {
	$lock_subject = is_string( $lock_subject ) && '' !== $lock_subject
		? $lock_subject
		: {{phpPrefix}}_get_public_write_client_subject();

	return 'wpt_pwl_' . md5(
		'{{phpPrefix}}|' . (string) $scope . '|' . (int) $post_id . '|' . (string) $resource_key . '|' . $lock_subject
	);
}

function {{phpPrefix}}_with_public_write_lock( $post_id, $resource_key, $scope, $callback, $lock_subject = '' ) {
	global $wpdb;

	$lock_key = {{phpPrefix}}_get_public_write_lock_key( $post_id, $resource_key, $scope, $lock_subject );
	$acquired = (int) $wpdb->get_var(
		$wpdb->prepare(
			'SELECT GET_LOCK(%s, 5)',
			$lock_key
		)
	);

	if ( 1 === $acquired ) {
		try {
			return $callback();
		} finally {
			$wpdb->get_var(
				$wpdb->prepare(
					'SELECT RELEASE_LOCK(%s)',
					$lock_key
				)
			);
		}
	}

	return new WP_Error(
		'rest_temporarily_unavailable',
		'Could not acquire the public write lock.',
		array( 'status' => 503 )
	);
}

function {{phpPrefix}}_enforce_public_write_rate_limit( $post_id, $resource_key ) {
	return {{phpPrefix}}_with_public_write_lock(
		$post_id,
		$resource_key,
		'rate_limit',
		function () use ( $post_id, $resource_key ) {
			$key   = {{phpPrefix}}_get_public_write_rate_limit_key( $post_id, $resource_key );
			$count = (int) get_transient( $key );

			if ( $count >= 10 ) {
				return new WP_Error(
					'rest_rate_limited',
					'Too many public write attempts. Wait a minute and try again.',
					array( 'status' => 429 )
				);
			}

			set_transient( $key, $count + 1, MINUTE_IN_SECONDS );
			return true;
		}
	);
}

function {{phpPrefix}}_consume_public_write_request_id( $post_id, $resource_key, $request_id ) {
	if ( ! is_string( $request_id ) || '' === $request_id ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write request id is missing.',
			array( 'status' => 403 )
		);
	}

	return {{phpPrefix}}_with_public_write_lock(
		$post_id,
		$resource_key,
		'replay',
		function () use ( $post_id, $resource_key, $request_id ) {
			$key = {{phpPrefix}}_get_public_write_request_replay_key( $post_id, $resource_key, $request_id );
			if ( false !== get_transient( $key ) ) {
				return new WP_Error(
					'rest_conflict',
					'This public write request was already processed.',
					array( 'status' => 409 )
				);
			}

			set_transient( $key, 1, HOUR_IN_SECONDS );
			return true;
		},
		$request_id
	);
}

function {{phpPrefix}}_release_public_write_request_id( $post_id, $resource_key, $request_id ) {
	if ( ! is_string( $request_id ) || '' === $request_id ) {
		return;
	}

	delete_transient( {{phpPrefix}}_get_public_write_request_replay_key( $post_id, $resource_key, $request_id ) );
}

function {{phpPrefix}}_create_public_write_token( $post_id, $resource_key ) {
	$expires_at = time() + HOUR_IN_SECONDS;
	$payload    = array(
		'action'      => {{phpPrefix}}_get_public_write_action(),
		'exp'         => $expires_at,
		'postId'      => (int) $post_id,
		'resourceKey' => (string) $resource_key,
	);
	$json       = wp_json_encode( $payload );

	if ( ! is_string( $json ) || '' === $json ) {
		return array(
			'expiresAt' => $expires_at,
			'token'     => '',
		);
	}

	$payload_segment   = {{phpPrefix}}_base64url_encode( $json );
	$signature_segment = {{phpPrefix}}_base64url_encode(
		hash_hmac( 'sha256', $payload_segment, wp_salt( 'nonce' ), true )
	);

	return array(
		'expiresAt' => $expires_at,
		'token'     => $payload_segment . '.' . $signature_segment,
	);
}

function {{phpPrefix}}_verify_public_write_token( $token, $post_id, $resource_key ) {
	if ( ! is_string( $token ) || '' === $token ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token is missing.',
			array( 'status' => 403 )
		);
	}

	$segments = explode( '.', $token );
	if ( 2 !== count( $segments ) ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token format is invalid.',
			array( 'status' => 403 )
		);
	}

	list( $payload_segment, $signature_segment ) = $segments;
	$expected_signature = {{phpPrefix}}_base64url_encode(
		hash_hmac( 'sha256', $payload_segment, wp_salt( 'nonce' ), true )
	);

	if ( ! hash_equals( $expected_signature, $signature_segment ) ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token signature is invalid.',
			array( 'status' => 403 )
		);
	}

	$payload_json = {{phpPrefix}}_base64url_decode( $payload_segment );
	if ( false === $payload_json ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token payload is invalid.',
			array( 'status' => 403 )
		);
	}

	$payload = json_decode( $payload_json, true );
	if ( ! is_array( $payload ) ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token payload is invalid.',
			array( 'status' => 403 )
		);
	}

	if ( time() > (int) ( $payload['exp'] ?? 0 ) ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token has expired. Refresh write access and try again.',
			array( 'status' => 403 )
		);
	}

	if ( {{phpPrefix}}_get_public_write_action() !== (string) ( $payload['action'] ?? '' ) ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token action is invalid.',
			array( 'status' => 403 )
		);
	}

	if ( (int) ( $payload['postId'] ?? 0 ) !== (int) $post_id ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token is not valid for this post.',
			array( 'status' => 403 )
		);
	}

	if ( (string) ( $payload['resourceKey'] ?? '' ) !== (string) $resource_key ) {
		return new WP_Error(
			'rest_forbidden',
			'The public write token is not valid for this resource key.',
			array( 'status' => 403 )
		);
	}

	return true;
}

function {{phpPrefix}}_can_write_publicly( WP_REST_Request $request ) {
	$body = $request->get_json_params();
	$post_id = (int) ( is_array( $body ) ? ( $body['postId'] ?? 0 ) : 0 );
	$resource_key = (string) ( is_array( $body ) ? ( $body['resourceKey'] ?? '' ) : '' );
	$token = (string) ( is_array( $body ) ? ( $body['publicWriteToken'] ?? '' ) : '' );

	$rate_limit = {{phpPrefix}}_enforce_public_write_rate_limit( $post_id, $resource_key );
	if ( is_wp_error( $rate_limit ) ) {
		return $rate_limit;
	}

	return {{phpPrefix}}_verify_public_write_token( $token, $post_id, $resource_key );
}
