<?php
/**
 * Fast Events SaaS controller.
 *
 * @since 1.3
 */

declare( strict_types=1 );
namespace FeData\FastEvents\RestAPI;

defined( 'ABSPATH' ) || exit;

use FeData\FastEvents\Payment\PaymentFactory;
use FeData\FastEvents\Storage\Options;
use FeData\FastEvents\Storage\PluginSettings;
use FeData\FastEvents\Storage\UserMeta;
use JetBrains\PhpStorm\NoReturn;
use stdClass;
use WP_Error;
use WP_HTTP_Response;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_User;

/**
 * Fast Events saas class.
 */
class SaasController extends WP_REST_Controller {

	/**
	 * Current user object
	 *
	 * @since 1.4
	 * @var ?WP_User
	 */
	private ?WP_User $current_user;

	/**
	 * Fast Events saas constructor.
	 *
	 * @param PluginSettings $settings  Settings of this plugin.
	 * @param Options        $options   Options storage.
	 * @param PaymentFactory $payment   Payment factory.
	 * @param UserMeta       $user_meta User meta storage.
	 *
	 * @since 1.3
	 */
	public function __construct(
		private readonly PluginSettings $settings,
		private readonly Options $options,
		private readonly PaymentFactory $payment,
		private readonly UserMeta $user_meta,
	) {}


	/**
	 * Register the routes for the objects of the controller.
	 *
	 * @since 1.3
	 */
	public function register_routes(): void {

		if ( ! $this->settings->saas_mode() ) {
			return;
		}

		// Request authorization for the SaaS App.
		register_rest_route(
			'fast-events',
			'/v1/saas/request_authorization',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'request_authorisation' ),
				'permission_callback' => function () {
					return $this->check_user();
				},
			)
		);

		// Revoke authorization for the SaaS App.
		register_rest_route(
			'fast-events',
			'/v1/saas/revoke',
			array(
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'revoke_authorisation' ),
				'permission_callback' => function () {
					return $this->check_user();
				},
			)
		);

		// Admin user revokes authorization for the SaaS App.
		register_rest_route(
			'fast-events',
			'/v1/admin/saas/info/revoke/(?P<id>\d+)',
			array(
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'admin_revoke_authorisation' ),
				'permission_callback' => function () {
					return in_array( 'administrator', wp_get_current_user()->roles, true );
				},
			)
		);

		// Receive an authorization result from the provider.
		register_rest_route(
			'fast-events',
			'/v1/saas/authorize',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'receive_authorisation' ),
				'permission_callback' => '__return_true',
				'args'                => array(
					'state'             => array(
						'required'          => true,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'code'              => array(
						'required'          => false,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'error'             => array(
						'required'          => false,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
					'error_description' => array(
						'required'          => false,
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
			)
		);

		// Revoke authorization for the SaaS App.
		register_rest_route(
			'fast-events',
			'/v1/admin/saas/info/(?P<id>\d+)',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'info_block' ),
				'permission_callback' => function () {
					return $this->check_user();
				},
			)
		);
	}


	/**
	 * Ask the user to authorize this SaaS App.
	 *
	 * @since 1.4
	 */
	public function request_authorisation(): WP_Error|WP_REST_Response|WP_HTTP_Response {

		// No user?
		if ( ! $this->check_user() ) {
			return new WP_Error( 'no_user', esc_html__( 'No valid user found', 'fast-events' ), array( 'status' => 401 ) );
		}

		// Redirect the client.
		return rest_ensure_response( array( 'redirect' => $this->payment->get_oauth_url( $this->current_user ) ) );
	}


	/**
	 * Revoke authorization from this SaaS App.
	 *
	 * @since 1.4
	 * @since 2.5.0 Now using 'current_user_can'.
	 *
	 * @return WP_Error|WP_REST_Response
	 */
	public function revoke_authorisation(): WP_Error|WP_REST_Response {

		// No user? Or is this an admin user?
		if ( ! $this->check_user() || current_user_can( 'manage_options' ) ) {
			return new WP_Error( 'no_user', esc_html__( 'No valid user found', 'fast-events' ), array( 'status' => 401 ) );
		}

		// Get user SaaS info.
		$response = $this->payment->revoke_authorization( $this->current_user->ID );
		$this->delete_saas_data( $this->current_user->ID );
		if ( is_wp_error( $response ) ) {
			return $response;
		}

		return rest_ensure_response( 'Ok' );
	}


	/**
	 * Admin revokes authorization from this SaaS App.
	 *
	 * @since 1.4
	 *
	 * @param WP_REST_Request $data Request instance.
	 *
	 * @return WP_Error|WP_REST_Response
	 */
	public function admin_revoke_authorisation( WP_REST_Request $data ): WP_Error|WP_REST_Response {
		$user_id = absint( $data['id'] );
		wp_set_current_user( $user_id );

		// No user?
		if ( ! $this->check_user() ) {
			return new WP_Error( 'no_user', esc_html__( 'No valid user found', 'fast-events' ), array( 'status' => 401 ) );
		}

		// Delete the SaaS info.
		$this->payment->revoke_authorization( $user_id );
		$this->delete_saas_data( $user_id );

		return rest_ensure_response( 'Ok' );
	}


	/**
	 * Response from the authorisation request for this SaaS App.
	 *
	 * @since 1.4
	 * @since 1.9.3 Always select the first profile
	 * @since 2.4.0 Get User-Agent from utility
	 * @since 2.5.0 Optionally encrypt the merchant name and profile ID.
	 *
	 * @param WP_REST_Request $data Request instance.
	 */
	#[NoReturn] public function receive_authorisation( WP_REST_Request $data ): void {

		$params   = $data->get_params();
		$redirect = 'Location: ' . home_url() . '/wp-admin/admin.php?page=fast_events';

		// Check if there was an error.
		if ( isset( $params['error_description'] ) ) {
			$this->options->save_saas_error( $params['error_description'] );
			header( $redirect );
			die();

		}

		// Check state parameter.
		$parts = explode( ':', $params['state'] );
		if ( 2 !== count( $parts ) || ! filter_var( $parts[1], FILTER_VALIDATE_INT ) ) {
			$this->options->save_saas_error( esc_html__( 'No valid user ID found', 'fast-events' ) );
			header( $redirect );
			die();
		}

		// Exchange the auth-code for an access token.
		$user     = wp_set_current_user( absint( $parts[1] ) );
		$response = $this->payment->get_access_token_from_auth_token( $user->ID, $parts[0], $params );
		if ( is_wp_error( $response ) ) {
			$this->options->save_saas_error( $response->get_error_message() );
			$this->delete_saas_data( $user->ID );
			header( $redirect );
			die();
		}

		header( $redirect );
		die();
	}


	/**
	 * Generate info for the SaaS info block, which is part of editing events.
	 * Only admins will see this block and can revoke authorizations.
	 *
	 * @since 1.4
	 * @since 2.5.0 Now using 'current_user_can'.
	 *
	 * @param WP_REST_Request $data Request instance.
	 *
	 * @return WP_REST_Response The info structure 'admin_user: true/false', 'authorized: true/false' and 'merchant: string'.
	 */
	public function info_block( WP_REST_Request $data ): WP_REST_Response {
		$user_id = absint( $data['id'] );

		$result             = new stdClass();
		$result->admin      = false;
		$result->authorized = false;
		$result->merchant   = '';

		// Only admins are allowed and non-zero users.
		if ( ! current_user_can( 'manage_options' ) || 0 === $user_id ) {
			$result->admin = true;
			return rest_ensure_response( $result );
		}

		// Is the id an admin user?
		$user = wp_set_current_user( $user_id );
		if ( $user->has_cap( 'administrator' ) ) {
			$result->admin = true;
			return rest_ensure_response( $result );
		}

		// Look up the merchant name.
		$name = $this->user_meta->get_saas_merchant_name( $user->ID );
		if ( ! empty( $name ) ) {
			$result->authorized = true;
			$result->merchant   = $name;
		}
		return rest_ensure_response( $result );
	}


	/**
	 * Get and check the current user
	 *
	 * @since 1.4
	 */
	private function check_user(): bool {

		$this->current_user = wp_get_current_user();

		// No user?
		if ( ! $this->current_user->exists() ) {
			return false;
		} else {
			return true;
		}
	}


	/**
	 * Delete SaaS meta data for a specific user
	 *
	 * @since 1.4
	 *
	 * @param int $user_id The ID of the user.
	 */
	private function delete_saas_data( int $user_id ): void {
		$this->user_meta->delete_saas_config( $user_id );
		$this->user_meta->delete_saas_profile_id( $user_id );
		$this->user_meta->delete_saas_merchant_name( $user_id );
	}
}
