<?php
/**
 * The core plugin class.
 *
 * This is used to define hooks, dependants and templates.
 *
 * @since      1.2
 * @copyright (c) 2020 Dan Porter, Jack Mawhinney <info@mdjm.co.uk>
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class MDJM_GCal_Init {

	public $gcal_connected = false;

	/**
	 * @var Google_Client
	 */
	public $client;

	/**
	 * @var Google_Service_Calendar
	 */
	public $gcal_calendar;

	/**
	 * Constructor.
	 *
	 * @since 1.1
	 */
	public function __construct() {
		$this->mdjm_checks();

		$this->clientid      = mdjm_get_option( 'gcal_client_id' );
		$this->clientsecret  = mdjm_get_option( 'gcal_client_secret' );
		$this->refreshToken  = get_option( 'mdjm_gcal_oauth_refresh_token' );
		$this->authCode      = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
		$this->sync_status   = mdjm_get_option( 'gcal_event_status', array() );
		$this->full_sync     = false;
		$this->gcal_id       = '';
		$this->timezone      = '';

		$this->googlecal_init();

		add_action( 'rest_api_init', array( new MDJM_ICS_Feed(), 'mdjm_register_calendar_feed' ) );
	}

	/**
	 * Initialise connection to Google oAuth & Calendar.
	 *
	 * @since 1.1
	 * @access private
	 */
	private function googlecal_init() {
		$this->redirecturi = add_query_arg(
			array(
				'post_type' => 'mdjm-event',
				'page'      => 'mdjm-settings',
				'tab'       => 'extensions',
				'section'   => 'gcal_sync',
			),
			admin_url( 'edit.php' )
		);

		$this->set_hooks();

		// Composer autoloader (you confirmed this is where it lives).
		require_once MDJM_GCAL_PATH . '/libs/Google/vendor/autoload.php';

		$this->gcal_create_client();

		// Load stored tokens into class properties (for UI / notices).
		$this->gcal_get_tokens();

		// Handle auth callback OR refresh token if needed.
		$this->gcal_get_auth_token();

		// Initialise Google Calendar service and settings.
		$this->run_gcal();
	}

	/**
	 * Finish setup after tokens are available.
	 *
	 * @since 1.1
	 */
	public function run_gcal() {

		// If we have no refresh token at all, we cannot maintain a connection.
		if ( empty( get_option( 'mdjm_gcal_oauth_refresh_token' ) ) ) {
			add_action( 'admin_notices', 'mdjm_gcalnotice_autherror' );
			$this->gcal_connected = false;
			return false;
		}

		// Create the Calendar service (also refreshes token if required).
		if ( ! $this->get_gcal() ) {
			add_action( 'admin_notices', 'mdjm_gcalnotice_accesstoken' );
			$this->gcal_connected = false;
			return false;
		}

		$this->gcal_connected = true;

		// Settings.
		$this->full_sync = mdjm_get_option( 'google_calendar_full_sync' );
		$this->gcal_id   = mdjm_get_option( 'gcal_calendar' );
		$this->timezone  = $this->gcal_timezone();

		return true;
	}

	/**
	 * Load tokens into class properties (used by settings UI).
	 *
	 * @since 1.1
	 */
	public function gcal_get_tokens() {
		$this->refreshToken      = get_option( 'mdjm_gcal_oauth_refresh_token', '' );
		$this->accessToken       = '';
		$this->tokenCreatedTime  = absint( get_option( 'mdjm_gcal_token_time_created', 0 ) );

		$access_token_arr = get_option( 'mdjm_gcal_oauth_access_token', array() );
		$access_token_arr = maybe_unserialize( $access_token_arr );

		if ( is_array( $access_token_arr ) && ! empty( $access_token_arr['access_token'] ) ) {
			$this->accessToken = $access_token_arr['access_token'];
		}
	}

	/**
	 * Google oAuth Client Credentials Setup.
	 *
	 * @since 1.1
	 */
	public function gcal_create_client() {

		$this->client = new Google_Client();

		$this->client->setApplicationName( MDJM_NAME );
		$this->client->setClientId( $this->clientid );
		$this->client->setClientSecret( $this->clientsecret );
		$this->client->setRedirectUri( $this->redirecturi );

		// Offline access so we can receive a refresh token.
		$this->client->setAccessType( 'offline' );

		// Force consent screen when needed (helps ensure refresh_token is issued).
		if ( method_exists( $this->client, 'setPrompt' ) ) {
			$this->client->setPrompt( 'consent' );
		} else {
			// Back-compat (older client versions).
			$this->client->setApprovalPrompt( 'consent' );
		}

		// Scopes must be an array.
		$this->client->setScopes(
			array(
				Google_Service_Calendar::CALENDAR,
			)
		);
	}

	/**
	 * Exchange auth code for tokens, or refresh tokens when needed.
	 *
	 * @since 1.1
	 */
	public function gcal_get_auth_token() {

		$this->gcal_connected = false;

		/*
		 * 1) Handle callback from Google (auth code exchange).
		 */
		if ( isset( $_GET['code'] ) ) {
			$auth_code = sanitize_text_field( wp_unslash( $_GET['code'] ) );

			try {
				$token = $this->client->fetchAccessTokenWithAuthCode( $auth_code );
			} catch ( Exception $e ) {
				MDJM()->debug->log_it( 'Unable to fetch access token: ' . $e->getMessage() );
				return false;
			}

			if ( empty( $token ) || ( is_array( $token ) && ! empty( $token['error'] ) ) ) {
				MDJM()->debug->log_it( 'Google auth error: ' . wp_json_encode( $token ) );
				return false;
			}

			// Set token on client.
			$this->client->setAccessToken( $token );

			// Persist access token and time created.
			update_option( 'mdjm_gcal_oauth_access_token', $token, false );
			update_option( 'mdjm_gcal_token_time_created', time(), false );

			// Persist refresh token ONLY if provided (Google often returns it only on first consent).
			if ( ! empty( $token['refresh_token'] ) ) {
				update_option(
					'mdjm_gcal_oauth_refresh_token',
					sanitize_text_field( $token['refresh_token'] ),
					false
				);
			}

			$this->gcal_connected = true;

			$reload_success = add_query_arg(
				array(
					'post_type' => 'mdjm-event',
					'page'      => 'mdjm-settings',
					'tab'       => 'extensions',
					'section'   => 'gcal_sync',
				),
				admin_url( 'edit.php' )
			);

			// Reload without the code url param.
			wp_safe_redirect( $reload_success );
			exit;
		}

		/*
		 * 2) Normal request: load stored token + refresh if needed.
		 */
		$stored_token  = get_option( 'mdjm_gcal_oauth_access_token', array() );
		$refresh_token = get_option( 'mdjm_gcal_oauth_refresh_token', '' );
		$created_time  = absint( get_option( 'mdjm_gcal_token_time_created', 0 ) );

		// Normalise stored token.
		if ( is_string( $stored_token ) ) {
			$decoded      = json_decode( $stored_token, true );
			$stored_token = is_array( $decoded ) ? $decoded : array();
		} elseif ( ! is_array( $stored_token ) ) {
			$stored_token = array();
		}

		if ( ! empty( $stored_token['access_token'] ) ) {
			$this->client->setAccessToken( $stored_token );
		}

		// Refresh if expired.
		$is_expired = false;

		if ( method_exists( $this->client, 'isAccessTokenExpired' ) ) {
			$is_expired = $this->client->isAccessTokenExpired();
		} elseif ( $created_time > 0 && ( time() > ( $created_time + 3600 ) ) ) {
			$is_expired = true;
		}

		if ( $is_expired ) {
			if ( empty( $refresh_token ) ) {
				MDJM()->debug->log_it( 'Access token expired and no refresh token is stored.' );
				return false;
			}

			$new_token = $this->client->fetchAccessTokenWithRefreshToken( $refresh_token );

			if ( empty( $new_token ) || ! empty( $new_token['error'] ) ) {
				MDJM()->debug->log_it( 'Token refresh failed: ' . wp_json_encode( $new_token ) );
				return false;
			}

			// Keep refresh_token if Google doesn’t return it on refresh.
			if ( empty( $new_token['refresh_token'] ) ) {
				$new_token['refresh_token'] = $refresh_token;
			}

			$this->client->setAccessToken( $new_token );

			update_option( 'mdjm_gcal_oauth_access_token', $new_token, false );
			update_option( 'mdjm_gcal_token_time_created', time(), false );

			if ( ! empty( $new_token['refresh_token'] ) ) {
				update_option(
					'mdjm_gcal_oauth_refresh_token',
					sanitize_text_field( $new_token['refresh_token'] ),
					false
				);
			}
		}

		// Final sanity check.
		$current = $this->client->getAccessToken();
		if ( empty( $current ) || ( is_array( $current ) && empty( $current['access_token'] ) ) ) {
			MDJM()->debug->log_it( 'Google client still has no access token after load/refresh.' );
			return false;
		}

		$this->gcal_connected = true;
		return true;
	}

	/**
	 * Use refresh token from storage.
	 *
	 * @since 1.1
	 */
	public function gcal_use_refresh_token() {
		return get_option( 'mdjm_gcal_oauth_refresh_token' );
	}

	/**
	 * Create Calendar service, refreshing tokens if needed.
	 *
	 * @since 1.1
	 */
	public function get_gcal() {

		$stored_token  = get_option( 'mdjm_gcal_oauth_access_token', array() );
		$refresh_token = get_option( 'mdjm_gcal_oauth_refresh_token', '' );

		// Normalise token into array.
		if ( is_string( $stored_token ) ) {
			$decoded      = json_decode( $stored_token, true );
			$stored_token = is_array( $decoded ) ? $decoded : array();
		} elseif ( ! is_array( $stored_token ) ) {
			$stored_token = array();
		}

		// Apply token to client if present.
		if ( ! empty( $stored_token['access_token'] ) ) {
			$this->client->setAccessToken( $stored_token );
		}

		// Refresh if expired (or token missing) but we have refresh token.
		$needs_refresh = empty( $this->client->getAccessToken() )
			|| ( method_exists( $this->client, 'isAccessTokenExpired' ) && $this->client->isAccessTokenExpired() );

		if ( $needs_refresh && ! empty( $refresh_token ) ) {

			$new_token = $this->client->fetchAccessTokenWithRefreshToken( $refresh_token );

			if ( empty( $new_token['error'] ) && ! empty( $new_token['access_token'] ) ) {

				// Keep refresh token if Google doesn't return it during refresh.
				if ( empty( $new_token['refresh_token'] ) ) {
					$new_token['refresh_token'] = $refresh_token;
				}

				$this->client->setAccessToken( $new_token );

				update_option( 'mdjm_gcal_oauth_access_token', $new_token, false );
				update_option( 'mdjm_gcal_token_time_created', time(), false );

			} else {
				MDJM()->debug->log_it( 'Google token refresh failed: ' . wp_json_encode( $new_token ) );
			}
		}

		// Final sanity check.
		$current = $this->client->getAccessToken();
		if ( empty( $current ) || ( is_array( $current ) && empty( $current['access_token'] ) ) ) {
			MDJM()->debug->log_it( 'Google client has no access token set (CREDENTIALS_MISSING).' );
			$this->gcal_connected = false;
			return false;
		}

		// Create service.
		try {
			$this->gcal_calendar  = new Google_Service_Calendar( $this->client );
			$this->gcal_connected = true;
		} catch ( Google_Service_Exception $e ) {
			MDJM()->debug->log_it( 'Unable to get calendars ' . $e->getMessage() );
			$this->gcal_connected = false;
			return false;
		}

		return true;
	}

	/**
	 * Function hooks.
	 *
	 * @since 1.1
	 */
	public function set_hooks() {
		add_filter( 'plugin_action_links_' . MDJM_GCAL_BASENAME, array( __CLASS__, 'plugin_action_links' ) );
		add_filter( 'mdjm_settings_extensions', array( $this, 'mdjm_gcal_settings' ) );
		add_action( 'mdjm-unauth_gcal', array( $this, 'disconnect_auth' ) );
		add_action( 'mdjm_after_event_save', array( $this, 'gcal_syncevent' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'gcal_adminjs' ) );
		add_action( 'wp_ajax_gcal_full_sync', array( $this, 'gcal_full_sync' ) );
	}

	/**
	 * Load admin JS/CSS.
	 *
	 * @since 1.1
	 */
	public function gcal_adminjs() {

		wp_enqueue_script( 'jquery' );

		wp_enqueue_script(
			'mdjm-gcal-adminjs',
			MDJM_GCAL_URL . '/assets/admin/js/admin_actions.js',
			array(),
			MDJM_GCAL_VERSION,
			false
		);

		wp_localize_script(
			'mdjm-gcal-adminjs',
			'mdjm_gcal_vars',
			apply_filters(
				'mdjm_gcal_vars',
				array(
					'ajax_url' => admin_url( 'admin-ajax.php' ),
				)
			)
		);

		wp_enqueue_style( 'mdjm-gcal-admincss', MDJM_GCAL_URL . '/assets/admin/css/admin_styles.css', array(), MDJM_GCAL_VERSION );
	}

	/**
	 * Retrieve the calendar timezone, falling back to the WordPress timezone.
	 *
	 * @since 1.1
	 * @return string
	 */
	public function gcal_timezone() {

		$fallback = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : get_option( 'timezone_string' );

		if ( empty( $fallback ) ) {
			$offset   = (float) get_option( 'gmt_offset', 0 );
			$fallback = timezone_name_from_abbr( '', (int) ( $offset * 3600 ), 0 );

			if ( false === $fallback || empty( $fallback ) ) {
				$fallback = 'UTC';
			}
		}

		if ( ! $this->gcal_connected ) {
			return $fallback;
		}

		try {
			$timezone = $this->gcal_calendar->settings->get( 'timezone' );
		} catch ( Google_Service_Exception $e ) {
			MDJM()->debug->log_it( 'Unable to get timezone setting from calendar ' . $e->getMessage() );
			return $fallback;
		}

		$tz_value = $timezone ? $timezone->getValue() : '';

		return ! empty( $tz_value ) ? $tz_value : $fallback;
	}

	/**
	 * Fetch list of available Google Calendars.
	 *
	 * @since 1.1
	 */
	public function gcal_fetch_cals() {

		if ( ! $this->gcal_connected ) {
			return array( '0' => __( 'API Not Connected', 'mdjm-gcal-sync' ) );
		}

		$calendars = array();

		try {
			$calendar_list = $this->gcal_calendar->calendarList->listCalendarList();
		} catch ( Google_Service_Exception $e ) {
			MDJM()->debug->log_it( 'Google Calendar list error: ' . $e->getMessage() );
			$this->gcal_connected = false;
			return array( '0' => __( 'Unable to fetch calendars (see logs)', 'mdjm-gcal-sync' ) );
		} catch ( Exception $e ) {
			MDJM()->debug->log_it( 'Google Calendar list error: ' . $e->getMessage() );
			$this->gcal_connected = false;
			return array( '0' => __( 'Unable to fetch calendars (see logs)', 'mdjm-gcal-sync' ) );
		}

		if ( empty( $calendar_list ) || ! method_exists( $calendar_list, 'getItems' ) ) {
			return array( '0' => __( 'No calendars found', 'mdjm-gcal-sync' ) );
		}

		foreach ( (array) $calendar_list->getItems() as $calendar ) {
			$calendar_id = ! empty( $calendar->primary ) ? 'primary' : $calendar->id;
			$summary     = ! empty( $calendar->summary ) ? $calendar->summary : $calendar_id;

			$calendars[ $calendar_id ] = $summary;
		}

		if ( empty( $calendars ) ) {
			return array( '0' => __( 'No calendars found', 'mdjm-gcal-sync' ) );
		}

		return $calendars;
	}

	/**
	 * Sync Event with Google Calendar on add/update.
	 *
	 * @since 1.1
	 */
	public function gcal_syncevent( $post_id ) {

		if ( ! $post_id || ! $this->gcal_connected ) {
			add_action( 'admin_notices', 'mdjm_gcalnotice_accesstoken' );
			return false;
		}

		if ( 'mdjm-event' !== get_post_type( $post_id ) ) {
			return false;
		}

		$event_args = array(
			'p' => $post_id,
		);

		$events = mdjm_get_events( $event_args );

		if ( $events ) {
			foreach ( $events as $event ) {
				$this->gcal_sync_event( $event, $event->post_status );
			}
		}

		return true;
	}

	/**
	 * Convert MDJM statuses to readable Google Calendar strings.
	 *
	 * @since 1.1
	 */
	public function mdjm_event_status( $event ) {

		$event_status = get_post_status( $event->ID );

		if ( in_array( $event_status, array( 'mdjm-unattended', 'mdjm-awaitingdeposit', 'mdjm-enquiry', 'mdjm-contract' ), true ) ) {
			return 'tentative';
		}

		if ( in_array( $event_status, array( 'mdjm-cancelled', 'mdjm-failed', 'mdjm-rejected' ), true ) ) {
			return 'cancelled';
		}

		return 'confirmed';
	}

	/**
	 * Full sync to Google Calendar.
	 *
	 * @since 1.1
	 */
	public function gcal_full_sync() {

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

		$events = mdjm_get_events();

		if ( $events ) {
			foreach ( $events as $event ) {
				$this->gcal_sync_event( $event, $event->post_status );
			}
		}

		return true;
	}

	/**
	 * Sync a single event to Google Calendar API.
	 *
	 * @since 1.1
	 */
	public function gcal_sync_event( $post, $post_status ) {

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

		if ( 'auto-draft' === $post_status ) {
			return false;
		}

		$existing_entry = get_post_meta( $post->ID, '_mdjm_event_google_calendar_entry', true );

		if ( ! in_array( $post_status, $this->sync_status, true ) ) {
			if ( $existing_entry ) {
				$this->delete_entry( $existing_entry['id'], $post->ID );
			}
			return false;
		}

		if ( ! empty( $existing_entry ) ) {
			return $this->update_event( $post->ID, $existing_entry['id'] );
		}

		return $this->add_event( $post->ID );
	}

	/**
	 * Delete a calendar entry.
	 *
	 * @param string   $calendar_entry Calendar entry ID.
	 * @param int|bool $event_id Event ID.
	 * @return bool
	 */
	public function delete_entry( $calendar_entry, $event_id = false ) {

		if ( $this->event_exists( $calendar_entry ) ) {
			try {
				$this->gcal_calendar->events->delete( $this->gcal_id, $calendar_entry );
			} catch ( Google_Service_Exception $e ) {
				MDJM()->debug->log_it( 'Calendar entry could not be deleted for event ' . $event_id . '. ' . $e->getMessage() );
			}
		}

		if ( $event_id ) {
			return (bool) delete_post_meta( $event_id, '_mdjm_event_google_calendar_entry' );
		}

		return true;
	}

	/**
	 * Add event to Google Calendar.
	 *
	 * @since 1.1
	 */
	public function add_event( $id, $event = '' ) {

		if ( empty( $event ) ) {
			$event = new MDJM_Event( $id );
			if ( empty( $event ) ) {
				return false;
			}
		}

		$calendar_entry = array();

		$calendar_entry['summary']     = $this->mdjm_event_summary( $event );
		$calendar_entry['location']    = $this->mdjm_event_location( $event );
		$calendar_entry['description'] = $this->mdjm_event_description( $event );
		$calendar_entry['start']       = $this->mdjm_event_starttime( $event );
		$calendar_entry['end']         = $this->mdjm_event_endtime( $event );
		$calendar_entry['status']      = $this->mdjm_event_status( $event );

		$google_entry = new Google_Service_Calendar_Event( $calendar_entry );

		try {
			$insert = $this->gcal_calendar->events->insert( $this->gcal_id, $google_entry );
		} catch ( Google_Service_Exception $e ) {
			MDJM()->debug->log_it( 'Calendar entry could not be created for event ' . $event->ID . '. ' . $e->getMessage() );
			return false;
		}

		if ( ! empty( $insert ) ) {

			MDJM()->debug->log_it( 'Calendar entry successfully created for event ' . $event->ID );

			$calendar_event = array(
				'calendar' => $this->gcal_id,
				'id'       => $insert->id,
				'link'     => esc_url( $insert->htmlLink ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
				'iCalUID'  => $insert->iCalUID, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
			);

			add_post_meta( $event->ID, '_mdjm_event_google_calendar_entry', $calendar_event, true );

			return $insert->id;
		}

		return false;
	}

	/**
	 * Update event in Google Calendar.
	 *
	 * @since 1.1
	 */
	public function update_event( $id, $calendar_entry, $event = '' ) {

		if ( empty( $event ) ) {
			$event = new MDJM_Event( $id );
			if ( empty( $event ) ) {
				return false;
			}
		}

		if ( ! $this->event_exists( $calendar_entry ) ) {
			delete_post_meta( $event->ID, '_mdjm_event_google_calendar_entry' );
			return $this->add_event( $event->ID, $event );
		}

		$google_entry = $this->gcal_calendar->events->get( $this->gcal_id, $calendar_entry );

		$google_entry->setSummary( $this->mdjm_event_summary( $event ) );
		$google_entry->setLocation( $this->mdjm_event_location( $event ) );
		$google_entry->setDescription( $this->mdjm_event_description( $event ) );
		$google_entry->setStart( $this->mdjm_event_starttime( $event ) );
		$google_entry->setEnd( $this->mdjm_event_endtime( $event ) );
		$google_entry->setStatus( $this->mdjm_event_status( $event ) );

		try {
			$update_entry = $this->gcal_calendar->events->update( $this->gcal_id, $google_entry->getId(), $google_entry );
		} catch ( Google_Service_Exception $e ) {
			MDJM()->debug->log_it( 'Calendar entry could not be updated for event ' . $event->ID . '. ' . $e->getMessage() );
			return false;
		}

		if ( ! empty( $update_entry ) ) {

			MDJM()->debug->log_it( 'Calendar entry successfully updated for event ' . $event->ID );

			$calendar_event = array(
				'calendar' => $this->gcal_id,
				'id'       => $update_entry->id,
				'link'     => esc_url( $update_entry->htmlLink ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
				'iCalUID'  => $update_entry->iCalUID, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
			);

			update_post_meta( $event->ID, '_mdjm_event_google_calendar_entry', $calendar_event );

			return $update_entry->id;
		}

		return false;
	}

	/**
	 * Check if event exists in Google Calendar.
	 *
	 * @since 1.1
	 */
	public function event_exists( $calendar_entry ) {
		try {
			$this->gcal_calendar->events->get( $this->gcal_id, $calendar_entry );
			return true;
		} catch ( Google_Service_Exception $e ) {
			return false;
		}
	}

	/**
	 * Save Google Calendar event title based on format chosen in settings.
	 *
	 * @since 1.1
	 */
	public function mdjm_event_summary( $event ) {
		$summary = html_entity_decode(
			mdjm_do_content_tags(
				mdjm_get_option( 'gcal_entry_format' ),
				$event->ID,
				$event->client,
				$event->venue
			)
		);

		return apply_filters( 'mdjm_google_set_event_summary', $summary );
	}

	/**
	 * Save Google Calendar event location.
	 *
	 * @since 1.1
	 */
	public function mdjm_event_location( $event ) {
		if ( empty( $event ) || empty( $event->ID ) ) {
			return '';
		}

		$mdjm_event = new MDJM_Event( $event->ID );
		$venue      = array(
			mdjm_get_event_venue_meta( $mdjm_event->get_venue_id(), 'name' ),
			mdjm_get_event_venue_meta( $mdjm_event->get_venue_id(), 'town' ),
			mdjm_get_event_venue_meta( $mdjm_event->get_venue_id(), 'postcode' ),
		);

		return ! empty( $venue ) ? implode( ', ', array_filter( $venue ) ) : '';
	}

	/**
	 * Save Google Calendar event description based on Client Notes.
	 *
	 * @since 1.1
	 */
	public function mdjm_event_description( $event ) {
		$description = mdjm_do_content_tags(
			mdjm_get_option( 'google_calendar_entry_body' ),
			$event->ID,
			$event->client
		);

		$description = str_replace( PHP_EOL, "\n", $description );
		$description = utf8_encode( $description );

		return apply_filters( 'mdjm_google_set_event_description', $description );
	}

	/**
	 * Create MDJM Google Calendar start time.
	 *
	 * @since 1.1
	 * @return Google_Service_Calendar_EventDateTime
	 */
	public function mdjm_event_starttime( $event ) {

		$start = new Google_Service_Calendar_EventDateTime();

		$event_date = $event->date;
		$time       = $event->get_start_time();

		$tz_string = ! empty( $this->timezone ) ? $this->timezone : $this->gcal_timezone();

		if ( empty( $tz_string ) ) {
			$tz_string = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : 'UTC';
		}

		try {
			$tz = new DateTimeZone( $tz_string );
		} catch ( Exception $e ) {
			$tz_string = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : 'UTC';
			$tz        = new DateTimeZone( $tz_string );
		}

		$start_dt = DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $event_date . ' ' . $time, $tz );

		if ( false === $start_dt ) {
			$start_dt = new DateTimeImmutable( $event_date . ' ' . $time, $tz );
		}

		$start->setTimeZone( $tz_string );
		$start->setDateTime( $start_dt->format( DateTimeInterface::RFC3339 ) );

		return $start;
	}

	/**
	 * Create MDJM Google Calendar end time.
	 *
	 * @since 1.1
	 * @return Google_Service_Calendar_EventDateTime
	 */
	public function mdjm_event_endtime( $event ) {

		$end = new Google_Service_Calendar_EventDateTime();

		$time     = $event->get_finish_time();
		$end_date = get_post_meta( $event->ID, '_mdjm_event_end_date', true );

		if ( empty( $end_date ) ) {
			$end_date = $event->date;
		}

		$tz_string = ! empty( $this->timezone ) ? $this->timezone : $this->gcal_timezone();

		if ( empty( $tz_string ) ) {
			$tz_string = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : 'UTC';
		}

		try {
			$tz = new DateTimeZone( $tz_string );
		} catch ( Exception $e ) {
			$tz_string = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : 'UTC';
			$tz        = new DateTimeZone( $tz_string );
		}

		$end_dt = DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $end_date . ' ' . $time, $tz );

		if ( false === $end_dt ) {
			$end_dt = new DateTimeImmutable( $end_date . ' ' . $time, $tz );
		}

		$end->setTimeZone( $tz_string );
		$end->setDateTime( $end_dt->format( DateTimeInterface::RFC3339 ) );

		return $end;
	}

	/**
	 * Create MDJM Google Calendar Integration Settings Page.
	 *
	 * @since 1.1
	 */
	public function mdjm_gcal_settings( $settings ) {

		$event_single = mdjm_get_label_singular();

		$client = new Google_Client();
		$client->setApplicationName( MDJM_NAME );
		$client->setClientId( $this->clientid );
		$client->setClientSecret( $this->clientsecret );
		$client->setRedirectUri( $this->redirecturi );
		$client->setAccessType( 'offline' );

		if ( method_exists( $client, 'setPrompt' ) ) {
			$client->setPrompt( 'consent' );
		} else {
			$client->setApprovalPrompt( 'consent' );
		}

		$client->setScopes(
			array(
				Google_Service_Calendar::CALENDAR,
			)
		);

		$auth_url = $client->createAuthUrl();

		if ( empty( $this->accessToken ) || ! $this->gcal_connected ) {
			$gcal_auth_button = '<a class="gcalconnectbtn connected" href="' . esc_url( $auth_url ) . '">Connect</a>';
		} else {
			$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) : '';
			$gcal_auth_button = '<a class="gcalconnectbtn notconnected" href="' . wp_nonce_url(
				add_query_arg(
					array( 'mdjm-action' => 'unauth_gcal' ),
					$request_uri
				),
				'unauth_gcal',
				'mdjm-gcal-action'
			) . '">Disconnect</a>';
		}

		$gcal_auth_button .= '<div class="changemsg" style="margin-top:15px;color:#fc0000;font-weight:bold;"></div>';

		$mdjm_gcal_settings = array(
			'gcal_sync' => array(
				array(
					'id'   => 'mdjm_gcal_settings_title',
					'name' => '<h3><u>' . esc_html__( 'Google Calendar Integration Settings', 'mdjm-gcal-sync' ) . '</u></h3><p>Before setting up, please ensure you have setup a <a href="https://www.mdjm.co.uk/articles/google-calendar-integration/" target="_blank">Google Calendar API</a>.</p>',
					'type' => 'header',
					'desc' => '',
				),
				array(
					'id'   => 'gcal_get_auth',
					'name' => $gcal_auth_button,
					'desc' => __( 'This generates an auth code to connect to your Google Calendar', 'mdjm-gcal-sync' ),
					'type' => 'header',
				),
				array(
					'id'       => 'gcal_client_id',
					'name'     => __( 'Client ID', 'mdjm-gcal-sync' ),
					'type'     => 'text',
					'desc'     => __( 'Find out how to get this <a href="https://www.mdjm.co.uk/articles/google-calendar-integration/" target="_blank">here</a>', 'mdjm-gcal-sync' ),
					'readonly' => $this->gcal_connected ? true : false,
				),
				array(
					'id'       => 'gcal_client_secret',
					'name'     => __( 'Client Secret', 'mdjm-gcal-sync' ),
					'type'     => 'password',
					'desc'     => __( 'Find out how to get this <a href="https://www.mdjm.co.uk/articles/google-calendar-integration/" target="_blank">here</a>', 'mdjm-gcal-sync' ),
					'readonly' => $this->gcal_connected ? true : false,
				),
				array(
					'id'   => 'mdjm_gcal_redirecturi',
					'name' => __( 'Redirect URI', 'mdjm-gcal-sync' ),
					'hint' => __( 'Use this when setting up your OAuth Credentials' ),
					'type' => 'messagetext',
					'desc' => esc_url( $this->redirecturi ),
				),
				array(
					'id'      => 'gcal_calendar',
					'name'    => __( 'Calendar', 'mdjm-gcal-sync' ),
					'desc'    => __( 'Select the Google calendar to which we should synchronise', 'mdjm-gcal-sync' ),
					'type'    => 'select',
					'options' => $this->gcal_connected ? $this->gcal_fetch_cals() : array( '0' => 'API Not Connected' ),
				),
				array(
					'id'   => 'gcal_manual_actions',
					'name' => __( 'Manual Actions', 'mdjm-gcal-sync' ),
					'type' => 'gcalbtns',
					'btns' => array( 'Click to perform a full sync' => 'fullsyncbtn' ),
				),
				array(
					'id'   => 'gcal_entry_format',
					'name' => __( 'Entry Format', 'mdjm-gcal-sync' ),
					'desc' => __( 'How you would like the calendar entry to display. Full list of shortcodes can be found here - ', 'mdjm-gcal-sync' ),
					'type' => 'text',
					'std'  => '{event_type} - {venue} - ({event_status})',
				),
				array(
					'id'      => 'gcal_event_status',
					'name'    => __( 'Sync Events with Status', 'mdjm-gcal-sync' ),
					/* translators: %s: event */
					'desc'    => sprintf( __( "CTRL (cmd on MAC) + Click to select %s status' that you want to synchronise with your calendar", 'mdjm-gcal-sync' ), strtolower( $event_single ) ),
					'type'    => 'multiple_select',
					'options' => mdjm_all_event_status(),
				),
				array(
					'id'   => 'google_calendar_entry_body',
					'name' => __( 'Content', 'mdjm-gcal-sync' ),
					'desc' => __( 'The content for the calendar entry', 'mdjm-gcal-sync' ),
					'type' => 'textarea',
					'std'  => sprintf( '%s ID: {contract_id}', $event_single ) . PHP_EOL .
						'Employee: {dj_fullname}' . PHP_EOL .
						'Balance: {balance}' . PHP_EOL .
						'Setup: {dj_setup_time}' . PHP_EOL .
						'Employee Notes: {dj_notes}',
				),
			),
		);

		return array_merge( $settings, $mdjm_gcal_settings );
	}

	/**
	 * Disconnect from API & remove tokens.
	 *
	 * @since 1.1
	 */
	public function disconnect_auth( $data ) {

		if ( ! isset( $data['mdjm-gcal-action'] ) || ! wp_verify_nonce( $data['mdjm-gcal-action'], 'unauth_gcal' ) ) {
			return;
		}

		delete_option( 'mdjm_gcal_oauth_refresh_token' );
		delete_option( 'mdjm_gcal_oauth_access_token' );
		delete_option( 'mdjm_gcal_token_time_created' );

		$this->gcal_connected = false;
		unset( $this->client );

		$back = add_query_arg(
			array(
				'post_type' => 'mdjm-event',
				'page'      => 'mdjm-settings',
				'tab'       => 'extensions',
				'section'   => 'gcal_sync',
			),
			admin_url( 'edit.php' )
		);

		wp_safe_redirect( $back );
		exit;
	}

	/**
	 * Do checks for MDJM Installation.
	 *
	 * @since 1.1
	 * @access private
	 */
	private function mdjm_checks() {
		global $wp_version;

		if ( ! class_exists( 'Mobile_DJ_Manager', false ) ) {
			deactivate_plugins( plugin_basename( MDJM_GCAL_FILE ) );
			wp_die(
				'<h1>Plugin cannot be activated</h1><p>Sorry, Mobile DJ Manager is required to install this plugin.</p>',
				'Plugin Activation Error',
				array( 'back_link' => true )
			);
		}

		// Do not activate if MDJM is not activated or out of date.
		if ( ! class_exists( 'Mobile_DJ_Manager', false ) || version_compare( MDJM_VERSION_NUM, MDJM_REQUIRED, '<' ) ) {
			deactivate_plugins( plugin_basename( MDJM_GCAL_FILE ) );
			wp_die(
				'<h1>Plugin cannot be activated</h1><p>Sorry, your Mobile DJ Manager is out of date. To install this plugin, please update MDJM.</p>',
				'Plugin Activation Error',
				array( 'back_link' => true )
			);
		}

		// Check minimum MDJM plugin version.
		if ( version_compare( MDJM_VERSION_NUM, MDJM_REQUIRED, '<' ) ) {
			$message = sprintf(
				__( '<h1>Plugin cannot be activated</h1><p>MDJM - Google Calendar Integration requires Mobile DJ Manager version %1$s and higher. You are currently running version %2$s. You must update Mobile DJ Manager to use this plugin.</p><p><a href="%3$s">&laquo; Back to Plugins page</a></p>', 'mdjm-gcal-sync' ),
				MDJM_REQUIRED,
				MDJM_VERSION_NUM,
				admin_url( 'plugins.php' )
			);

			deactivate_plugins( plugin_basename( MDJM_GCAL_FILE ) );
			wp_die( $message, array( 'back_link' => true ) );
		}

		// Do not activate if PHP is below minimum.
		if ( version_compare( PHP_VERSION, MDJM_PHP_MIN, '<' ) ) {
			$message = sprintf(
				__( '<h1>Plugin cannot be activated</h1><p>MDJM - Google Calendar Integration requires PHP version %1$s and higher. You are currently running version %2$s. You must update PHP to use this plugin.</p><p><a href="%3$s">&laquo; Back to Plugins page</a></p>', 'mdjm-gcal-sync' ),
				MDJM_PHP_MIN,
				PHP_VERSION,
				admin_url( 'plugins.php' )
			);

			deactivate_plugins( plugin_basename( MDJM_GCAL_FILE ) );
			wp_die( $message, array( 'back_link' => true ) );
		}

		// Do not activate if WordPress is below minimum.
		if ( version_compare( $wp_version, MDJM_WP_MIN, '<' ) ) {
			$message = sprintf(
				__( '<h1>Plugin cannot be activated</h1><p>MDJM - Google Calendar Integration requires WordPress version %1$s and higher. You are currently running version %2$s. You must update WordPress to use this plugin.</p><p><a href="%3$s">&laquo; Back to Plugins page</a></p>', 'mdjm-gcal-sync' ),
				MDJM_WP_MIN,
				$wp_version,
				admin_url( 'plugins.php' )
			);

			deactivate_plugins( plugin_basename( MDJM_GCAL_FILE ) );
			wp_die( $message, array( 'back_link' => true ) );
		}
	}

	/**
	 * Add settings section.
	 *
	 * @since 1.1
	 */
	public static function plugin_action_links( $links ) {
		$link = add_query_arg(
			array(
				'post_type' => 'mdjm-event',
				'page'      => 'mdjm-settings',
				'tab'       => 'extensions',
				'section'   => 'gcal_sync',
			),
			admin_url( 'edit.php' )
		);

		$calendar_links = array(
			'<a href="' . esc_url( $link ) . '">' . __( 'Settings', 'mdjm-gcal-sync' ) . '</a>',
		);

		return array_merge( $links, $calendar_links );
	}
} // end of MDJM_GCal_Init

/**
 * Add settings section.
 *
 * @since 1.1
 */
function mdjm_gcal_section( $sections ) {
	$sections['gcal_sync'] = __( 'Google Calendar Integration Settings', 'mdjm-gcal-sync' );
	return $sections;
}
add_filter( 'mdjm_settings_sections_extensions', 'mdjm_gcal_section' );

/**
 * Create custom callback for manual buttons in settings.
 *
 * @since 1.1
 */
function mdjm_gcalbtns_callback( $args ) {

	// NOTE: This instantiates the class again (existing plugin behaviour).
	$MDJM_Extension = new MDJM_GCal_Init(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase

	if ( isset( $args['faux'] ) && true === $args['faux'] ) {
		$name = '';
	} else {
		$name = 'name="mdjm_settings[' . $args['id'] . ']"';
	}

	if ( ! is_array( $args['btns'] ) ) {
		return false;
	}

	$readonly = ( isset( $args['readonly'] ) && true === $args['readonly'] ) ? ' readonly="readonly"' : '';
	$html     = '';

	foreach ( $args['btns'] as $label => $action ) {
		$html .= '<input type="button" class="button button-primary" id="mdjm_settings[' . $args['id'] . ']_' . esc_attr( $action ) . '" name="' . esc_attr( $action ) . '" value="' . esc_attr( $label ) . '"' . $readonly . '/>';
	}

	$html .= "<div class='gcalbtn-message'></div>";

	echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

/**
 * Create custom callback for custom 2 column in settings.
 *
 * @since 1.1
 */
function mdjm_messagetext_callback( $args ) {

	if ( isset( $args['faux'] ) && true === $args['faux'] ) {
		$name = '';
	} else {
		$name = 'name="mdjm_settings[' . $args['id'] . ']"';
	}

	$html = '';
	if ( isset( $args['desc'] ) ) {
		$html = $args['desc'];
	}

	echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

/**
 * Admin notices.
 *
 * @since 1.1
 */
function mdjm_gcalnotice_secretid() {
	?>
	<div class="notice error my-acf-notice is-dismissible">
		<p><?php esc_html_e( '<strong>MDJM Events</strong> - Your client ID or Secret is incorrect or does not have the correct OAuth permissions.', 'mdjm-gcal-sync' ); ?></p>
	</div>
	<?php
}

function mdjm_gcalnotice_accesstoken() {
	?>
	<div class="notice error my-acf-notice is-dismissible">
		<p><?php esc_html_e( '<strong>Mobile DJ Manager is currently not syncing with your Google Calendar. Please check your settings.</strong>', 'mdjm-gcal-sync' ); ?></p>
	</div>
	<?php
}

function mdjm_gcalnotice_autherror() {
	?>
	<div class="notice notice-error my-acf-notice is-dismissible">
		<p>
			<?php
			$message = __(
				'<strong>Mobile DJ Manager is having trouble authenticating your Google API. Please check your credentials. If you still have problems, try revoking access to your Google App <a href="https://myaccount.google.com/u/1/permissions" target="_blank" rel="noopener noreferrer">on this page</a>, then try connecting again.</strong>',
				'mdjm-gcal-sync'
			);

			echo wp_kses(
				$message,
				array(
					'strong' => array(),
					'a'      => array(
						'href'   => true,
						'target' => true,
						'rel'    => true,
					),
				)
			);
			?>
		</p>
	</div>
	<?php
}