<?php
/**
 * Simply Schedule Appointments Support Status.
 *
 * @since   2.1.6
 * @package Simply_Schedule_Appointments
 */

/**
 * Simply Schedule Appointments Support Status.
 *
 * @since 2.1.6
 */
class SSA_Support_Status {
	/**
	 * Parent plugin class.
	 *
	 * @since 2.1.6
	 *
	 * @var   Simply_Schedule_Appointments
	 */
	protected $plugin = null;

	/**
	 * Constructor.
	 *
	 * @since  2.1.6
	 *
	 * @param  Simply_Schedule_Appointments $plugin Main plugin object.
	 */
	public function __construct( $plugin ) {
		$this->plugin = $plugin;
		$this->hooks();
	}

	/**
	 * Initiate our hooks.
	 *
	 * @since  2.1.6
	 */
	public function hooks() {
		add_action( 'admin_init', array( $this, 'validate_directory_name' ), 0 );

	}

	/**
	 * Get file path
	 *
	 * @param  string $filename Filename
	 *
	 * @return string
	 */
	public function get_log_file_path($filename = 'debug')
	{
		$path = SSA_Filesystem::get_uploads_dir_path();
		if (empty($path)) {
			return false;
		}

		$path .= '/logs';
		if (!wp_mkdir_p($path)) {
			return false;
		}

		if (!file_exists($path . '/index.html')) {
			$handle = @fopen($path . '/index.html', 'w');
			@fwrite($handle, '');
			@fclose($handle);
		}
		
		if ( defined('AUTH_KEY') ) {
			$filename .= '-' . substr(sha1(AUTH_KEY), 0, 10);
		}

		return $path . '/' . sanitize_title($filename) . '.log';
	}


	/**
	 * Performs a list o site and plugin status checks and return the results.
	 *
	 * @return array
	 */
	public function get_site_status() {
		$site_status = new TD_Health_Check_Site_Status();

		$status = array(
			'plugin_version'       => $site_status->test_ssa_plugin_version(),
			'php_version'          => $site_status->test_php_version(),
			'wordpress_version'    => $site_status->test_wordpress_version(),
			'sql_server'           => $site_status->test_sql_server(),
			'json_extension'       => $site_status->test_json_extension(),
			'utf8mb4_support'      => $site_status->test_utf8mb4_support(),
			'dotorg_communication' => $site_status->test_dotorg_communication(),
			'https_status'         => $site_status->test_https_status(),
			'ssl_support'          => $site_status->test_ssl_support(),
			'scheduled_events'     => $site_status->test_scheduled_events(),
			'php_timezone'         => $site_status->test_php_default_timezone()
		);

		// If Paid edition, test site license
		if ( $this->plugin->settings_installed->is_installed( 'license' ) ) {
			$status = array_merge( array( 'ssa_license' => $this->test_site_license() ), $status );
		}
		// if google calendar is installed and enabled
		if ( $this->plugin->settings_installed->is_enabled( 'google_calendar' ) ) {
			
			$google_calendar_settings = ssa()->google_calendar_settings->get();
			// if google calendar has ssa_quick_connect_gcal_mode set to true
			if( true === $google_calendar_settings[ "quick_connect_gcal_mode" ] ){
				$status = array_merge( $status, array( 'ssa_quick_connect_status'=> $site_status->test_ssa_quick_connect_status() ) );
			}
		}

		if( current_user_can( 'ssa_manage_site_settings' ) ) {
			$status = array_merge( 
				$status, 
				array( 
					'support_pin' => $this->get_site_support_pin()
				) 
			);
		}

		return $status;
	}

	/**
	 * Receives a JSON formatted string, parses into import data, and runs all the import process.
	 *
	 * @param array $decoded    the JSON import data, decoded into an associative array format.
	 * @return boolean|WP_Error true if import process was successful, WP_Error if something bad happens.
	 */
	public function import_data( $decoded ) {

		// If settings data is available, disable all settings (so we don't trigger hooks for notifications, webhooks, etc).
		// The settings will get overwritten again at the end of this import process.
		if ( isset( $decoded['settings'] ) ) {
			$old_settings = $this->plugin->settings->get();
			foreach ( $old_settings as $key => &$old_setting ) {
				if ( empty( $old_setting ) || ! is_array( $old_setting ) ) {
					continue;
				}

				$old_setting['enabled'] = false;
			}

			// disable settings before import.
			$update = $this->plugin->settings->update( $old_settings );

			// Quick Connect credentials are bound to the source site's URL via the remote token service.
			// Skip clearing only when importing back to the same site (URLs match).
			$source_url               = isset( $decoded['settings']['google_calendar']['quick_connect_home_url'] ) ? $decoded['settings']['google_calendar']['quick_connect_home_url'] : '';
			$should_clear_quick_connect = ! empty( $decoded['settings']['google_calendar']['quick_connect_gcal_mode'] )
				&& get_home_url() !== $source_url;

			// staff.
			$delete = $this->plugin->staff_model->truncate();
			$this->plugin->staff_model->create_table();
			if( !empty( $decoded['staff'] ) && is_array( $decoded['staff'] ) ){
				foreach ( $decoded['staff'] as $staff ) {
					// Remove user IDs from export code since it sometimes assign staff members to the wrong WP users.
					$staff['user_id'] = 0;
					if ( $should_clear_quick_connect ) {
						$staff['google_access_token'] = '';
						$staff['google']              = '';
					}
					$include = $this->plugin->staff_model->raw_insert( $staff );
					// if any error happens while trying to staff data, return.
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			}

			// resource group.
			$delete = $this->plugin->resource_group_model->truncate();
			$this->plugin->resource_group_model->create_table();
			if ( isset( $decoded['resource_groups'] ) && ! empty( $decoded['resource_groups'] ) ) {
				foreach ( $decoded['resource_groups'] as $resource_group ) {
					$include = $this->plugin->resource_group_model->raw_insert( $resource_group );
	
					// if any error happens while trying to import resource group data, return.
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			}

			// resources.
			$delete = $this->plugin->resource_model->truncate();
			$this->plugin->resource_model->create_table();
			if ( isset( $decoded['resources'] ) && ! empty( $decoded['resources'] ) ) {
				foreach ( $decoded['resources'] as $resource ) {
					$include = $this->plugin->resource_model->raw_insert( $resource );

					// if any error happens while trying to import resource data, return.
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			}

			// resource groups resource relation.
			$delete = $this->plugin->resource_group_resource_model->truncate();
			$this->plugin->resource_group_resource_model->create_table();
			if ( isset( $decoded['resource_group_resources'] ) && ! empty( $decoded['resource_group_resources'] ) ) {
				foreach ( $decoded['resource_group_resources'] as $resource_group_resource ) {
					$include = $this->plugin->resource_group_resource_model->raw_insert( $resource_group_resource );
	
					// if any error happens while trying to import resource group/resource data, return.
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			}
		}

		// if appointment types data is available, update.
		if ( isset( $decoded['appointment_types'] ) ) {
			$delete = $this->plugin->appointment_type_model->truncate();
			$this->plugin->appointment_type_model->create_table();

			foreach ( $decoded['appointment_types'] as $appointment_type ) {
				$include = $this->plugin->appointment_type_model->raw_insert( $appointment_type );

				// If any error happens while trying to import appointment type data, return.
				if ( is_wp_error( $include ) ) {
					return $include;
				}
			}

			$delete = $this->plugin->staff_appointment_type_model->truncate();
			$this->plugin->staff_appointment_type_model->create_table();
			if( !empty( $decoded['staff_appointment_types'] ) && is_array( $decoded['staff_appointment_types'] ) ) {
				foreach ( $decoded['staff_appointment_types'] as $staff_appointment_type ) {
					$include = $this->plugin->staff_appointment_type_model->raw_insert( $staff_appointment_type );
	
					// If any error happens while trying to import staff appointment type data, return.
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			}

			$delete = $this->plugin->resource_group_appointment_type_model->truncate();
			$this->plugin->resource_group_appointment_type_model->create_table();
			if ( isset( $decoded['resource_group_appointment_types'] ) && ! empty( $decoded['resource_group_appointment_types'] ) ) {
				foreach ( $decoded['resource_group_appointment_types'] as $resource_group_appointment_type ) {
					$include = $this->plugin->resource_group_appointment_type_model->raw_insert( $resource_group_appointment_type );

					// If any error happens while trying to import resource group appointment type data, return.
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			}

			// If $decoded contains 'appointment_type_labels' -> the exported site has the migration run & all was set for the labels -> just import labels
			if ( isset( $decoded['appointment_type_labels'] ) && ! empty( $decoded['appointment_type_labels'] ) ) {

				$delete = $this->plugin->appointment_type_label_model->truncate();
				$this->plugin->appointment_type_label_model->create_table();

				foreach ( $decoded['appointment_type_labels'] as $appointment_type_label ) {
					$include = $this->plugin->appointment_type_label_model->raw_insert( $appointment_type_label );
	
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			} else {
				// We need to call the migration for the appt type labels to be set
				$this->plugin->upgrade->migrate_appointment_type_labels();
				$this->plugin->upgrade->maybe_fix_appointment_type_label_id_equal_to_zero();
			}

		}

		// If appointments data is available, update.
		if ( isset( $decoded['appointments'] ) ) {
			$delete = $this->plugin->appointment_model->truncate();
			$this->plugin->appointment_model->create_table();

			foreach ( $decoded['appointments'] as $appointment ) {
				$include = $this->plugin->appointment_model->raw_insert( $appointment );

				// If any error happens while trying to import appointment data, return.
				if ( is_wp_error( $include ) ) {
					return $include;
				}
			}

			// staff
			$delete = $this->plugin->staff_appointment_model->truncate();
			$this->plugin->staff_appointment_model->create_table();

			if ( ! empty( $decoded['staff_appointments'] ) ) {
				foreach ( $decoded['staff_appointments'] as $staff_appointment ) {
					$include = $this->plugin->staff_appointment_model->raw_insert( $staff_appointment );

					// If any error happens while trying to import staff_appointment data, return.
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			}

			// resource
			$delete = $this->plugin->resource_appointment_model->truncate();
			$this->plugin->resource_appointment_model->create_table();

			if ( ! empty( $decoded['resource_appointments'] ) ) {
				foreach ( $decoded['resource_appointments'] as $resource_appointment ) {
					$include = $this->plugin->resource_appointment_model->raw_insert( $resource_appointment );

					// If any error happens while trying to import resource_appointment data, return.
					if ( is_wp_error( $include ) ) {
						return $include;
					}
				}
			}
		}

		// If appointments meta data is available, update.
		if ( isset( $decoded['appointment_meta'] ) ) {
			$delete = $this->plugin->appointment_meta_model->truncate();

			foreach ( $decoded['appointment_meta'] as $appointment_meta ) {
				$include = $this->plugin->appointment_meta_model->raw_insert( $appointment_meta );

				// If any error happens while trying to import appointment data, return.
				if ( is_wp_error( $include ) ) {
					return $include;
				}
			}
		}

		// If settings data is available, update.
		if ( isset( $decoded['settings'] ) ) {
			if ( $should_clear_quick_connect ) {
				$decoded['settings']['google_calendar']['access_token']                = '';
				$decoded['settings']['google_calendar']['quick_connect_backoff']       = 0;
				$decoded['settings']['google_calendar']['quick_connect_backoff_timestamp'] = 0;
				$decoded['settings']['google_calendar']['quick_connect_home_url']      = get_home_url();
			}
			$update = $this->plugin->settings->update( $decoded['settings'] );
		}

		$delete_revison_meta = $this->plugin->revision_meta_model->truncate();
		$delete_revision = $this->plugin->revision_model->truncate();
		$delete = $this->plugin->availability_model->truncate();
		$this->plugin->availability_cache_invalidation->increment_cache_version();
		$this->plugin->google_calendar->increment_google_cache_version();

		$this->plugin->upgrade->migrate_free_to_paid_customer_info();
		$this->plugin->upgrade->migrate_paid_to_free_customer_info();

		if ( ! empty( $should_clear_quick_connect ) ) {
			$this->plugin->error_notices->add_error_notice( 'quick_connect_site_mismatch' );
			return 'cleared_quick_connect';
		}

		// Everything was successfully imported.
		return true;
	}

	/**
	 * Save JSON export backups into the database.
	 *
	 * @param string $code
	 * @return bool|WP_Error true if backup was successfully saved. WP_Error if something wrong happens.
	 */
	public function save_export_backup( $code = null ) {
		if( ! $code ) {
			return false;
		}

		$date = date('Y-m-d H:i:s');
		$encoded = json_encode($code);

		$backups = get_option( 'ssa_export_backups' );

		if( ! $backups ) {
			$backups = array();
		}

		// if there is already 3 backups, remove the oldest one
		if( count($backups) >= 3 ) {
			array_pop($backups);
		}

		// insert the newest one at the beginning of the array
		array_unshift( 
			$backups, 
			array(
				'date' => $date,
				'content' => $encoded
			) 
		);

		$update = update_option( 'ssa_export_backups', $backups, false );

		if( ! $update ) {
			return new WP_Error( 'ssa-export-backup-not-saved', __( 'An error occurred while trying to save a backup.', 'simply-schedule-appointments' ) );
		}

		return $update;
	}

	/**
	 * Checks if there is a backup export file stored and, if it does, then decode the JSON into an associative array.
	 *
	 * @return boolean|array false if we can't find the file, or an associative array if we find it and it has a valid format.
	 */
	public function get_export_backup() {
		$backups = $this->get_export_backup_list();

		if( is_wp_error($backups) ) {
			return $backups;
		}

		$json = $backups[0]['content'];

		// verify if JSON data is valid
		$decoded = json_decode( $json, true );

		if ( ! is_object( $decoded ) && ! is_array( $decoded ) ) {
			return new WP_Error( 'export-code-invalid-format', __( 'Invalid data format.', 'simply-schedule-appointments'));
		}
		
		if ( json_last_error() !== JSON_ERROR_NONE ) {
			return new WP_Error( 'export-code-invalid-format', __( 'Invalid data format.', 'simply-schedule-appointments'));
		}

		if( $decoded ) {
			return $decoded;
		}

		return false;
	}

	/**
	 * Checks if there is an export backup stored and returns a list if any.
	 *
	 * @return array|WP_Error An associative array if we find backups. WP_Error if we can't find anything.
	 */
	public function get_export_backup_list() {
		$backups = get_option('ssa_export_backups');

		if( ! $backups || empty($backups) ) {
			return new WP_Error( 'ssa-export-backups-not-found', __( 'Could not find any export backups.', 'simply-schedule-appointments' ) );
		}

		return $backups;
	}	
	
	/**
	 * Searches for latest export backup and, if found, recover the data by running the import logic.
	 *
	 * @return boolean|WP_Error
	 */
	public function restore_settings_backup() {
		$backup = $this->get_export_backup();

		if( ! $backup || is_wp_error( $backup ) ) {
			return new WP_Error( 'ssa-export-file-not-found', __( 'No backup files were found.', 'simply-schedule-appointments' ) );
		}
		
		$import = $this->import_data( $backup );

		if( is_wp_error( $import ) ) {
			return $import;
		}

		return $import;
	}

	/**
	 * Checks the current status of the plugin license.
	 *
	 * @return array
	 */
	public function test_site_license() {
		$expiration_date = $this->get_license_expiration_date();
		$settings  = $this->plugin->settings->get();
		$license   = $settings['license'];


		$login_url = 'https://simplyscheduleappointments.com/your-account/';
		$pricing_url = 'https://simplyscheduleappointments.com/pricing/';


		if ( ! $this->plugin->settings_installed->is_installed( 'license' ) || 'empty' === $license['license_status'] || 'inactive' === $license['license_status'] ) {
			return array(
				// translators: %s is the URL to the login page.
				'notices' => array( sprintf( __( '<a href="%s" target="_blank">Get your license key</a> and add it to <a href="#/ssa/settings/license">this site\'s settings</a> to enable automatic updates.', 'simply-schedule-appointments' ), $login_url ) ),
				'status'  => 'warning',
				'value'   => false,
			);
		}

		if ( 'disabled' === $license['license_status'] ) {
			return array(
				// translators: %s is the URL to the login page.
				'notices' => array( sprintf( __( 'Your license is disabled. <a href="%s" target="_blank">Purchase</a> a new license key to enable automatic updates and support.', 'simply-schedule-appointments' ), $pricing_url ) ),
				'status'  => 'warning',
				'value'   => false,
			);
		}

		if ( 'expired' === $license['license_status'] ) {
			return array(
				// translators: %s is the URL to the login page.
				'notices' => array(
					sprintf(
						__( 'Your license expired on %1$s. <a href="%2$s" target="_blank">Renew your license</a> to enable automatic updates, bug fixes and support.', 'simply-schedule-appointments' ),
						$expiration_date,
						$license['license_renewal_link']
					),
				),
				'status'  => 'warning',
				'value'   => false,
			);
		}

		if ( 'active' === $license['license_status'] || 'valid' === $license['license_status'] ) {
			if ( 'lifetime' === $license['license_expiration_date'] ) {
				$notice = __( 'Your license is active. You have lifetime access.', 'simply-schedule-appointments' );
			} else {
				$notice = sprintf(
					__( 'Your license is up-to-date. Next renewal is due on %s.', 'simply-schedule-appointments' ),
					$expiration_date
				);
			}
			return array(
				'notices' => array( $notice ),
				'status'  => 'good',
				'value'   => true,
			);
		}

		// if there isn't any other information, then we assume that the license is invalid.
		return array(
			// translators: %s is the URL to the login page.
			'notices' => array( sprintf( __( '<a href="%s" target="_blank">Get your license key</a> and add it to <a href="#/ssa/settings/license">this site\'s settings</a> to enable automatic updates.', 'simply-schedule-appointments' ), $login_url ) ),
			'status'  => 'warning',
			'value'   => false,
		);
	}

	/**
	 * get the expiration date for expired license
	 *
	 */
	public function get_license_expiration_date() {

		$license = $this->plugin->license->check();

		if ( ! empty( $license['license_expiration_date'] ) && 'lifetime' !== $license['license_expiration_date'] ) {

			$formatted_date = date_i18n( get_option( 'date_format' ), strtotime( $license['license_expiration_date'] ) );

			return $formatted_date;
		}
	}

	/**
	 * Check if the main directory is named correctly
	 *
	 * @return void
	 */
	public function validate_directory_name() {
		
		global $pagenow;
		if ( 'plugins.php' !== $pagenow ) {
			return;
		}

		$directory = $this->plugin->dir();
		$pattern = '/(.*)(\/|\\\)simply\-schedule\-appointments(\/|\\\)$/';
		$matching = preg_match( $pattern, $directory );
		
		if( ! $matching ) {
			add_action( 'after_plugin_row_' . $this->plugin->basename, array( $this, 'display_wrong_dir_name_error' ), 10 );
		}
	}

	/**
	 * Display warning message to users in plugins.php screen
	 *
	 * @return void
	 */
	public function display_wrong_dir_name_error () {

		echo '<tr class="plugin-update-tr active">';
		echo '<td colspan="4" class="plugin-update colspanchange">';
		echo '<div class="update-message notice inline notice-error notice-alt">';
		echo '<p>' .  __( '<strong>Error: Invalid directory name</strong>. Please rename this plugin\'s directory to simply-schedule-appointments to avoid errors when updating.', 'simply-schedule-appointments' ) . '</p>';
		echo '</div>';
		echo '</td>';
		echo '</tr>';
	}

	public function get_site_support_pin() {
		$status  = 'good';
		$value   = true;
		$notices = array(
			SSA_Debug::get_site_unique_hash_for_debug_logs()
		);

		return compact(
			'status', 
			'value', 
			'notices'
		);
	}

}
