<?php
/**
 * RestController.
 * php version 5.6
 *
 * @category RestController
 * @package  SureTriggers
 * @author   BSF <username@example.com>
 * @license  https://www.gnu.org/licenses/gpl-3.0.html GPLv3
 * @link     https://www.brainstormforce.com/
 * @since    1.0.0
 */

namespace SureTriggers\Controllers;

use Exception;
use SureTriggers\Integrations\WordPress\WordPress;
use SureTriggers\Traits\SingletonLoader;
use SureTriggers\Models\SaasApiToken;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use Throwable;
use RuntimeException;
use InvalidArgumentException;

/**
 * RestController
 *
 * @category RestController
 * @package  SureTriggers
 * @author   BSF <username@example.com>
 * @license  https://www.gnu.org/licenses/gpl-3.0.html GPLv3
 * @link     https://www.brainstormforce.com/
 * @since    1.0.0
 */
class RestController {

	/**
	 * Access token for authentication.
	 *
	 * @var string $acccess_token
	 */
	private $secret_key;

	use SingletonLoader;

	/**
	 * Initialize data.
	 */
	public function __construct() {
		$this->secret_key = SaasApiToken::get();
		add_filter( 'determine_current_user', [ $this, 'basic_auth_handler' ], 20 );
		add_filter( 'debug_information', [ $this, 'sure_triggers_connection_info' ] );
	}

	/**
	 * Permission callback for rest api after determination of current user.
	 *
	 * @param WP_REST_Request $request Request.
	 * 
	 * @return bool
	 */
	public function autheticate_user( $request ) {
		$secret_key = $request->get_header( 'st_authorization' );

		if ( ! is_string( $secret_key ) || empty( $secret_key ) || empty( $this->secret_key ) ) { 
			return false;
		}

		$parsed = sscanf( $secret_key, 'Bearer %s' );
		if ( is_array( $parsed ) ) {
			list( $secret_key ) = $parsed;
		}

		if ( empty( $secret_key ) ) { 
			return false;
		}

		if ( $this->secret_key !== $secret_key ) {
			return false;
		}

		return hash_equals( $this->secret_key, $secret_key );
	}

	/**
	 * Create WP Connection.
	 * 
	 * @param WP_REST_Request $request Request data.
	 * @return WP_REST_Response
	 */
	public function create_wp_connection( $request ) {

		$user_agent     = $request->get_header( 'user-agent' );
		$allowed_agents = [ 'OttoKit', 'SureTriggers' ];
		if ( ! in_array( $user_agent, $allowed_agents, true ) ) {
			return new WP_REST_Response(
				[
					'success' => false,
					'data'    => 'Unauthorized',
				],
				403
			);
		}
		$params = wp_unslash( $request->get_json_params() );

		$username = isset( $params['wp-username'] ) ? sanitize_user( $params['wp-username'] ) : '';
		$password = isset( $params['wp-password'] ) ? $params['wp-password'] : '';

		if ( empty( $username ) || empty( $password ) ) {
			return new WP_REST_Response(
				[
					'success' => false,
					'data'    => 'Authentication failed.',
				],
				401
			);
		}

		$user = wp_authenticate_application_password( null, $username, $password );

		if ( ! ( $user instanceof \WP_User ) ) {
			return new WP_REST_Response(
				[
					'success' => false,
					'data'    => 'Authentication failed.',
				],
				403
			);
		}

		if ( ! user_can( $user, 'administrator' ) ) {
			return new WP_REST_Response(
				[
					'success' => false,
					'data'    => 'Not allowed to perform this action.',
				],
				403
			);
		}

		$connection_status  = isset( $params['connection-status'] ) ? sanitize_text_field( $params['connection-status'] ) : false;
		$access_key         = isset( $params['sure-triggers-access-key'] ) ? sanitize_text_field( $params['sure-triggers-access-key'] ) : '';
		$connected_email_id = isset( $params['connected_email'] ) ? sanitize_email( $params['connected_email'] ) : '';

		if ( empty( $connection_status ) ) {
			return new WP_REST_Response(
				[
					'success' => false,
					'data'    => 'Connection denied.',
				],
				403
			);
		}

		if ( empty( $access_key ) ) {
			return new WP_REST_Response(
				[
					'success' => false,
					'data'    => 'Invalid access key.',
				],
				403
			);
		}       

		$response = self::verify_user_token( $access_key );
		if ( empty( $response ) || is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
			return new WP_REST_Response(
				[
					'success' => false,
					'data'    => 'Verification failed.',
				],
				403
			);
		}
		
		SaasApiToken::save( $access_key );
		OptionController::set_option( 'connected_email_key', $connected_email_id );

		return new WP_REST_Response(
			[
				'success' => true,
				'data'    => 'Connected successfully.',
			],
			200
		);
	}

	/**
	 * Verify user token.
	 * 
	 * @param string $token Token.
	 * 
	 * @return  array|WP_Error $response Response.
	 */
	public static function verify_user_token( $token = '' ) {
		if ( empty( $token ) ) {
			$token = SaasApiToken::get();
		}
		$args     = [
			'body'    => [
				'token'      => $token,
				'saas-token' => $token,
				'base_url'   => str_replace( '/wp-json/', '', get_rest_url() ),
			],
			'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
		];
		$response = wp_remote_post( SURE_TRIGGERS_API_SERVER_URL . '/token/verify', $args );
		if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
			$response_body = wp_remote_retrieve_body( $response );
			$data          = json_decode( $response_body, true );
			if ( is_array( $data ) && isset( $data['plan_id'] ) ) {
				// Save plan_id to database.
				$plan_data = [
					'plan_id' => sanitize_text_field( $data['plan_id'] ),
				];
				
				update_option( 'suretriggers_lifetime_user_plan_data', $plan_data );
			}
		}
		
		return $response;
	}

	/**
	 * Verify connection.
	 * 
	 * @return  array|WP_Error $response Response.
	 */
	public static function suretriggers_verify_wp_connection() {
		$args     = [
			'body'    => [
				'saas-token'     => SaasApiToken::get(),
				'base_url'       => str_replace( '/wp-json/', '', get_rest_url() ),
				'plugin_version' => SURE_TRIGGERS_VER,
			],
			'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
		];
		$response = wp_remote_post( SURE_TRIGGERS_API_SERVER_URL . '/connection/wordpress/ping', $args );
		return $response;
	}

	/**
	 * Authenticate User for API calls.
	 *
	 * @param array|object $user USer.
	 *
	 * @return int|null|WP_Error|array|object
	 */
	public function basic_auth_handler( $user ) {
		// Don't authenticate twice.
		if ( ! empty( $user ) ) {
			return $user;
		}

		if ( ! is_ssl() ) {
			return new WP_Error( 'insecure_connection', 'Use a secure HTTPS connection to access this resource.', [ 'status' => 403 ] );
		}       

		// Check that we're trying to authenticate.
		if ( ! isset( $_SERVER['PHP_AUTH_USER'] ) || ! isset( $_SERVER['PHP_AUTH_PW'] ) ) { //phpcs:ignore
			return $user;
		}

		$username = sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) ); //phpcs:ignore
		$password = sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); //phpcs:ignore

		/**
		 * In multi-site, wp_authenticate_spam_check filter is run on authentication. This filter calls.
		 * get_currentuserinfo which in turn calls the determine_current_user filter. This leads to infinite.
		 * recursion and a stack overflow unless the current function is removed from the determine_current_user.
		 * filter during authentication.
		 */
		remove_filter( 'determine_current_user', [ $this, 'basic_auth_handler' ], 20 );

		$user = wp_authenticate( $username, $password );

		add_filter( 'determine_current_user', [ $this, 'basic_auth_handler' ], 20 );

		if ( is_wp_error( $user ) ) {
			return null;
		}

		return $user->ID;
	}

	/**
	 * Authenticate user for new connection create api.
	 *
	 * @return bool
	 */
	public function is_current_user() {
		if ( current_user_can( 'manage_options' ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Execute action events.
	 *
	 * @param WP_REST_Request $request Request data.
	 * @return WP_REST_Response|object
	 */
	public function run_action( $request ) {
		$request->get_param( 'wp_user_id' );

		$user_id          = $request->get_param( 'wp_user_id' );
		$automation_id    = $request->get_param( 'automation_id' );
		$integration      = $request->get_param( 'integration' );
		$action_type      = $request->get_param( 'type_event' );
		$selected_options = $request->get_param( 'selected_options' );
		$context          = $request->get_param( 'context' );
		$fields           = $request->get_param( 'fields' );

		if ( empty( $user_id ) ) {
			$user_id = isset( $context['pluggable_data']['wp_user_id'] ) ? sanitize_text_field( $context['pluggable_data']['wp_user_id'] ) : '';
		}

		if ( empty( $integration ) || empty( $action_type ) ) {
			return self::error_message( 'Integration or action type is missing' );
		}

		if ( isset( $selected_options['wp_user_email'] ) && ! ( 'EDD' === $integration && 'find_user_purchased_download' === $action_type ) ) {
			$is_valid = WordPress::validate_email( $selected_options['wp_user_email'] );

			if ( ! is_object( $is_valid ) || ! property_exists( $is_valid, 'valid' ) || ! property_exists( $is_valid, 'multiple' ) ) {
				return self::error_message( 'Invalid email validation response.' );
			}

			if ( ! $is_valid->valid ) {
				if ( $is_valid->multiple ) {
					return self::error_message( 'One or more email address is not valid.' );
				} else {
					return self::error_message( 'Email address is not valid.' );
				}
			}

			if ( str_contains( $selected_options['wp_user_email'], ',' ) ) {
				$email_list = explode( ',', $selected_options['wp_user_email'] );

				foreach ( $email_list as $single_email ) {
					if ( ! email_exists( trim( $single_email ) ) ) {
						return self::error_message( 'User with email ' . $single_email . ' does not exists.' );
					}
				}
			} else {
				if ( ! email_exists( $selected_options['wp_user_email'] ) ) {
					return self::error_message( 'User with email ' . $selected_options['wp_user_email'] . ' does not exists.' );
				}
			}
		}
		$registered_actions = EventController::get_instance()->actions;
		$action_event       = $registered_actions[ $integration ][ $action_type ];

		$fully_qualified_class_name = "\SureTriggers\Integrations\\$integration\\$integration";

		$fun_params = [
			$user_id,
			$automation_id,
			$fields,
			$selected_options,
			$context,
		];

		try {
			// Check if integration class exists and plugin is active.
			if ( class_exists( $fully_qualified_class_name ) ) {
				$class_obj        = new $fully_qualified_class_name();
				$is_plugin_active = false;
				if ( method_exists( $class_obj, 'is_plugin_installed' ) ) {
					$is_plugin_active = $class_obj->is_plugin_installed();
				}
				if ( ! $is_plugin_active ) {
					return self::error_message( $integration . ' plugin is not installed or activated.', 400 );
				}
			} else {
				return self::error_message( 'Integration class not found.', 400 );
			}
			
			// Execute the action with error handling.
			$result = null;
			try {
				$result = call_user_func_array(
					$action_event['function'],
					$fun_params
				);
				
				return self::success_message( (array) $result );
			} catch ( InvalidArgumentException $arg_error ) {
				return self::error_message( 'Invalid argument: ' . $arg_error->getMessage(), 400 );
			} catch ( RuntimeException $runtime_error ) {
				return self::error_message( 'Runtime error: ' . $runtime_error->getMessage(), 500 );
			} catch ( Exception $action_error ) {
				return self::error_message( 'Action execution failed: ' . $action_error->getMessage(), 400 );
			} catch ( Throwable $php_error ) {
				return self::error_message( 'PHP error in action: ' . $php_error->getMessage(), 500 );
			}
		} catch ( Exception $e ) {
			return self::error_message( 'Error executing action: ' . $e->getMessage(), 400 );
		}
	}

	/**
	 * Error message format.
	 *
	 * @param string $message Error message.
	 * @param int    $status Error message.
	 *
	 * @return object
	 */
	public static function error_message( $message, $status = 401 ) {
		return new WP_REST_Response(
			[
				'success' => false,
				'data'    => [
					'errors' => $message,
				],
			],
			$status
		);
	}

	/**
	 * Success message format.
	 *
	 * @param array $data response data to be sent.
	 *
	 * @return object
	 */
	public static function success_message( $data = [] ) {
		$result = [];

		if ( ! empty( $data ) ) {
			$result['result'] = $data;
		}

		return new WP_REST_Response(
			[
				'success' => true,
				'data'    => $result,
			],
			200
		);

	}

	/**
	 * Add/Remove/Update the triggers..
	 * When new/update/remove automation on Sass then execute this endpoint to update the automation.
	 *
	 * @param WP_REST_Request $request Request data.
	 * @return object
	 */
	public function manage_triggers( $request ) {
		$events = $request->get_param( 'events' ) ? json_decode( stripslashes( $request->get_param( 'events' ) ), true ) : [];

		// Selected field data from the trigger.
		$data = $request->get_param( 'data' ) ? json_decode( stripslashes( $request->get_param( 'data' ) ), true ) : [];

		// Get the trigger data from the option and append data in trigger data option.
		$trigger_data = OptionController::get_option( 'trigger_data' );
		if ( empty( $trigger_data ) ) {
			$trigger_data = [];
		}

		if ( is_array( $data ) && is_array( $events ) ) {
			$index = array_search( $data['trigger'], array_column( $events, 'trigger' ) );
			if ( is_array( $trigger_data ) && false !== $index && $data['integration'] === $events[ $index ]['integration'] ) {
				$trigger_data[ $data['integration'] ][ $data['trigger'] ]['selected_options'] = $data['selected_data'];
			}
		}

		OptionController::set_option( 'triggers', (array) $events );
		// Set the new option for the trigger data.
		OptionController::set_option( 'trigger_data', (array) $trigger_data );
		$events = array_column( (array) $events, 'trigger' );
		return self::success_message(
			[
				'events' => $events,
				'data'   => $trigger_data,
			] 
		);
	}

	/**
	 * Send response to Saas that trigger is executed.
	 *
	 * @param array $trigger_data Trigger data.
	 *
	 * @return bool
	 */
	public function trigger_listener( $trigger_data ) {
		// Pass unique WordPress webhook id.
		$wordpress_webhook_uuid                 = str_replace( '-', '', wp_generate_uuid4() );
		$site_url                               = esc_url_raw( str_replace( '/wp-json/', '', get_site_url() ) );
		$site_url                               = preg_replace( '/^https?:\/\//', '', $site_url );
		$encoded_site_url                       = urlencode( (string) $site_url );
		$trigger_data['wordpress_webhook_uuid'] = $wordpress_webhook_uuid . '_' . $encoded_site_url;
		$args                                   = [
			'headers' => [
				'Authorization'  => 'Bearer ' . $this->secret_key,
				'Referer'        => str_replace( '/wp-json/', '', get_site_url() ),
				'RefererRestUrl' => str_replace( '/wp-json/', '', get_rest_url() ),
			],
			'body'    => json_decode( (string) wp_json_encode( $trigger_data ), true ),
			'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
		];
		
		/**
		 *
		 * Ignore line
		 *
		 * @phpstan-ignore-next-line
		 */
		$response = wp_remote_post( SURE_TRIGGERS_WEBHOOK_SERVER_URL . '/wordpress/webhook', $args );
		// Store every webhook requests.
		$error_info = wp_remote_retrieve_body( $response );
		if ( 405 === wp_remote_retrieve_response_code( $response ) ) {
			$error_info = wp_remote_retrieve_response_message( $response );
		}
		if ( 0 === wp_remote_retrieve_response_code( $response ) ) {
			$error_info = __( 'Service not available', 'suretriggers' );
		}
		unset( $args['headers']['Authorization'] );
		WebhookRequestsController::suretriggers_log_request( (string) wp_json_encode( $args ), (int) wp_remote_retrieve_response_code( $response ), $error_info );

		if ( wp_remote_retrieve_response_code( $response ) === 200 ) {
			return true;
		}

		return false;
	}

	/**
	 * Disconnect connection
	 *
	 * @param WP_REST_Request $request Request data.
	 * @return object
	 */
	public function connection_disconnect( $request ) {
		SaasApiToken::save( null );
		return self::success_message();
	}

	/**
	 * Test Trigger
	 * When test trigger is initiated on Sass then execute this endpoint to create a transient for identifying trigger event.
	 *
	 * @param WP_REST_Request $request Request data.
	 * @return void
	 */
	public function test_triggers( $request ) {
		$test_triggers = (array) OptionController::get_option( 'test_triggers' );
		$event         = [
			'trigger'     => $request->get_param( 'trigger' ),
			'integration' => $request->get_param( 'integration' ),
		];

		// if request is to delete the transient, delete it and return.
		if ( $request->get_param( 'clear_transient_data' ) === 'yes' ) {
			$test_triggers = array_filter(
				$test_triggers,
				function ( $v ) use ( $event ) {
					return $v !== $event;
				}
			);
			OptionController::set_option( 'test_triggers', $test_triggers );

			return;
		}

		$test_triggers[]   = $event;
		$test_triggers     = array_unique( $test_triggers, SORT_REGULAR );
		$tmp_test_triggers = [];

		foreach ( $test_triggers as $test_trigger ) {
			if ( ! empty( $test_trigger['trigger'] ) ) {
				$tmp_test_triggers[] = $test_trigger;
			}
		}

		OptionController::set_option( 'test_triggers', $tmp_test_triggers );
	}

	/**
	 * OttoKit Connection Info
	 *
	 * @param array $debug_info Info data.
	 * @return array
	 */
	public function sure_triggers_connection_info( $debug_info ) {
		// Verify if OttoKit is connected successfully.
		$response   = self::verify_user_token();
		$connection = ( wp_remote_retrieve_response_code( $response ) === 200 );
		if ( $connection ) {
			$connection_status = __( 'Connection Successfully Set', 'suretriggers' );
		} else {
			$connection_status = __( 'Error in Connection', 'suretriggers' );
		}
		
		$fields = [
			'suretriggers_status'  => [
				'label'   => __( 'OttoKit Status', 'suretriggers' ),
				'value'   => $connection_status,
				'private' => false,
			],
			'rest_url'             => [
				'label'   => __( 'Rest URL', 'suretriggers' ),
				'value'   => esc_url( get_rest_url() ),
				'private' => false,
			],
			'suretriggers_version' => [
				'label'   => __( 'OttoKit Version', 'suretriggers' ),
				'value'   => SURE_TRIGGERS_VER,
				'private' => false,
			],
		];

		if ( defined( 'SURETRIGGERS_ENCRYPTION_KEY' ) ) {
			$fields['suretriggers_encryption_key'] = [
				'label'   => __( 'Encryption Key', 'suretriggers' ),
				'value'   => __( 'Defined', 'suretriggers' ),
				'private' => false,
			];
		}
		
		if ( defined( 'SURETRIGGERS_ENCRYPTION_SALT' ) ) {
			$fields['suretriggers_encryption_salt'] = [
				'label'   => __( 'Encryption Salt', 'suretriggers' ),
				'value'   => __( 'Defined', 'suretriggers' ),
				'private' => false,
			];
		}
		
		$debug_info['suretriggers'] = [
			'label'  => __( 'OttoKit', 'suretriggers' ),
			'fields' => $fields,
		];
		
		return $debug_info;
	}

}

RestController::get_instance();
