<?php
/**
 * Microsoft Teams API Class
 *
 * This class manages the API functionality for Microsoft Teams integration.
 *
 * @package SOVLIX\MHUB\API
 */

namespace SOVLIX\MHUB\API;

/**
 * Class MsTeams_Api
 *
 * This class handles communication with the Microsoft Graph API for Teams meetings.
 */
class MsTeams_Api {
	/**
	 * The URI for authorization.
	 *
	 * @var string
	 */
	const AUTHORIZE_URI = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';

	/**
	 * The URI to obtain an access token.
	 *
	 * @var string
	 */
	const ACCESS_TOKEN_URI = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';

	/**
	 * The base URI for the Microsoft Graph API.
	 *
	 * @var string
	 */
	private $api_base_uri = 'https://graph.microsoft.com/v1.0/';

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

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

	/**
	 * The Microsoft Teams 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 Microsoft Teams settings from the WordPress options.
	 *
	 * @var array
	 */
	private $msteams_settings;

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

	/**
	 * MsTeams_Api constructor.
	 */
	public function __construct() {
		$this->msteams_settings = get_option( 'mhub_msteams_settings', array() );
		// Use a clean URL without query string for MS Teams.
		$this->redirect_url     = home_url( '/mhub-msteams-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->msteams_settings ) && ! empty( $this->msteams_settings ) ) {
			$this->client_id     = $this->msteams_settings['client_id'];
			$this->client_secret = $this->msteams_settings['client_secret'];
		}

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

	/**
	 * Gets the singleton instance of the class.
	 *
	 * @return MsTeams_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->msteams_settings = get_option( 'mhub_msteams_settings', array() );
			if ( is_array( self::$instance->msteams_settings ) && ! empty( self::$instance->msteams_settings ) ) {
				self::$instance->client_id     = isset( self::$instance->msteams_settings['client_id'] ) ? self::$instance->msteams_settings['client_id'] : '';
				self::$instance->client_secret = isset( self::$instance->msteams_settings['client_secret'] ) ? self::$instance->msteams_settings['client_secret'] : '';
			}
		}
		return self::$instance;
	}

	/**
	 * Loads the access token from the database.
	 */
	private function load_access_token() {
		$this->access_token_data = get_user_meta( $this->current_user_id, 'mhub_msteams_access_token', true );
	}

	/**
	 * Saves the access token to the database.
	 *
	 * @param array $token_data The token data to save.
	 */
	private function save_access_token( $token_data ) {
		// Save to user meta for per-user tokens.
		update_user_meta( $this->current_user_id, 'mhub_msteams_access_token', $token_data );

		// Also save to global options for compatibility with legacy code.
		update_option( 'mhub_msteams_global_oauth_data', (object) $token_data );
	}

	/**
	 * Gets the authorization URI for Microsoft Teams.
	 *
	 * @return string
	 */
	public function get_auth_uri() {
		$params = array(
			'client_id'     => $this->client_id,
			'response_type' => 'code',
			'redirect_uri'  => $this->redirect_url,
			'scope'         => 'openid profile User.Read Calendars.ReadWrite offline_access',
			'response_mode' => 'query',
			'state'         => admin_url( 'admin.php' ),
			'prompt'        => 'consent',  // Force consent screen to show new permissions
		);

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

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

			return;
		}

		$token_request_body = array(
			'client_id'     => $this->client_id,
			'client_secret' => $this->client_secret,
			'code'          => $code,
			'redirect_uri'  => $this->redirect_url,
			'grant_type'    => 'authorization_code',
			'scope'         => 'openid profile User.Read Calendars.ReadWrite OnlineMeetings.ReadWrite offline_access', // Include scope in token request
		);



		$response = wp_remote_post(
			self::ACCESS_TOKEN_URI,
			array(
				'body' => $token_request_body,
				'headers' => array(
					'Content-Type' => 'application/x-www-form-urlencoded',
				),
			)
		);

		if ( is_wp_error( $response ) ) {

			return;
		}

		$response_code = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );

		$body = json_decode( $response_body, true );
		if ( isset( $body['error'] ) ) {
			return;
		}

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

	/**
	 * Revokes the access token.
	 */
	public function revoke_access_token() {
		// Clear user meta token.
		delete_user_meta( $this->current_user_id, 'mhub_msteams_access_token' );

		// Clear global option token for compatibility.
		delete_option( 'mhub_msteams_global_oauth_data' );
	}

	/**
	 * Clear all MS Teams tokens (for re-authentication).
	 */
	public function clear_tokens() {
		// Clear user meta token.
		delete_user_meta( $this->current_user_id, 'mhub_msteams_access_token' );
		// Clear global option token for compatibility.
		delete_option( 'mhub_msteams_global_oauth_data' );
	}

	/**
	 * Refreshes the access token using refresh token.
	 *
	 * @return bool
	 */
	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(
					'client_id'     => $this->client_id,
					'client_secret' => $this->client_secret,
					'refresh_token' => $this->access_token_data['refresh_token'],
					'grant_type'    => 'refresh_token',
				),
			)
		);

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

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( isset( $body['access_token'] ) ) {
			$this->save_access_token( $body );
			$this->load_access_token();
			return true;
		}

		return false;
	}

	/**
	 * Sends a request to the Microsoft Graph API using application permissions.
	 *
	 * @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_with_app_permissions( $endpoint, $method = 'GET', $data = array() ) {
		// Get application access token.
		$app_token = $this->get_application_access_token();
		if ( ! $app_token ) {

			return false;
		}

		$args = array(
			'method'  => $method,
			'headers' => array(
				'Authorization' => 'Bearer ' . $app_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 );
		$response_body = wp_remote_retrieve_body( $response );

		return json_decode( $response_body, true );
	}

	/**
	 * Get application access token using client credentials flow.
	 *
	 * @return string|false
	 */
	private function get_application_access_token() {
		// Check if we have a cached token.
		$cached_token = get_transient( 'mhub_msteams_app_token' );
		if ( $cached_token ) {
			return $cached_token;
		}

		$response = wp_remote_post(
			self::ACCESS_TOKEN_URI,
			array(
				'body' => array(
					'client_id'     => $this->client_id,
					'client_secret' => $this->client_secret,
					'scope'         => 'https://graph.microsoft.com/.default',
					'grant_type'    => 'client_credentials',
				),
				'headers' => array(
					'Content-Type' => 'application/x-www-form-urlencoded',
				),
			)
		);

		if ( is_wp_error( $response ) ) {

			return false;
		}

		$response_code = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );

		$body = json_decode( $response_body, true );

		if ( isset( $body['access_token'] ) ) {
			// Cache the token for 50 minutes (tokens expire in 1 hour).
			set_transient( 'mhub_msteams_app_token', $body['access_token'], 50 * MINUTE_IN_SECONDS );
			return $body['access_token'];
		}

		return false;
	}

	/**
	 * Sends a request to the Microsoft Graph 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() ) {
		// Log API request details.
		if ( empty( $this->access_token_data['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 );
		$response_body = wp_remote_retrieve_body( $response );

		// If token expired, try to refresh.
		if ( 401 === $response_code ) {

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

			}
		}

		if ( 204 === $response_code ) {
			return array( 'success' => true );
		}

		return json_decode( $response_body, true );
	}

	/**
	 * Create Teams meeting via calendar event (fallback method).
	 *
	 * @param array $data Meeting data.
	 * @return mixed The created meeting.
	 */
	private function create_teams_meeting_via_calendar( $data ) {
		// Convert meeting data to calendar event format.
		$start_time = isset( $data['startDateTime'] ) ? $data['startDateTime'] : gmdate( 'c' );
		$duration   = isset( $data['duration_hours'] ) && isset( $data['duration_minutes'] ) 
			? ( $data['duration_hours'] * 60 + $data['duration_minutes'] ) 
			: 60;

		$end_time = gmdate( 'c', strtotime( $start_time ) + ( $duration * 60 ) );

		$calendar_event = array(
			'subject' => $data['subject'] ?? 'Teams Meeting',
			'body' => array(
				'contentType' => 'HTML',
				'content' => $data['agenda'] ?? 'Teams meeting created via MeetingHub'
			),
			'start' => array(
				'dateTime' => $start_time,
				'timeZone' => $data['meeting_timezone'] ?? 'UTC'
			),
			'end' => array(
				'dateTime' => $end_time,
				'timeZone' => $data['meeting_timezone'] ?? 'UTC'
			),
			'isOnlineMeeting' => true,
			'onlineMeetingProvider' => 'teamsForBusiness'
		);

		$result = $this->api_request( 'me/events', 'POST', $calendar_event );

		if ( $result && isset( $result['onlineMeeting'] ) ) {
			// Extract Teams meeting info from calendar event.
			$teams_meeting = array(
				'id' => $result['id'],
				'joinUrl' => $result['onlineMeeting']['joinUrl'] ?? '',
				'joinWebUrl' => $result['onlineMeeting']['joinUrl'] ?? '', // Alias for Mhub_Meetings.
				'subject' => $result['subject'],
				'startDateTime' => $result['start']['dateTime'],
				'endDateTime' => $result['end']['dateTime'],
				'meetingType' => 'scheduled',
			);

			return $teams_meeting;
		}

		return $result;
	}

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

	/**
	 * Creates a new online meeting.
	 *
	 * @param array $data The data for the new meeting.
	 * @return mixed The created meeting.
	 */
	public function create_meeting( $data ) {
		// Use all the prepared data instead of just basic fields.
		$meeting_data = $data;

		// Ensure required fields are present.
		if ( empty( $meeting_data['subject'] ) ) {
			$meeting_data['subject'] = 'Meeting';
		}

		$result = $this->create_teams_meeting_via_calendar( $meeting_data );

		// If calendar approach fails, try direct calendar event creation.
		if ( ! $result || isset( $result['error'] ) ) {
			$result = $this->create_direct_calendar_event( $meeting_data );
		}

		// If all else fails, try simple calendar event without Teams meeting.
		if ( ! $result || isset( $result['error'] ) ) {
			$result = $this->create_simple_calendar_event( $meeting_data );
		}

		return $result;
	}

	/**
	 * Create direct calendar event (most reliable for personal accounts).
	 *
	 * @param array $data Meeting data.
	 * @return mixed The created meeting.
	 */
	private function create_direct_calendar_event( $data ) {
		// Convert meeting data to calendar event format.
		$start_time = isset( $data['startDateTime'] ) ? $data['startDateTime'] : gmdate( 'c' );
		$duration = isset( $data['duration_hours'] ) && isset( $data['duration_minutes'] )
			? ( $data['duration_hours'] * 60 + $data['duration_minutes'] )
			: 60;

		$end_time = gmdate( 'c', strtotime( $start_time ) + ( $duration * 60 ) );

		$calendar_event = array(
			'subject' => $data['subject'] ?? 'Teams Meeting',
			'body' => array(
				'contentType' => 'HTML',
				'content' => '<p>' . ( $data['agenda'] ?? 'Teams meeting created via MeetingHub' ) . '</p>'
			),
			'start' => array(
				'dateTime' => $start_time,
				'timeZone' => $data['meeting_timezone'] ?? 'UTC'
			),
			'end' => array(
				'dateTime' => $end_time,
				'timeZone' => $data['meeting_timezone'] ?? 'UTC'
			),
			'isOnlineMeeting' => true,
			'onlineMeetingProvider' => 'teamsForBusiness',
			'location' => array(
				'displayName' => 'Microsoft Teams Meeting'
			),
		);

		$result = $this->api_request( 'me/events', 'POST', $calendar_event );

		if ( $result && isset( $result['id'] ) ) {
			// Extract Teams meeting info from calendar event.
			$teams_meeting = array(
				'id' => $result['id'],
				'joinUrl' => $result['onlineMeeting']['joinUrl'] ?? '',
				'joinWebUrl' => $result['onlineMeeting']['joinUrl'] ?? '', // Alias for Mhub_Meetings
				'subject' => $result['subject'],
				'startDateTime' => $result['start']['dateTime'],
				'endDateTime' => $result['end']['dateTime'],
				'meetingType' => 'scheduled',
				'webUrl' => $result['webLink'] ?? '',
				'calendarEvent' => $result,
			);

			return $teams_meeting;
		}

		return $result;
	}

	/**
	 * Create simple calendar event (fallback for free Azure accounts).
	 *
	 * @param array $data Meeting data.
	 * @return mixed The created meeting.
	 */
	private function create_simple_calendar_event( $data ) {
		// Convert meeting data to simple calendar event format.
		$start_time = isset( $data['startDateTime'] ) ? $data['startDateTime'] : gmdate( 'c' );
		$duration = isset( $data['duration_hours'] ) && isset( $data['duration_minutes'] ) 
			? ( $data['duration_hours'] * 60 + $data['duration_minutes'] ) 
			: 60;

		$end_time = gmdate( 'c', strtotime( $start_time ) + ( $duration * 60 ) );

		$calendar_event = array(
			'subject' => $data['subject'] ?? 'Meeting',
			'body' => array(
				'contentType' => 'HTML',
				'content' => '<p>' . ( $data['agenda'] ?? 'Meeting created via MeetingHub' ) . '</p><p><strong>Note:</strong> This is a calendar event. For Teams meetings, please create them manually in Teams.</p>'
			),
			'start' => array(
				'dateTime' => $start_time,
				'timeZone' => $data['meeting_timezone'] ?? 'UTC'
			),
			'end' => array(
				'dateTime' => $end_time,
				'timeZone' => $data['meeting_timezone'] ?? 'UTC'
			),
			'location' => array(
				'displayName' => 'Microsoft Teams (Manual)'
			),
		);

		$result = $this->api_request( 'me/events', 'POST', $calendar_event );

		if ( $result && isset( $result['id'] ) ) {
			// Return calendar event as meeting.
			$meeting = array(
				'id' => $result['id'],
				'joinUrl' => '', // No Teams link available
				'subject' => $result['subject'],
				'startDateTime' => $result['start']['dateTime'],
				'endDateTime' => $result['end']['dateTime'],
				'meetingType' => 'calendar_event',
				'webUrl' => $result['webLink'] ?? '',
				'calendarEvent' => $result,
				'note' => 'Calendar event created. Please create Teams meeting manually.'
			);

			return $meeting;
		}

		return $result;
	}

	/**
	 * Test what permissions are actually available.
	 *
	 * @return array Test results.
	 */
	public function test_permissions() {
		$results = array();

		// Test User.Read.
		$user_info = $this->api_request( 'me' );
		$results['user_read'] = ! empty( $user_info['id'] );
		// Test Calendars.ReadWrite.
		$calendars = $this->api_request( 'me/calendars' );
		$results['calendars_readwrite'] = ! empty( $calendars );

		// Test OnlineMeetings.ReadWrite.
		$meetings = $this->api_request( 'me/onlineMeetings' );
		$results['onlinemeetings_readwrite'] = ! empty( $meetings ) && ! isset( $meetings['error'] );
		// Log results.
		return $results;
	}

	/**
	 * Gets a meeting by ID.
	 *
	 * @param string $id The meeting ID.
	 * @return mixed The meeting details.
	 */
	public function get_meeting( $id ) {
		// Check if ID looks like an Outlook Calendar Event ID.
		if ( strpos( $id, 'AAMk' ) === 0 || strpos( $id, 'AQMk' ) === 0 ) {
			$event = $this->api_request( 'me/events/' . $id, 'GET' );

			if ( ! empty( $event ) && ! isset( $event['error'] ) ) {
				// Map event details to meeting format.
				$event['joinWebUrl'] = isset( $event['onlineMeeting']['joinUrl'] ) ? $event['onlineMeeting']['joinUrl'] : '';
				return $event;
			}
		}

		return $this->api_request( 'me/onlineMeetings/' . $id, 'GET' );
	}

	/**
	 * 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 ) {
		// Since we create meetings via Calendar API, we need to update them as calendar events.
		$start_time = $data['startDateTime'] ?? gmdate( 'c' );
		$end_time = $data['endDateTime'] ?? gmdate( 'c', strtotime( $start_time ) + 3600 );

		$calendar_data = array(
			'subject' => $data['subject'] ?? '',
			'start' => array(
				'dateTime' => $start_time,
				'timeZone' => $data['meeting_timezone'] ?? 'UTC'
			),
			'end' => array(
				'dateTime' => $end_time,
				'timeZone' => $data['meeting_timezone'] ?? 'UTC'
			),
		);

		if ( ! empty( $data['agenda'] ) ) {
			$calendar_data['body'] = array(
				'contentType' => 'HTML',
				'content' => $data['agenda']
			);
		}

		// Use calendar events endpoint since meetings are created via calendar.
		return $this->api_request( 'me/events/' . $id, 'PATCH', $calendar_data );
	}

	/**
	 * Deletes a meeting by ID.
	 *
	 * @param string $id The meeting ID.
	 * @return mixed The response from the API.
	 */
	public function delete_meeting( $id ) {
		// Since we create meetings via Calendar API, we need to delete them as calendar events.
		return $this->api_request( 'me/events/' . $id, 'DELETE' );
	}

	/**
	 * Lists online meetings.
	 *
	 * @param array $params The parameters for the request.
	 * @return mixed The list of meetings.
	 */
	public function list_meetings( $params = array() ) {
		$query_params = '';
		if ( ! empty( $params ) ) {
			$query_params = '?' . http_build_query( $params );
		}

		return $this->api_request( 'me/onlineMeetings' . $query_params, 'GET' );
	}

	/**
	 * Check if MS Teams is connected.
	 *
	 * @return bool
	 */
	public function is_connected() {
		$user_token   = get_user_meta( $this->current_user_id, 'mhub_msteams_access_token', true );
		$global_token = get_option( 'mhub_msteams_global_oauth_data' );

		return ! empty( $user_token['access_token'] ) || ! empty( $global_token->access_token );
	}

	/**
	 * Test MS Teams connection and API.
	 *
	 * @return array
	 */
	public function test_connection() {
		$result = array(
			'connected' => false,
			'user_info' => null,
			'error' => null,
		);

		if ( ! $this->is_connected() ) {
			$result['error'] = 'No access token found';
			return $result;
		}

		$result['connected'] = true;

		// Try to get user info.
		$user_info = $this->get_user_info();
		if ( $user_info ) {
			$result['user_info'] = $user_info;
		} else {
			$result['error'] = 'Failed to get user info';
		}

		return $result;
	}

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

		if ( ! get_option( 'mhub_msteams_rules_flushed_v2' ) ) {
			flush_rewrite_rules();
			update_option( 'mhub_msteams_rules_flushed_v2', true );
		}
	}

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

	/**
	 * Handle OAuth redirect.
	 */
	public function handle_oauth_redirect() {
		if ( get_query_var( 'mhub_msteams_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( $_GET['code'] ) : '';
			$error = isset( $_GET['error'] ) ? sanitize_text_field( $_GET['error'] ) : '';

			if ( ! empty( $error ) ) {
				// Handle error.
				wp_redirect( admin_url( 'admin.php?page=meetinghub-settings&error=' . urlencode( $error ) ) );
				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.' ) ) );
				exit;
			}
		}
	}
}
