<?php
/**
 * File containing the Mhub_MsTeams_Api class for connecting to Microsoft Graph API.
 *
 * @package SOVLIX\MHUB\MsTeams
 */

namespace SOVLIX\MHUB\MsTeams;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'Mhub_MsTeams_Api' ) ) {
	/**
	 * Class Mhub_MsTeams_Api
	 *
	 * Description: Connecting Microsoft Graph API for Teams meetings functionality.
	 */
	class Mhub_MsTeams_Api {
		/**
		 * Number of attempts to revalidate OAuth. Used to control the revalidation flow.
		 *
		 * @var int
		 */
		public static $oauth_revalidate_attempts = 0;

		/**
		 * Microsoft Teams Client ID.
		 *
		 * @var string
		 */
		public $client_id;

		/**
		 * Microsoft Teams Client Secret.
		 *
		 * @var string
		 */
		public $client_secret;

		/**
		 * Hold my instance.
		 *
		 * @var Mhub_MsTeams_Api|null
		 */
		protected static $instance;

		/**
		 * API endpoint base
		 *
		 * @var string
		 */
		private $api_url = 'https://graph.microsoft.com/v1.0/';

		/**
		 * OAuth endpoint base
		 *
		 * @var string
		 */
		private $oauth_url = 'https://login.microsoftonline.com/common/oauth2/v2.0/';

		/**
		 * Create only one instance so that it may not Repeat
		 */
		public static function instance() {
			if ( is_null( self::$instance ) ) {
				self::$instance = new self();
			}

			return self::$instance;
		}

		/**
		 * Mhub_MsTeams_Api constructor.
		 *
		 * @param string $client_id    Microsoft Teams Client ID.
		 * @param string $client_secret Microsoft Teams Client Secret.
		 */
		public function __construct( $client_id = '', $client_secret = '' ) {
			$this->client_id     = $client_id;
			$this->client_secret = $client_secret;
		}

		/**
		 * Send request to API.
		 *
		 * @param string $called_function API function to call.
		 * @param mixed  $data           Data to send in the request.
		 * @param string $request        Type of request (GET, POST, DELETE, PATCH, PUT).
		 *
		 * @return array|bool|string|\WP_Error
		 */
		protected function send_request( $called_function, $data, $request = 'GET' ) {
			$initial_request = $request;
			$request_url     = $this->api_url . $called_function;
			$bearer_token    = $this->get_bearer_token();

			if ( ! $bearer_token ) {
				return false;
			}

			$args = array(
				'timeout' => 30,
				'headers' => array(
					'Authorization' => 'Bearer ' . $bearer_token,
					'Content-Type'  => 'application/json',
				),
			);

			if ( 'GET' === $request ) {
				$args['body'] = ! empty( $data ) ? $data : array();
				$request      = wp_remote_get( $request_url, $args );
			} elseif ( 'DELETE' === $request ) {
				$args['body']   = ! empty( $data ) ? wp_json_encode( $data ) : array();
				$args['method'] = 'DELETE';
				$request        = wp_remote_request( $request_url, $args );
			} elseif ( 'PATCH' === $request ) {
				$args['body']   = ! empty( $data ) ? wp_json_encode( $data ) : array();
				$args['method'] = 'PATCH';
				$request        = wp_remote_request( $request_url, $args );
			} elseif ( 'PUT' === $request ) {
				$args['body']   = ! empty( $data ) ? wp_json_encode( $data ) : array();
				$args['method'] = 'PUT';
				$request        = wp_remote_request( $request_url, $args );
			} else {
				$args['body']   = ! empty( $data ) ? wp_json_encode( $data ) : array();
				$args['method'] = 'POST';
				$request        = wp_remote_post( $request_url, $args );
			}

			if ( is_wp_error( $request ) ) {
				$this->log_message( $request->get_error_message(), $request->get_error_code(), $request );
				return false;
			} else {
				$response_code = wp_remote_retrieve_response_code( $request );
				$response_body = wp_remote_retrieve_body( $request );
				$debug_log     = get_option( 'msteams_api_enable_debug_log' );

				if ( 401 === (int) $response_code ) {
					// Try to regenerate access token
					$this->regenerate_access_token();
					// only retry twice.
					if ( self::$oauth_revalidate_attempts <= 2 ) {
						++self::$oauth_revalidate_attempts;
						// resend the request after regenerating access token.
						return $this->send_request( $called_function, $data, $initial_request );
					} else {
						self::$oauth_revalidate_attempts = 0;
						if ( ! empty( $debug_log ) ) {
							$this->log_message( $response_body, $response_code, $request );
						}
					}
				}

				// If Debug log is enabled.
				if ( ! empty( $debug_log ) ) {
					if ( in_array( (int) $response_code, array( 400, 401, 403, 404, 409, 429 ), true ) ) {
						$this->log_message( $response_body, $response_code, $request );
					}
				}
			}

			return $response_body;
		}

		/**
		 * Check if given string is a correct JSON object.
		 *
		 * @param string $get_string The string to check.
		 *
		 * @return bool
		 */
		public function is_json( $get_string ) {
			json_decode( $get_string );
			return json_last_error() === JSON_ERROR_NONE;
		}

		/**
		 * Log the message.
		 *
		 * @param mixed $response_body Response body.
		 * @param int   $response_code Response code.
		 * @param mixed $request       Request object.
		 */
		public function log_message( $response_body, $response_code, $request ) {
			$message  = $response_code . ' ::: ';
			$message .= wp_remote_retrieve_response_message( $request );

			$error_data = array();

			if ( ! empty( $response_body ) ) {
				if ( $this->is_json( $response_body ) ) {
					$response_body = json_decode( $response_body );
				}

				if ( ! empty( $response_body ) && ! empty( $response_body->error ) ) {
					$message .= ' ::: ERROR => ' . $response_body->error->message;
				} elseif ( ! empty( $response_body ) && is_string( $response_body ) ) {
					$message .= ' ::: MESSAGE => ' . $response_body;
				}
			}
			$error = new \WP_Error( $response_code, $message, $error_data );
		}

		/**
		 * Get Bearer Token for authorization.
		 *
		 * @return string|false
		 */
		private function get_bearer_token() {
			$oauth_data = get_option( 'mhub_msteams_global_oauth_data' );
			if ( ! empty( $oauth_data ) && ! empty( $oauth_data->access_token ) ) {
				return $oauth_data->access_token;
			}
			return false;
		}

		/**
		 * Regenerate access token using refresh token.
		 */
		private function regenerate_access_token() {
			$oauth_data = get_option( 'mhub_msteams_global_oauth_data' );
			if ( empty( $oauth_data ) || empty( $oauth_data->refresh_token ) ) {
				return false;
			}

			$response = wp_remote_post(
				$this->oauth_url . 'token',
				array(
					'body' => array(
						'client_id'     => $this->client_id,
						'client_secret' => $this->client_secret,
						'refresh_token' => $oauth_data->refresh_token,
						'grant_type'    => 'refresh_token',
					),
				)
			);

			if ( is_wp_error( $response ) ) {
				return false;
			}

			$body = json_decode( wp_remote_retrieve_body( $response ) );
			if ( ! empty( $body->access_token ) ) {
				update_option( 'mhub_msteams_global_oauth_data', $body );
				return true;
			}

			return false;
		}

		/**
		 * Get current user information.
		 *
		 * @return array|bool|string
		 */
		public function get_user_info() {
			return $this->send_request( 'me', array(), 'GET' );
		}

		/**
		 * Create a Microsoft Teams meeting.
		 *
		 * @param array $data Meeting data.
		 *
		 * @return array|bool|string|void|WP_Error
		 */
		public function create_teams_meeting( $data = array() ) {
			$create_meeting_data = apply_filters( 'mhub_msteams_create_meeting', $data );
			if ( ! empty( $create_meeting_data ) ) {
				return $this->send_request( 'me/onlineMeetings', $create_meeting_data, 'POST' );
			}
			return;
		}

		/**
		 * Update information for a Microsoft Teams meeting identified by its ID.
		 *
		 * @param string $meeting_id The Teams meeting ID.
		 * @param array  $data       Data to update for the meeting.
		 *
		 * @return array|bool|string|void|WP_Error
		 */
		public function update_teams_meeting( $meeting_id, $data = array() ) {
			$update_meeting_data = apply_filters( 'mhub_msteams_update_meeting', $data );

			if ( ! empty( $update_meeting_data ) ) {
				$response = $this->send_request( 'me/onlineMeetings/' . $meeting_id, $update_meeting_data, 'PATCH' );
				return $response;
			}
			return;
		}

		/**
		 * Retrieve information for a Microsoft Teams meeting by its ID.
		 *
		 * @param string $id   The ID of the Teams meeting.
		 * @param array  $args Additional arguments for retrieving meeting information.
		 *
		 * @return array
		 */
		public function get_meeting_info( $id, $args = array() ) {
			$get_meeting_info_array = apply_filters( 'mhub_msteams_get_meeting_info', $args );
			return $this->send_request( 'me/onlineMeetings/' . $id, $get_meeting_info_array, 'GET' );
		}

		/**
		 * Delete a Microsoft Teams meeting by its ID.
		 *
		 * @param string $meeting_id The ID of the Teams meeting to delete.
		 *
		 * @return array|bool|string|WP_Error
		 */
		public function delete_meeting( $meeting_id ) {
			return $this->send_request( 'me/onlineMeetings/' . $meeting_id, false, 'DELETE' );
		}

		/**
		 * List Microsoft Teams online meetings.
		 *
		 * @param array $args Additional arguments for listing meetings.
		 *
		 * @return array
		 */
		public function list_meetings( $args = array() ) {
			$list_meetings_array = apply_filters( 'mhub_msteams_list_meetings', $args );
			return $this->send_request( 'me/onlineMeetings', $list_meetings_array, 'GET' );
		}
	}
}