<?php
/**
 * Signed OAuth return state (no DB transients; survives external redirects).
 *
 * @package SHC4WC
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class SHC4WC_OAuth_Return_State {
	const VERSION     = 'v1';
	const MAX_AGE_SEC = 1200;

	/**
	 * @return string Signed return token for shc4wc_oauth_state query arg.
	 */
	public static function create() {
		$ts      = time();
		$rand    = wp_generate_password( 16, false, false );
		$payload = self::VERSION . '|' . $ts . '|' . $rand;
		$sig     = hash_hmac( 'sha256', $payload, self::secret() );
		return self::encode_payload( $payload ) . '.' . $sig;
	}

	/**
	 * @param string $state Raw query value.
	 * @return bool
	 */
	public static function verify( $state ) {
		$state = self::sanitize( $state );
		if ( '' === $state ) {
			return false;
		}

		if ( false !== strpos( $state, '.' ) ) {
			return self::verify_signed( $state );
		}

		return self::verify_legacy_transient( $state );
	}

	/**
	 * @param string $state Untrusted query value.
	 * @return string
	 */
	public static function sanitize( $state ) {
		$state = is_string( $state ) ? wp_unslash( $state ) : '';
		$state = rawurldecode( $state );
		return preg_replace( '/[^a-zA-Z0-9._=-]/', '', (string) $state );
	}

	/**
	 * @param string $session_id Square Worker session id.
	 * @return bool
	 */
	public static function is_valid_session_id( $session_id ) {
		$session_id = sanitize_text_field( (string) $session_id );
		if ( '' === $session_id || strlen( $session_id ) > 128 ) {
			return false;
		}
		return (bool) preg_match( '/^[a-zA-Z0-9_-]+$/', $session_id );
	}

	/**
	 * @return string
	 */
	public static function secret() {
		return wp_salt( 'auth' ) . '|shc4wc_oauth_return';
	}

	/**
	 * @param string $payload Pipe-delimited payload.
	 * @return string
	 */
	private static function encode_payload( $payload ) {
		return rtrim( strtr( base64_encode( $payload ), '+/', '-_' ), '=' );
	}

	/**
	 * @param string $encoded Base64url payload segment.
	 * @return string|false
	 */
	private static function decode_payload( $encoded ) {
		$b64 = strtr( $encoded, '-_', '+/' );
		$pad = strlen( $b64 ) % 4;
		if ( $pad > 0 ) {
			$b64 .= str_repeat( '=', 4 - $pad );
		}
		return base64_decode( $b64, true );
	}

	/**
	 * @param string $state token.sig
	 * @return bool
	 */
	private static function verify_signed( $state ) {
		$parts = explode( '.', $state, 2 );
		if ( 2 !== count( $parts ) ) {
			return false;
		}

		list( $encoded, $sig ) = $parts;
		$payload               = self::decode_payload( $encoded );
		if ( ! is_string( $payload ) || '' === $payload ) {
			return false;
		}

		$expected = hash_hmac( 'sha256', $payload, self::secret() );
		if ( ! hash_equals( $expected, $sig ) ) {
			return false;
		}

		$chunks = explode( '|', $payload );
		if ( 3 !== count( $chunks ) || self::VERSION !== $chunks[0] ) {
			return false;
		}

		$ts = (int) $chunks[1];
		if ( $ts <= 0 || abs( time() - $ts ) > self::MAX_AGE_SEC ) {
			return false;
		}

		return true;
	}

	/**
	 * @param string $state Legacy random token stored in a transient.
	 * @return bool
	 */
	private static function verify_legacy_transient( $state ) {
		if ( strlen( $state ) > 64 ) {
			return false;
		}
		$key  = 'shc4wc_oauth_wr_' . $state;
		$data = get_transient( $key );
		if ( ! is_array( $data ) ) {
			return false;
		}
		delete_transient( $key );
		return true;
	}
}
