<?php

/**
 * GoogleCalendarMeetIntegration class integrates Google Calendar API
 * to manage*/

namespace RadiusTheme\RadiusBooking\Integrations;

use RadiusTheme\RadiusBooking\Models\Settings;
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}
/**
 * Class GoogleCalendarMeetIntegration
 */
class GoogleCalendarMeetIntegration {

	/**
	 * Client ID
	 *
	 * @var mixed|string
	 */
	private $client_id;
	/**
	 * Client Secret
	 *
	 * @var mixed|string
	 */
	private $client_secret;
	/**
	 * Redirect URI
	 *
	 * @var mixed|string
	 */
	private $redirect_uri;

	/**
	 * Access Token
	 *
	 * @var mixed|string
	 */
	private $access_token;
	/**
	 * Refresh Token
	 *
	 * @var mixed|string
	 */
	private $refresh_token;

	/**
	 * Constructor
	 */
	public function __construct() {
		$settings     = new Settings();
		$integrations = $settings->getNestedKey( 'integrations.googleCalendar', false );
		if ( isset($integrations['enabled']) && ! $integrations['enabled'] ) {
			return false;
		}
		// Get credentials from options
		$this->client_id     = $integrations['clientId'] ?? '';
		$this->client_secret = $integrations['clientSecret'] ?? '';
		$this->redirect_uri  = admin_url( 'admin.php?page=radius-booking' );
		$this->access_token  = get_option( 'rtrb_google_access_token' );
		$this->refresh_token = get_option( 'rtrb_google_refresh_token' );
		add_action( 'admin_init', array( $this, 'maybe_handle_oauth_callback' ) );
		add_action( 'rest_api_init', array( $this, 'register_webhook_endpoint' ) );
	}


	/**
	 * Only handle OAuth callback when GET params exist
	 */
	public function maybe_handle_oauth_callback() {
		// Google OAuth returns the nonce via the `state` parameter, not a custom query param.
		if ( isset( $_GET['page'], $_GET['code'], $_GET['state'] ) ) {
			$nonce = sanitize_text_field( wp_unslash( $_GET['state'] ) );
			if ( ! wp_verify_nonce( $nonce, 'rtrb_google_oauth' ) ) {
				return;
			}
			$this->handle_oauth_callback();
		}
	}

	/**
	 * Get OAuth Authorization URL
	 */
	public function get_auth_url() {
		$nonce = wp_create_nonce( 'rtrb_google_oauth' );

		$params = array(
			'client_id'     => $this->client_id,
			'redirect_uri'  => $this->redirect_uri,
			'response_type' => 'code',
			'scope'         => implode(
				' ',
				array(
					'https://www.googleapis.com/auth/calendar',
					'https://www.googleapis.com/auth/calendar.events',
				)
			),
			'access_type'   => 'offline',
			'prompt'        => 'consent',
			'state'         => $nonce,
		);

		return 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query( $params );
	}

	/**
	 * Handle OAuth Callback
	 */
	public function handle_oauth_callback() {
		$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
		$code = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';

		if ( 'radius-booking' !== $page ) {
			return;
		}

		// Permission check
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to perform this action', 'radius-booking' ) );
		}

		if ( $code ) {
			$tokens = $this->exchange_code_for_token( $code );
			if ( $tokens ) {
				update_option( 'rtrb_google_access_token', $tokens['access_token'] );
				update_option( 'rtrb_google_refresh_token', $tokens['refresh_token'] );
				update_option( 'rtrb_google_token_expires', time() + $tokens['expires_in'] );

				wp_safe_redirect( admin_url( 'admin.php?page=radius-booking#/settings' ) );
				exit;
			}
		}
	}

	/**
	 * Exchanges the authorization code for access and refresh tokens.
	 *
	 * @param string $code The authorization code received from the OAuth provider.
	 *
	 * @return array|false An associative array containing the tokens and related information on success, or false on failure.
	 */
	private function exchange_code_for_token( $code ) {
		$response = wp_remote_post(
			'https://oauth2.googleapis.com/token',
			array(
				'body' => array(
					'code'          => $code,
					'client_id'     => $this->client_id,
					'client_secret' => $this->client_secret,
					'redirect_uri'  => $this->redirect_uri,
					'grant_type'    => 'authorization_code',
				),
			)
		);
		if ( is_wp_error( $response ) ) {
			return false;
		}

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

	/**
	 * Refresh Access Token
	 */
	private function refresh_access_token() {
		if ( ! $this->refresh_token ) {
			return false;
		}

		$response = wp_remote_post(
			'https://oauth2.googleapis.com/token',
			array(
				'body' => array(
					'refresh_token' => $this->refresh_token,
					'client_id'     => $this->client_id,
					'client_secret' => $this->client_secret,
					'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->access_token = $body['access_token'];
			update_option( 'rtrb_google_access_token', $body['access_token'] );
			update_option( 'rtrb_google_token_expires', time() + $body['expires_in'] );

			return true;
		}

		return false;
	}

	/**
	 * Get Valid Access Token
	 */
	private function get_access_token() {
		$expires = get_option( 'rtrb_google_token_expires', 0 );

		// Refresh if token expired or will expire in next 5 minutes
		if ( time() >= ( $expires - 300 ) ) {
			$this->refresh_access_token();
		}

		return $this->access_token;
	}

	/**
	 * Makes an HTTP request to the Google Calendar API.
	 *
	 * @param string $endpoint The API endpoint to make the request to.
	 * @param string $method The HTTP method to use for the request. Defaults to 'GET'.
	 * @param array $data Optional data to include in the request body for POST, PUT, or PATCH methods.
	 *
	 * @return array An array containing the HTTP status code and the response data, or an error message if the request fails.
	 */
	private function make_request( $endpoint, $method = 'GET', $data = array() ) {
		$token = $this->get_access_token();

		if ( ! $token ) {
			return array( 'error' => 'No access token available' );
		}

		$url = 'https://www.googleapis.com/calendar/v3' . $endpoint;

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

		if ( $method === 'POST' || $method === 'PUT' || $method === 'PATCH' ) {
			$args['body'] = json_encode( $data );
		}

		$response = wp_remote_request( $url, $args );

		if ( is_wp_error( $response ) ) {
			return array( 'error' => $response->get_error_message() );
		}

		$body        = wp_remote_retrieve_body( $response );
		$status_code = wp_remote_retrieve_response_code( $response );

		return array(
			'status_code' => $status_code,
			'data'        => json_decode( $body, true ),
		);
	}

	/**
	 * Create an event in a calendar with optional Google Meet and attendees.
	 *
	 * @param bool $is_meet Whether to create a Google Meet link for the event.
	 * @param string $calendar_id The ID of the calendar where the event will be created.
	 * @param string $summary The summary or title of the event.
	 * @param string $start_time The event start time in ISO 8601 format (e.g., '2023-10-01T10:00:00Z').
	 * @param string $end_time The event end time in ISO 8601 format.
	 * @param string $description Optional. The description of the event. Default is an empty string.
	 * @param array $attendees Optional. An array of attendee email addresses. Default is an empty array.
	 * @param array $options Optional. Additional options for the event, such as reminders and timezone. Default is an empty array.
	 *
	 * @return array An array containing the created event data if successful, or the API response with an error if unsuccessful.
	 */
	public function create_event( $is_meet, $calendar_id, $summary, $start_time, $end_time, $description = '', $attendees = array(), $options = array(), ) {
		$default_options = array(
			'add_meet'           => $is_meet,
			'send_notifications' => true,
			'timezone'           => wp_timezone_string(),
			'reminders'          => array(
				'useDefault' => false,
				'overrides'  => array(
					array(
						'method'  => 'email',
						'minutes' => 24 * 60,
					),
					array(
						'method'  => 'popup',
						'minutes' => 30,
					),
				),
			),
		);

		$options = wp_parse_args( $options, $default_options );

		$event_data = array(
			'summary'     => $summary,
			'description' => $description,
			'start'       => array(
				'dateTime' => $start_time,
				'timeZone' => $options['timezone'],
			),
			'end'         => array(
				'dateTime' => $end_time,
				'timeZone' => $options['timezone'],
			),
			'reminders'   => $options['reminders'],
		);

		// Add Google Meet
		if ( $is_meet ) {
			if ( $options['add_meet'] ) {
				$event_data['conferenceData'] = array(
					'createRequest' => array(
						'requestId'             => $this->generate_request_id(),
						'conferenceSolutionKey' => array(
							'type' => 'hangoutsMeet',
						),
					),
				);
			}
		}

		// Add attendees
		if ( ! empty( $attendees ) ) {
			$event_data['attendees'] = array();
			foreach ( $attendees as $email ) {
				$event_data['attendees'][] = array(
					'email'          => $email,
					'responseStatus' => 'needsAction',
				);
			}
		}

		$endpoint = '/calendars/' . urlencode( $calendar_id ) . '/events';
		if ( $options['add_meet'] ) {
			$endpoint .= '?conferenceDataVersion=1';
		}
		if ( $options['send_notifications'] ) {
			$endpoint .= ( strpos( $endpoint, '?' ) ? '&' : '?' ) . 'sendUpdates=all';
		}

		$response = $this->make_request( $endpoint, 'POST', $event_data );

		if ( $response['status_code'] === 200 ) {
			// Store event in database
			$this->store_event( $response['data'] );

			return $response['data'];
		}

		return $response;
	}

	/**
	 * List events from a specified calendar.
	 *
	 * @param string $calendar_id The ID of the calendar to retrieve events from.
	 * @param string|null $time_min Optional. The start time for filtering events in ISO 8601 format.
	 * @param string|null $time_max Optional. The end time for filtering events in ISO 8601 format.
	 * @param int $max_results Optional. The maximum number of events to retrieve. Default is 10.
	 *
	 * @return array Returns an array of events if the request is successful, otherwise an empty array.
	 */
	public function list_events( $calendar_id, $time_min = null, $time_max = null, $max_results = 10 ) {
		$endpoint = '/calendars/' . urlencode( $calendar_id ) . '/events?maxResults=' . $max_results;

		if ( $time_min ) {
			$endpoint .= '&timeMin=' . urlencode( $time_min );
		}
		if ( $time_max ) {
			$endpoint .= '&timeMax=' . urlencode( $time_max );
		}

		$response = $this->make_request( $endpoint, 'GET' );

		if ( $response['status_code'] === 200 ) {
			return $response['data']['items'];
		}

		return array();
	}

	/**
	 * Retrieves a list of calendars associated with the authenticated user.
	 *
	 * @return array An array of calendar items if the request is successful, otherwise an empty array.
	 */
	public function list_calendars() {
		$response = $this->make_request( '/users/me/calendarList', 'GET' );

		if ( $response['status_code'] === 200 ) {
			return $response['data']['items'];
		}

		return array();
	}

	/**
	 * Get Primary Calendar ID
	 */
	public function get_primary_calendar() {
		return 'primary';
	}

	/**
	 * Retrieves a meeting link from the provided event data.
	 *
	 * @param array $event_data The event data containing conference or hangout link details.
	 *
	 * @return string|null The meeting link if found, or null if no link is available.
	 */
	public function get_meet_link( $event_data ) {
		if ( isset( $event_data['conferenceData']['entryPoints'] ) ) {
			foreach ( $event_data['conferenceData']['entryPoints'] as $entry ) {
				if ( $entry['entryPointType'] === 'video' ) {
					return $entry['uri'];
				}
			}
		}

		if ( isset( $event_data['hangoutLink'] ) ) {
			return $event_data['hangoutLink'];
		}

		return null;
	}

	/**
	 * Generate a unique request identifier.
	 *
	 * @return string A uniquely generated request ID following the format 'meet-{unique_id}-{timestamp}'.
	 */
	private function generate_request_id() {
		return 'meet-' . uniqid() . '-' . time();
	}

	/**
	 * Store Event
	 *
	 * @param array $event_data Associative array containing event details including event ID, organizer email, summary, start and end times, and links.
	 *
	 * @return void
	 */
	private function store_event( $event_data ) {
		global $wpdb;
		$table_name = $wpdb->prefix . 'google_events';

		$meet_link = $this->get_meet_link( $event_data );

		$wpdb->insert(
			$table_name,
			array(
				'event_id'    => $event_data['id'],
				'calendar_id' => $event_data['organizer']['email'],
				'summary'     => $event_data['summary'],
				'start_time'  => $event_data['start']['dateTime'],
				'end_time'    => $event_data['end']['dateTime'],
				'meet_link'   => $meet_link,
				'html_link'   => $event_data['htmlLink'],
				'created_at'  => current_time( 'mysql' ),
			)
		);
	}

	/**
	 * Registers the webhook endpoint for handling incoming POST requests.
	 *
	 * @return void
	 */
	public function register_webhook_endpoint() {
		register_rest_route(
			'radius-booking/google/v1',
			'/webhook',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_webhook' ),
				// 'permission_callback' => '__return_true',
				'permission_callback' => function () {
					return current_user_can( 'manage_options' );
				},
			)
		);
	}

	/**
	 * Handle incoming webhook requests from Google Calendar.
	 *
	 * @param WP_REST_Request $request The HTTP request object containing webhook headers and data.
	 *
	 * @return WP_REST_Response The HTTP response object with success or error status.
	 */
	public function handle_webhook( $request ) {
		$headers = $request->get_headers();

		// Verify the notification is from Google
		if ( ! isset( $headers['x_goog_channel_id'] ) || ! isset( $headers['x_goog_resource_state'] ) ) {
			return new WP_REST_Response( array( 'error' => 'Invalid webhook' ), 400 );
		}

		$state = $headers['x_goog_resource_state'][0];

		error_log( 'Google Calendar Webhook: ' . $state );

		switch ( $state ) {
			case 'sync':
				// Initial sync message
				break;

			case 'exists':
				// Calendar changed, fetch updates
				$this->sync_calendar_changes();
				break;
		}

		return new WP_REST_Response( array( 'status' => 'success' ), 200 );
	}

	/**
	 * Synchronize changes from the Google Calendar.
	 *
	 * This method fetches updated events from a specified Google Calendar using a
	 * synchronization token and triggers the appropriate action for each updated event.
	 * If a new synchronization token is provided in the response, it is saved for future use.
	 *
	 * @return void
	 */
	private function sync_calendar_changes() {
		$calendar_id = get_option( 'rtrb_google_primary_calendar', 'primary' );
		$sync_token  = get_option( 'rtrb_google_calendar_sync_token' );

		$endpoint = '/calendars/' . urlencode( $calendar_id ) . '/events';
		if ( $sync_token ) {
			$endpoint .= '?syncToken=' . $sync_token;
		}

		$response = $this->make_request( $endpoint, 'GET' );

		if ( $response['status_code'] === 200 && isset( $response['data']['items'] ) ) {
			foreach ( $response['data']['items'] as $event ) {
				do_action( 'rtrb_google_calendar_event_updated', $event );
			}

			// Save new sync token
			if ( isset( $response['data']['nextSyncToken'] ) ) {
				update_option( 'rtrb_google_calendar_sync_token', $response['data']['nextSyncToken'] );
			}
		}
	}
}
