<?php
/**
 * Manage Rest API Authentication
 *
 * API key, secret create, invoke etc
 *
 * @package Tutor
 * @author Themeum <support@themeum.com>
 * @link https://themeum.com
 * @since 2.2.1
 */

namespace TUTOR;

use Tutor\Helpers\QueryHelper;

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

/**
 * Rest API authentication
 *
 * @since 2.2.1
 */
class RestAuth {

	/**
	 * Read Permissions
	 *
	 * @var string
	 */
	const READ = 'Read';

	/**
	 * Write Permissions
	 *
	 * @var string
	 */
	const WRITE = 'Write';

	/**
	 * Delete Permissions
	 *
	 * @var string
	 */
	const DELETE = 'Delete';

	/**
	 * Read Write Permissions
	 *
	 * @var string
	 */
	const READ_WRITE = 'Read/Write';

	/**
	 * All Permissions
	 *
	 * @var string
	 */
	const ALL = 'All';

	/**
	 * User meta key to store key, secret, permission info
	 *
	 * @var string
	 */
	const KEYS_USER_META_KEY = 'tutor-api-key-secret';

	/**
	 * Register hooks.
	 *
	 * @since 2.2.1
	 *
	 * @return void
	 */
	public function __construct() {
		add_action( 'wp_ajax_tutor_generate_api_keys', __CLASS__ . '::generate_api_keys' );
		add_action( 'wp_ajax_tutor_update_api_permission', __CLASS__ . '::update_api_permission' );
		add_action( 'wp_ajax_tutor_revoke_api_keys', __CLASS__ . '::revoke_api_keys' );
		add_filter( 'determine_current_user', array( $this, 'api_auth' ) );
	}

	/**
	 * API auth.
	 *
	 * @since 2.7.1
	 *
	 * @param int|false $user_id user id.
	 *
	 * @return int|false
	 */
	public function api_auth( $user_id ) {
		// Don't authenticate twice.
		if ( ! empty( $user_id ) || ! $this->is_tutor_api_request() ) {
			return $user_id;
		}

		if ( ! wp_is_application_passwords_available() ) {
			return $user_id;
		}

		if ( ! isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) ) {
			return $user_id;
		}

		$api_key    = $_SERVER['PHP_AUTH_USER']; //phpcs:ignore sanitization ok
		$api_secret = $_SERVER['PHP_AUTH_PW']; //phpcs:ignore sanitization ok
		$record     = self::validate_api_key_secret( $api_key, $api_secret, true );
		if ( $record ) {
			return $record->user_id;
		}

		return $user_id;
	}

	/**
	 * Is request is tutor rest api.
	 *
	 * @since 2.7.1
	 *
	 * @return boolean
	 */
	public static function is_tutor_api_request() {
		$rest_prefix = trailingslashit( rest_get_url_prefix() );
		$request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) );

		$is_tutor_api = ( false !== strpos( $request_uri, $rest_prefix . 'tutor/' ) );

		return $is_tutor_api;
	}

	/**
	 * Generate api keys
	 *
	 * @since 2.2.1
	 *
	 * @return void send wp_json response
	 */
	public static function generate_api_keys() {
		// Validate nonce.
		tutor_utils()->checking_nonce();

		// Check user permission.
		if ( ! current_user_can( 'administrator' ) ) {
			wp_send_json_error( tutor_utils()->error_message() );
		}

		$api_key    = 'key_' . bin2hex( random_bytes( 16 ) );
		$api_secret = 'secret_' . bin2hex( random_bytes( 32 ) );

		$permission  = Input::post( 'permission' );
		$description = Input::post( 'description', '', Input::TYPE_TEXTAREA );

		$info = wp_json_encode(
			array(
				'key'         => $api_key,
				'secret'      => $api_secret,
				'permission'  => $permission,
				'description' => $description,
			)
		);

		// Update user meta.
		$add = add_user_meta(
			get_current_user_id(),
			self::KEYS_USER_META_KEY,
			$info
		);

		if ( $add ) {
			$response = self::prepare_response( $add, $api_key, $api_secret, $permission, $description );
			wp_send_json_success( $response );
		} else {
			wp_send_json_error( tutor_utils()->error_message( '0' ) );
		}

	}


	/**
	 * Update api permission
	 *
	 * @since 2.5.0
	 *
	 * @return void send wp_json response
	 */
	public static function update_api_permission() {
		global $wpdb;

		// Validate nonce.
		tutor_utils()->checking_nonce();

		// Check user permission.
		if ( ! current_user_can( 'administrator' ) ) {
			wp_send_json_error( tutor_utils()->error_message() );
		}

		$meta_id     = Input::post( 'meta_id', 0, Input::TYPE_INT );
		$permission  = Input::post( 'permission' );
		$description = Input::post( 'description', '', Input::TYPE_TEXTAREA );

		$info       = QueryHelper::get_row( $wpdb->usermeta, array( 'umeta_id' => $meta_id ), 'umeta_id' );
		$meta_value = json_decode( $info->meta_value );

		$meta_value->permission  = $permission;
		$meta_value->description = $description;

		// Update user meta.
		try {
			QueryHelper::update(
				$wpdb->usermeta,
				array( 'meta_value' => json_encode( $meta_value ) ),
				array( 'umeta_id' => $meta_id )
			);

			$response = self::prepare_response( $meta_id, $meta_value->key, $meta_value->secret, $permission, $description );
			wp_send_json_success( $response );

		} catch ( \Throwable $th ) {
			wp_send_json_error( $th->getMessage() );
		}
	}

	/**
	 * Revoke api keys
	 *
	 * @since 2.2.1
	 *
	 * @return void send wp_json response
	 */
	public static function revoke_api_keys() {
		// Validate nonce.
		tutor_utils()->checking_nonce();

		// Check user permission.
		if ( ! current_user_can( 'administrator' ) ) {
			wp_send_json_error( tutor_utils()->error_message() );
		}

		$meta_id = Input::post( 'meta_id', 0, Input::TYPE_INT );

		if ( ! $meta_id ) {
			wp_send_json_error( __( 'Invalid meta id', 'tutor' ) );
		}

		// Delete api keys.
		global $wpdb;
		$delete = QueryHelper::delete( $wpdb->usermeta, array( 'umeta_id' => $meta_id ) );

		if ( $delete ) {
			wp_send_json_success( __( 'API keys permanently revoked', 'tutor' ) );
		} else {
			wp_send_json_error( __( 'API keys revoke failed, please try again.', 'tutor' ) );
		}
	}

	/**
	 * Check if api key & secret is valid
	 *
	 * @since 2.2.1
	 * @since 2.7.1 $return_result param added.
	 *
	 * @param string $api_key api key.
	 * @param string $api_secret api secret.
	 * @param bool   $return_result return matched meta record.
	 *
	 * @return bool|object
	 */
	public static function validate_api_key_secret( $api_key, $api_secret, $return_result = false ) {
		global $wpdb;
		$table = $wpdb->usermeta;

		$valid = false;

		$results = QueryHelper::get_all(
			$table,
			array( 'meta_key' => self::KEYS_USER_META_KEY ), //phpcs:ignore
			'umeta_id'
		);

		if ( is_array( $results ) && count( $results ) ) {
			foreach ( $results as $result ) {
				$obj = json_decode( $result->meta_value );
				if ( $obj->key === $api_key && $obj->secret === $api_secret ) {
					$valid = true;
					if ( $return_result ) {
						return $result;
					}
					break;
				}
			}
		}

		return $valid;
	}

	/**
	 * Process api request
	 *
	 * @since 2.2.1
	 *
	 * @return boolean
	 */
	public static function process_api_request() {
		$headers = apache_request_headers();

		if ( isset( $headers['Authorization'] ) ) {
			$authorization_header = $headers['Authorization'];

			if ( strpos( $authorization_header, 'Basic' ) !== false ) {
				$base_64_credentials = str_replace( 'Basic ', '', $authorization_header );
				$credentials         = base64_decode( $base_64_credentials ); //phpcs:ignore

				list($api_key, $api_secret) = explode( ':', $credentials );

				if ( self::validate_api_key_secret( $api_key, $api_secret ) ) {
					return true;
				}
			}
		}

		// Key and secret are invalid or not provided.
		return false;
	}

	/**
	 * Prepare html response
	 *
	 * @since 2.2.1
	 *
	 * @param int    $meta_id meta id.
	 * @param string $key api key.
	 * @param string $secret api secret.
	 * @param string $permission authorization permission.
	 * @param string $description description.
	 *
	 * @return string
	 */
	public static function prepare_response( $meta_id, $key, $secret, $permission, $description = '' ) {
		$user_id = get_current_user_id();
		ob_start();
		?>
		<tr id="<?php echo esc_attr( $meta_id ); ?>">
			<td>
				<?php echo esc_html( tutor_utils()->display_name( $user_id ) ); ?>
			</td>
			<td>
				<a class="tutor-btn tutor-btn-outline-primary tutor-btn-sm">
					<span class="tutor-icon-copy tutor-mr-8"></span>
					<span class="tutor-copy-text" data-text="<?php echo esc_attr( $key ); ?>">
						<?php echo esc_html( substr( $key, 0, 5 ) . '...' ); ?>
					</span>
				</a>
			</td>
			<td>
				<a class="tutor-btn tutor-btn-outline-primary tutor-btn-sm">
					<span class="tutor-icon-copy tutor-mr-8"></span>
					<span class="tutor-copy-text" data-text="<?php echo esc_attr( $secret ); ?>">
						<?php echo esc_html( substr( $secret, 0, 9 ) . '...' ); ?>
					</span>
				</a>
			</td>
			<td>
				<?php echo esc_html( $permission ); ?>
				<?php if ( ! empty( $description ) ) : ?>
				<div class="tooltip-wrap tooltip-icon-custom" >
					<i class="tutor-fs-7 tutor-icon-circle-info-o tutor-color-muted tutor-ml-4"></i>
					<span class="tooltip-txt tooltip-bottom">
						<?php echo esc_textarea( $description ); ?>
					</span>
				</div>
				<?php endif; ?>
			</td>
			<td>
				<div class="tutor-dropdown-parent">
					<button type="button" class="tutor-iconic-btn" action-tutor-dropdown="toggle">
						<span class="tutor-icon-kebab-menu" aria-hidden="true"></span>
					</button>
					<div class="tutor-dropdown tutor-dropdown-dark tutor-text-left">
						<a href="javascript:void(0)" class="tutor-dropdown-item" data-tutor-modal-target="tutor-update-permission-modal" data-update-id="<?php echo esc_attr( $meta_id ); ?>" data-permission="<?php echo esc_attr( $permission ); ?>" data-description="<?php echo esc_attr( $description ); ?>">
							<i class="tutor-icon-edit tutor-mr-8" aria-hidden="true" data-update-id="<?php echo esc_attr( $meta_id ); ?>" data-permission="<?php echo esc_attr( $permission ); ?>" data-description="<?php echo esc_attr( $description ); ?>"></i>
							<span data-update-id="<?php echo esc_attr( $meta_id ); ?>" data-permission="<?php echo esc_attr( $permission ); ?>" data-description="<?php echo esc_attr( $description ); ?>"><?php esc_html_e( 'Edit', 'tutor' ); ?></span>
						</a>
						<a href="javascript:void(0)" class="tutor-dropdown-item" data-meta-id="<?php echo esc_attr( $meta_id ); ?>">
							<i class="tutor-icon-trash-can-bold tutor-mr-8" aria-hidden="true" data-meta-id="<?php echo esc_attr( $meta_id ); ?>"></i>
							<span data-meta-id="<?php echo esc_attr( $meta_id ); ?>"><?php esc_html_e( 'Revoke', 'tutor' ); ?></span>
						</a>
					</div>
				</div>
			</td>
		</tr>
		<?php
		return ob_get_clean();
	}

	/**
	 * Get available permission
	 *
	 * @since 2.2.1
	 *
	 * @return array
	 */
	public static function available_permissions(): array {
		$permissions = array(
			array(
				'value' => self::READ,
				'label' => __( 'Read', 'tutor' ),
			),
		);
		return apply_filters( 'tutor_rest_api_permissions', $permissions );
	}
}
