<?php
/**
 * Mhub_Webex API Class
 *
 * This class manages the API functionality for the Meeting Hub plugin settings.
 *
 * @package SOVLIX\MHUB\API
 */

namespace SOVLIX\MHUB\API;

/**
 * Class Webex_Api
 *
 * This class handles communication with the Webex API.
 */
class Webex_Api {
	/**
	 * The URI for authorization.
	 *
	 * @var string
	 */
	const AUTHORIZE_URI = 'https://webexapis.com/v1/authorize';

	/**
	 * The URI to obtain an access token.
	 *
	 * @var string
	 */
	const ACCESS_TOKEN_URI = 'https://webexapis.com/v1/access_token';

	/**
	 * The base URI for the Webex API.
	 *
	 * @var string
	 */
	private $api_base_uri = 'https://webexapis.com/v1/';

	/**
	 * The redirect URL after authentication.
	 *
	 * @var string
	 */
	private $redirect_url;

	/**
	 * The Webex client ID.
	 *
	 * @var string
	 */
	private $client_id = '';

	/**
	 * The Webex client secret.
	 *
	 * @var string
	 */
	private $client_secret = '';

	/**
	 * Data for the access token.
	 *
	 * @var mixed
	 */
	public $access_token_data;

	/**
	 * The current user ID.
	 *
	 * @var int
	 */
	private $current_user_id;

	/**
	 * The Webex settings from the WordPress options.
	 *
	 * @var array
	 */
	private $webex_settings;

	/**
	 * Singleton instance of the class.
	 *
	 * @var Webex_Api|null
	 */
	private static $instance = null;

	/**
	 * Webex_Api constructor.
	 */
	public function __construct() {
		$this->webex_settings = get_option( 'mhub_webex_settings', array() );
		$this->redirect_url   = home_url( '/mhub-webex-oauth/' );

		// Register hooks for custom endpoint.
		add_action( 'init', array( $this, 'add_rewrite_rules' ) );
		if ( did_action( 'init' ) ) {
			$this->add_rewrite_rules();
		}
		add_filter( 'query_vars', array( $this, 'add_query_vars' ) );
		add_action( 'template_redirect', array( $this, 'handle_oauth_redirect' ) );

		if ( is_array( $this->webex_settings ) && ! empty( $this->webex_settings ) ) {
			$this->client_id     = isset( $this->webex_settings['client_id'] ) ? $this->webex_settings['client_id'] : '';
			$this->client_secret = isset( $this->webex_settings['client_secret'] ) ? $this->webex_settings['client_secret'] : '';
		}

		$this->current_user_id = get_current_user_id();
		$this->load_access_token();
	}

	/**
	 * Add rewrite rules for Webex OAuth.
	 */
	public function add_rewrite_rules() {
		add_rewrite_rule( '^mhub-webex-oauth/?', 'index.php?mhub_webex_oauth=1', 'top' );

		// Use v2 to force re-flush if there were previous issues.
		if ( ! get_option( 'mhub_webex_rules_flushed_v2' ) ) {
			flush_rewrite_rules();
			update_option( 'mhub_webex_rules_flushed_v2', true );
		}
	}

	/**
	 * Force flush rewrite rules (useful for debugging).
	 * Can be called manually if redirect isn't working.
	 */
	public function force_flush_rewrite_rules() {
		delete_option( 'mhub_webex_rules_flushed_v2' );
		flush_rewrite_rules();
		update_option( 'mhub_webex_rules_flushed_v2', true );
	}

	/**
	 * Add custom query vars.
	 *
	 * @param array $vars Query vars.
	 * @return array
	 */
	public function add_query_vars( $vars ) {
		$vars[] = 'mhub_webex_oauth';
		return $vars;
	}

	/**
	 * Handle OAuth redirect.
	 */
	public function handle_oauth_redirect() {
		if ( get_query_var( 'mhub_webex_oauth' ) == 1 ) {
			// Ensure user is logged in and has permissions.
			if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
				wp_die( 'You do not have permission to access this page.' );
			}

			// Capture Authorization Code.
			$code  = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
			$error = isset( $_GET['error'] ) ? sanitize_text_field( wp_unslash( $_GET['error'] ) ) : '';

			if ( ! empty( $error ) ) {
				// Handle error.
				wp_redirect( admin_url( 'admin.php?page=meetinghub-settings&error=' . urlencode( $error ) . '#/webex' ) );
				exit;
			}

			if ( ! empty( $code ) ) {
				// Exchange code for token.
				$this->fetch_access_token();

				// If we are here, fetch_access_token returned (failed).
				// Redirect with error.
				wp_redirect( admin_url( 'admin.php?page=meetinghub-settings&error=' . urlencode( 'Authentication failed. Please check error logs or try again.' ) . '#/webex' ) );
				exit;
			}
		}
	}

	/**
	 * Gets the singleton instance of the class.
	 *
	 * @return Webex_Api
	 */
	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		} else {
			// Reload settings to ensure we have the latest client_id/secret.
			self::$instance->webex_settings = get_option( 'mhub_webex_settings', array() );
			if ( is_array( self::$instance->webex_settings ) && ! empty( self::$instance->webex_settings ) ) {
				self::$instance->client_id     = isset( self::$instance->webex_settings['client_id'] ) ? self::$instance->webex_settings['client_id'] : '';
				self::$instance->client_secret = isset( self::$instance->webex_settings['client_secret'] ) ? self::$instance->webex_settings['client_secret'] : '';
			}
		}
		return self::$instance;
	}

	/**
	 * Loads the access token from the database.
	 *
	 * First tries the current user's token. If not found, falls back to
	 * the admin user who originally connected Webex. This ensures API calls
	 * work for WooCommerce orders placed by customer-role users who don't
	 * have their own Webex token.
	 */
	private function load_access_token() {
		$this->access_token_data = get_user_meta( $this->current_user_id, 'mhub_webex_access_token', true );

		// If current user has a valid token, ensure the connected user option is set.
		if ( ! empty( $this->access_token_data ) && ! empty( $this->access_token_data['access_token'] ) ) {
			// Save connected user ID if not set yet (handles existing installations).
			if ( ! get_option( 'mhub_webex_connected_user_id' ) ) {
				update_option( 'mhub_webex_connected_user_id', $this->current_user_id );
			}
			return;
		}

		// Fallback: if current user has no token, load from the admin who connected Webex.
		$connected_user_id = get_option( 'mhub_webex_connected_user_id' );

		if ( ! $connected_user_id ) {
			// For existing installations: find any user who has the Webex token.
			global $wpdb;
			$connected_user_id = $wpdb->get_var(
				"SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = 'mhub_webex_access_token' AND meta_value != '' LIMIT 1"
			);

			if ( $connected_user_id ) {
				update_option( 'mhub_webex_connected_user_id', $connected_user_id );
			}
		}

		if ( $connected_user_id && (int) $connected_user_id !== $this->current_user_id ) {
			$this->access_token_data = get_user_meta( $connected_user_id, 'mhub_webex_access_token', true );
		}
	}

	/**
	 * Saves the access token to the database.
	 *
	 * Always saves to the original admin who connected Webex, even if the
	 * current user is a customer (e.g. during token refresh triggered by a
	 * customer's WooCommerce order).
	 *
	 * @param array $token_data The token data to save.
	 */
	private function save_access_token( $token_data ) {
		// Add timestamp when token was saved for expiry calculation.
		if ( ! isset( $token_data['token_saved_at'] ) ) {
			$token_data['token_saved_at'] = time();
		}

		// Determine which user to save the token for.
		// Always save to the original connecting admin, not the current customer user.
		$save_user_id      = $this->current_user_id;
		$connected_user_id = get_option( 'mhub_webex_connected_user_id' );

		if ( $connected_user_id && (int) $connected_user_id !== $this->current_user_id ) {
			// Current user is not the admin who connected - save to the admin.
			$save_user_id = (int) $connected_user_id;
		}

		update_user_meta( $save_user_id, 'mhub_webex_access_token', $token_data );
		// Store which user connected Webex so other users (e.g. customers) can use the token.
		update_option( 'mhub_webex_connected_user_id', $save_user_id );
		// Update the instance variable so it's immediately available.
		$this->access_token_data = $token_data;
	}

	/**
	 * Gets the authorization URI for Webex.
	 *
	 * @return string
	 */
	public function get_auth_uri() {
		$params = array(
			'response_type' => 'code',
			'client_id'     => $this->client_id,
			'redirect_uri'  => $this->redirect_url,
			'scope'         => 'meeting:schedules_read meeting:schedules_write',
			'state'         => admin_url( 'admin.php?page=meetinghub-settings' ),
		);

		return add_query_arg( $params, self::AUTHORIZE_URI );
	}

	/**
	 * Fetches the access token from Webex using the authorization code.
	 */
	public function fetch_access_token() {
		$code = filter_input( INPUT_GET, 'code', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
		if ( empty( $code ) ) {
			return;
		}

		$response = wp_remote_post(
			self::ACCESS_TOKEN_URI,
			array(
				'body' => array(
					'grant_type'    => 'authorization_code',
					'client_id'     => $this->client_id,
					'client_secret' => $this->client_secret,
					'code'          => $code,
					'redirect_uri'  => $this->redirect_url,
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			// Handle error - redirect with error message.
			wp_safe_redirect( admin_url( 'admin.php?page=meetinghub-settings&error=connection_failed#/webex' ) );
			exit;
		}

		$response_code = wp_remote_retrieve_response_code( $response );
		$body          = json_decode( wp_remote_retrieve_body( $response ), true );

		// Check if the response was successful.
		if ( $response_code !== 200 || empty( $body['access_token'] ) ) {
			// Handle authentication error.
			wp_safe_redirect( admin_url( 'admin.php?page=meetinghub-settings&error=authentication_failed#/webex' ) );
			exit;
		}

		$this->save_access_token( $body );
		wp_safe_redirect( admin_url( 'admin.php?page=meetinghub-settings#/webex' ) );
		exit;
	}

	/**
	 * Revokes the access token.
	 */
	public function revoke_access_token() {
		delete_user_meta( $this->current_user_id, 'mhub_webex_access_token' );
	}

	/**
	 * Refreshes the access token using the refresh token.
	 *
	 * @return bool True if refresh was successful, false otherwise.
	 */
	private function refresh_access_token() {
		if ( empty( $this->access_token_data['refresh_token'] ) ) {

			return false;
		}

		$response = wp_remote_post(
			self::ACCESS_TOKEN_URI,
			array(
				'body' => array(
					'grant_type'    => 'refresh_token',
					'client_id'     => $this->client_id,
					'client_secret' => $this->client_secret,
					'refresh_token' => $this->access_token_data['refresh_token'],
				),
			)
		);

		if ( is_wp_error( $response ) ) {

			return false;
		}

		$response_code = wp_remote_retrieve_response_code( $response );
		$body          = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( $response_code !== 200 || empty( $body['access_token'] ) ) {

			return false;
		}

		// Save the new token data.
		$this->access_token_data = $body;
		$this->save_access_token( $body );

		return true;
	}

	/**
	 * Checks if the access token is expired or about to expire.
	 *
	 * @return bool True if token is expired or will expire in the next 5 minutes.
	 */
	private function is_token_expired() {
		if ( empty( $this->access_token_data['expires_in'] ) ) {
			return false; // No expiry info, assume valid.
		}

		// Check if we have a timestamp of when the token was saved.
		if ( empty( $this->access_token_data['token_saved_at'] ) ) {
			// No timestamp, add one now and assume token is still valid.
			$this->access_token_data['token_saved_at'] = time();
			$this->save_access_token( $this->access_token_data );
			return false;
		}

		$token_age  = time() - $this->access_token_data['token_saved_at'];
		$expires_in = $this->access_token_data['expires_in'];

		// Refresh if token will expire in the next 5 minutes (300 seconds buffer).
		return ( $token_age >= ( $expires_in - 300 ) );
	}

	/**
	 * Sends a request to the Webex API.
	 *
	 * @param string $endpoint The API endpoint.
	 * @param string $method The HTTP method (default: 'GET').
	 * @param array  $data The data to send with the request.
	 * @return mixed The API response.
	 */
	private function api_request( $endpoint, $method = 'GET', $data = array() ) {
		if ( empty( $this->access_token_data['access_token'] ) ) {

			return false;
		}

		// Check if token is expired and refresh if needed.
		if ( $this->is_token_expired() ) {

			if ( ! $this->refresh_access_token() ) {

				return false;
			}
		}

		$args = array(
			'method'  => $method,
			'headers' => array(
				'Authorization' => 'Bearer ' . $this->access_token_data['access_token'],
				'Content-Type'  => 'application/json',
			),
			'body'    => 'GET' !== $method ? wp_json_encode( $data ) : array(),
		);

		$response = wp_remote_request( $this->api_base_uri . $endpoint, $args );

		if ( is_wp_error( $response ) ) {

			return false;
		}

		$response_code = wp_remote_retrieve_response_code( $response );
		$body          = json_decode( wp_remote_retrieve_body( $response ), true );

		// If we get a 401 Unauthorized, the token might be invalid - try refreshing once.
		if ( 401 === $response_code ) {

			if ( $this->refresh_access_token() ) {
				// Retry the request with the new token.
				$args['headers']['Authorization'] = 'Bearer ' . $this->access_token_data['access_token'];
				$response                         = wp_remote_request( $this->api_base_uri . $endpoint, $args );

				if ( is_wp_error( $response ) ) {

					return false;
				}

				$body = json_decode( wp_remote_retrieve_body( $response ), true );
			} else {

				return false;
			}
		}

		return $body;
	}

	/**
	 * Gets the current user's info.
	 *
	 * @return mixed
	 */
	public function get_user_info() {
		return $this->api_request( 'people/me' );
	}

	/**
	 * Lists meetings.
	 *
	 * @param array $params The parameters for the request.
	 * @return mixed The list of meetings.
	 */
	public function list_meetings( $params = array() ) {
		return $this->api_request( 'meetings', 'GET', $params );
	}

	/**
	 * Gets a meeting by ID.
	 *
	 * @param string $id The meeting ID.
	 * @return mixed The meeting details.
	 */
	public function get_meeting( $id ) {
		return $this->api_request( 'meetings/' . $id, 'GET' );
	}

	/**
	 * Creates a new meeting.
	 *
	 * @param array $data The data for the new meeting.
	 * @return mixed The created meeting.
	 */
	public function create_meeting( $data ) {
		return $this->api_request( 'meetings', 'POST', $data );
	}

	/**
	 * Updates a meeting by ID.
	 *
	 * @param string $id The meeting ID.
	 * @param array  $data The data to update the meeting with.
	 * @return mixed The updated meeting.
	 */
	public function update_meeting( $id, $data ) {
		return $this->api_request( 'meetings/' . $id, 'PUT', $data );
	}

	/**
	 * Deletes a meeting by ID.
	 *
	 * @param string $id The meeting ID.
	 * @return mixed The response from the API.
	 */
	public function delete_meeting( $id ) {
		return $this->api_request( 'meetings/' . $id, 'DELETE' );
	}

	/**
	 * Gets the registration settings for a meeting/webinar.
	 *
	 * The Webex API requires a separate GET /meetings/{id}/registration call
	 * to retrieve current registration configuration. This is needed before
	 * attempting a PUT to confirm the registration object exists.
	 *
	 * @param string $id The Webex meeting ID.
	 * @return mixed The registration settings or false on failure.
	 */
	public function get_meeting_registration( $id ) {
		return $this->api_request( 'meetings/' . $id . '/registration', 'GET' );
	}

	/**
	 * Updates the registration settings for a webinar.
	 *
	 * IMPORTANT: The standard PUT /meetings/{id} endpoint silently ignores the
	 * `registration.autoAcceptRequest` field during updates. Per Webex API docs,
	 * registration settings must be updated via this dedicated endpoint:
	 * PUT /meetings/{id}/registration
	 *
	 * @param string $id   The Webex meeting ID.
	 * @param array  $data Registration data. Must include 'autoAcceptRequest' (bool).
	 *                     Should also include required fields: requireFirstName,
	 *                     requireLastName, requireEmail.
	 * @return mixed The API response or false on failure.
	 */
	public function update_meeting_registration( $id, $data ) {
		return $this->api_request( 'meetings/' . $id . '/registration', 'PUT', $data );
	}

	/**
	 * Gets a user by ID.
	 *
	 * @param string|null $user_id The user ID (optional).
	 * @return mixed The user details.
	 */
	public function get_user( $user_id = null ) {
		if ( $user_id ) {
			return $this->api_request( 'people/' . $user_id, 'GET' );
		} else {
			return $this->api_request( 'people', 'GET' );
		}
	}

	/**
	 * Registers a participant for a Webex meeting/webinar.
	 *
	 * Uses the bulk registration endpoint to register a single participant.
	 * This is required for webinars with registration enabled.
	 *
	 * @param string $meeting_id Webex meeting/webinar ID.
	 * @param array  $registrant Registrant data with keys: firstName, lastName, email.
	 * @return mixed Registration response from Webex API or error array.
	 */
	public function register_meeting_participant( $meeting_id, $registrant ) {
		if ( empty( $meeting_id ) || empty( $registrant['email'] ) ) {
			return array( 'error' => 'Meeting ID and email are required' );
		}

		// Prepare the bulk registration payload.
		$body = array(
			'items' => array( $registrant ),
		);

		// Call the bulk registration endpoint.
		$endpoint = 'meetings/' . $meeting_id . '/registrants/bulkInsert';
		$response = $this->api_request( $endpoint, 'POST', $body );

		// Return the first registrant from the response.
		if ( isset( $response['items'][0] ) ) {
			return $response['items'][0];
		}

		// Return full response if items not found (likely an error).
		return $response;
	}

	/**
	 * Lists recordings.
	 *
	 * @param array $params The parameters for the request.
	 * @return mixed The list of recordings.
	 */
	public function list_recordings( $params = array() ) {
		return $this->api_request( 'recordings', 'GET', $params );
	}
}
