<?php
/**
 * Simply Schedule Appointments Settings.
 *
 * @since   0.0.3
 * @package Simply_Schedule_Appointments
 */

/**
 * Simply Schedule Appointments Settings.
 *
 * @since 0.0.3
 */
class SSA_Settings {
	/**
	 * Parent plugin class.
	 *
	 * @since 0.0.3
	 *
	 * @var   Simply_Schedule_Appointments
	 */
	protected $plugin = null;

	protected $option_name = 'ssa_settings_json';

	protected $defaults = null;

	protected $schema;

	protected $settings;
	
	protected $computed_schema = array();

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

	/**
	 * Initiate our hooks
	 *
	 * @since  0.0.3
	 * @return void
	 */
	public function hooks() {
	}

	public function get_schema() {
		if ( !empty( $this->schema ) ) {
			return $this->schema;
		}

		$schema = apply_filters( 'ssa_settings_schema', array() );

		$this->schema = $schema;
		return $this->schema;
	}

	public function get_computed_schema() {
		if ( !empty( $this->computed_schema ) ) {
			return $this->computed_schema;
		}

		$computed_schema = apply_filters( 'ssa_settings_computed_schema', array() );

		$this->computed_schema = $computed_schema;
		return $this->computed_schema;
	}

	public function get() {
		if ( !empty( $this->settings ) ) {
			// $this->add_computed_values(); // we shouldn't need to re-compute values on every run through this function
			return $this->settings;
		}

		$settings_json = get_option( $this->option_name, json_encode( array() ) );
		$settings = json_decode( $settings_json, true );

		$schema = $this->get_schema(); // load the schema so that we can load default values as necessary
		foreach ($schema as $section_key => $section_schema) {
			if ( !empty( $settings[$section_key]['schema_version'] )
			 	&& $settings[$section_key]['schema_version'] >= $schema[$section_key]['version'] ) {
				// In this case we already have saved data with this schema, so we do not need to load any default values
				continue;
			}

			if ( empty( $settings[$section_key]['schema_version'] ) ) {
				// this is the first time that we have encountered this schema
				$settings[$section_key] = array();
			}

			//  let's start merging in default values
			$default_values_from_schema = array_combine(
				wp_list_pluck( $schema[$section_key]['fields'], 'name' ),
				wp_list_pluck( $schema[$section_key]['fields'], 'default_value' )
			);

			if ( empty( $settings[$section_key] ) ) {
				$settings[$section_key] = $default_values_from_schema;
			} else {
				$settings[$section_key] = array_merge( $default_values_from_schema, $settings[$section_key] );
			}
			
			$settings[$section_key]['schema_version'] = $schema[$section_key]['version'];
		}

		if ( empty( $settings['global']['timezone_string'] ) ) {
			$settings['global']['timezone_string'] = 'UTC';
		}
		$this->settings = $settings;
		$this->add_computed_values();
		$this->add_enabled_activated_values();
		$this->decrypt();
		return $this->settings;
	}

	public function remove_unauthorized_settings_for_current_user( $settings, $replace_tokens = true, $unset_admin_only_fields = false ) {
		foreach ($settings as $module_slug => $module_settings) {
			if ( empty( $module_settings ) || ! is_array( $module_settings ) ) {
				continue;
			}
			
			if ( 'global' === $module_slug ) {
				$module_settings_slug = 'settings_global';
			} else {
				$module_settings_slug = $module_slug.'_settings';
			}
			
			if('license' === $module_slug && defined('SSA_LICENSE_KEY') && !empty(SSA_LICENSE_KEY)){
				$settings['license']['license'] = "redacted-writeonly-secret";
				$settings['license']['license_filtered'] = "redacted-writeonly-secret";
				$settings['license']['license_renewal_link'] = "redacted-writeonly-secret";
			}
			
			foreach ($module_settings as $field_slug => $module_setting_value) {
				$module_schema = $this->plugin->$module_settings_slug->get_schema();
				$computed_schema = $this->plugin->$module_settings_slug->get_computed_schema();

				if ( empty( $module_schema['fields'] ) ) {
					continue;
				}
				
				// this is only doable with fields that the frontend will never need to write, read, or update
				// it will not work for something like Stripe keys
				if( true == $replace_tokens && isset( $module_schema['fields'][$field_slug]['writeonly_secret'] ) && true == $module_schema['fields'][$field_slug]['writeonly_secret'] ){
					if( !empty( $settings[$module_slug][$field_slug] ) ){
						$settings[$module_slug][$field_slug]="redacted-writeonly-secret";
						continue;
					}
				}

					if ( ! empty( $module_schema['fields'][$field_slug]['required_capability'] ) ) {
						if ( ! current_user_can( $module_schema['fields'][$field_slug]['required_capability'] )  || $unset_admin_only_fields ) {
							unset( $settings[$module_slug][$field_slug] );
						}
					}
					if ( ! empty( $computed_schema['fields'][$field_slug]['required_capability'] ) ) {
						if ( ! current_user_can( $computed_schema['fields'][$field_slug]['required_capability'] ) || $unset_admin_only_fields ) {
							unset( $settings[$module_slug][$field_slug] );
						}
					}
			}
		}

		return $settings;
	}

	public function add_computed_values() {
		$computed_schema = $this->get_computed_schema(); // load the computed_schema so that we can calculate necessary values
		foreach ($computed_schema as $section_key => $section_computed_schema) {
			if ( empty( $section_computed_schema['fields'] ) ) {
				continue;
			}

			foreach ($section_computed_schema['fields'] as $computed_field ) {
				if ( !empty( $computed_field['get_input'] ) ) {
					$input = $computed_field['get_input'];
				} elseif ( !empty( $computed_field['get_input_path'] ) && isset( $this->settings[$section_key][$computed_field['get_input_path']] ) ) {
					$input = $this->settings[$section_key][$computed_field['get_input_path']];
				} else {
					$input = null;
				}
				$computed_value = call_user_func( $computed_field['get_function'], $input );
				$this->settings[$section_key][$computed_field['name']] = $computed_value;
			}
		}
	}

	public function add_enabled_activated_values() {
		$schema = $this->get_schema(); // load the schema so that we can calculate necessary values
		foreach ($schema as $section_key => $section_schema) {
			if ( empty( $section_schema['fields'] ) ) {
				continue;
			}
			$this->settings[$section_key]['enabled'] = $this->plugin->settings_installed->is_enabled( $section_key );
		}
	}

	public function decrypt() {
		if ( ! function_exists( 'openssl_decrypt' ) ) {
			return;
		}

		$schema = $this->get_schema(); // load the schema so that we can calculate necessary values
		foreach ($schema as $section_key => $section_schema) {
			if ( empty( $section_schema['fields'] ) ) {
				continue;
			}
			
			foreach ($section_schema['fields'] as $field ) {
				if ( empty( $field['encrypt'] ) ) {
					continue;
				}
				if ( empty( $this->settings[$section_key][$field['name']] ) ) {
					continue;
				}

				if ( false === strpos( $this->settings[$section_key][$field['name']], SSA_Encryption::SALT ) ) {
					continue; // confirm that this indeed encrypted data
				}

				$this->settings[$section_key][$field['name']] = str_replace( SSA_Encryption::SALT, '', $this->settings[$section_key][$field['name']] );

				// $decrypted_value = call_user_func( $field['get_function'], $input );
				$this->settings[$section_key][$field['name']] = SSA_Encryption::decrypt( $this->settings[$section_key][$field['name']] );
			}
		}
	}


	public function get_encrypted_settings( $new_settings ) {
		if ( ! function_exists( 'openssl_encrypt' ) ) {
			return $new_settings;
		}

		$schema = $this->get_schema(); // load the schema so that we can calculate necessary values
		foreach ($schema as $section_key => $section_schema) {
			if ( empty( $section_schema['fields'] ) ) {
				continue;
			}
			
			foreach ($section_schema['fields'] as $field ) {
				if ( empty( $field['encrypt'] ) ) {
					continue;
				}

				if ( empty( $new_settings[$section_key][$field['name']] ) ) {
					continue;
				}

				// $decrypted_value = call_user_func( $field['get_function'], $input );
				$new_settings[$section_key][$field['name']] = SSA_Encryption::encrypt( $new_settings[$section_key][$field['name']] ) . SSA_Encryption::SALT;

			}
		}

		return $new_settings;
	}

	public function update( $new_settings ) {
		$existing_settings = $this->get();
		$merged_settings = shortcode_atts( $existing_settings, $new_settings );
		$merged_settings['last_updated'] = gmdate( 'Y-m-d H:i:s' );
		return $this->set( $merged_settings );
	}

	public function update_section( $section_key, $new_settings ) {

		$new_settings = $this->cleanup_writeonly_secret_values( $section_key, $new_settings );

		$settings = $this->get();
		$old_settings = $settings;
		if ( empty( $settings[$section_key] ) ) {
			return false;
		}
		
		$validation = $this->plugin->{$section_key.'_settings'}->validate( $new_settings, $old_settings[$section_key] );
		if ( is_wp_error( $validation ) ) {
			// we have an error with the inputs
			return $validation;
		}
		
		$settings[$section_key] = shortcode_atts( $settings[$section_key], $new_settings );

		$computed_schema = $this->get_computed_schema();
		if ( !empty( $computed_schema[$section_key]['fields'] ) ) {
			foreach ($computed_schema[$section_key]['fields'] as $computed_field ) {
				if ( !isset( $settings[$section_key][$computed_field['name']])) {
					continue;
				}
				if ( !empty( $settings[$section_key][$computed_field['name']] ) ) {
					if ( !empty( $computed_field['set_result_path'] ) ) {
						if ( isset( $settings[$section_key][$computed_field['set_result_path']] ) ) {					

							$result_value = call_user_func( $computed_field['set_function'], $settings[$section_key][$computed_field['name']] );
							$settings[$section_key][$computed_field['set_result_path']] = $result_value;
						}
					}
				}

				unset( $settings[$section_key][$computed_field['name']] );
			}
		}

		/* before_save */
		$schema = $this->get_schema();
		if ( !empty( $schema[$section_key]['fields'] ) ) {
			foreach ($schema[$section_key]['fields'] as $field) {
				if ( empty( $field['before_save_function'] ) ) {
					continue;
				}
				if ( !isset( $settings[$section_key][$field['name']])) {
					continue;
				}

				if ( !empty( $settings[$section_key][$field['name']] ) ) {
					if ( !empty( $field['before_save_function'] ) ) {
						$result_value = call_user_func( $field['before_save_function'], $settings[$section_key][$field['name']] );
						$settings[$section_key][$field['name']] = $result_value;
					}
				}
			}
		}

		$settings[$section_key]['last_updated'] = gmdate( 'Y-m-d H:i:s' );
		$this->set( $settings );

		$settings = $this->get();

		do_action( 'ssa/settings/'.$section_key.'/updated', $settings[$section_key], $old_settings[$section_key] );

		return $settings[$section_key];
	}

	private function set( $new_settings ) {
		$this->settings = $new_settings;
		$encrypted_settings = $this->get_encrypted_settings( $new_settings );
		update_option( $this->option_name, json_encode( $encrypted_settings ) );
		$this->add_computed_values();
		$this->add_enabled_activated_values();

		return $this->settings;
	}
	
	public function cleanup_writeonly_secret_values( $section_key, $settings ){
		$schema = $this->get_schema()[$section_key]['fields'];
		if( is_array( $settings ) ){
			foreach ( $settings as $field => $value ) {
			if ( isset( $schema[ $field ]['writeonly_secret'] ) && true == $schema[ $field ]['writeonly_secret'] && 'redacted-writeonly-secret' == $value){
					unset( $settings[$field] );
				}
			}
		}
		return $settings;
	}
}

abstract class SSA_Settings_Schema {

	protected $schema = array();
	protected $computed_schema = array();
	protected $slug;
	protected $parent_slug;
	protected $defaults;

	abstract function get_schema();
	public function get_computed_schema() {
		return array();
	}

	public function __construct() {
		if ( empty( $this->slug ) ) {
			die( 'no slug defined for: '.get_class( $this ).' (this slug will be used as the key to save in to the settings array)' ); // phpcs:ignore
		}

		$this->parent_hooks();
	}

	public function parent_hooks() {
		add_filter( 'ssa_settings_schema', array( $this, 'filter_settings_schema' ) );
		add_filter( 'ssa_settings_computed_schema', array( $this, 'filter_settings_computed_schema' ) );
	}

	public function filter_settings_schema( $schema ) {
		$schema[$this->slug] = $this->get_schema();

		return $schema;
	}

	public function filter_settings_computed_schema( $schema ) {
		$schema[$this->slug] = $this->get_computed_schema();

		return $schema;
	}

	public function get_field_defaults() {
		if ( !empty( $this->defaults ) ) {
			return $this->defaults;
		}
		
		$defaults = array();
		$schema = $this->get_schema();
		if ( empty( $schema['fields'] ) ) {
			return $defaults;
		}

		$defaults = array_combine(
			wp_list_pluck( $schema['fields'], 'name' ),
			wp_list_pluck( $schema['fields'], 'default_value' )
		);

		$this->defaults = $defaults;
		return $this->defaults;
	}
 
	public function get() {
		if ( $this->slug !== 'installed' ) {		
			if ( ! $this->plugin->settings_installed->is_enabled( $this->slug ) ) {
				return null;
			}

			if ( ! empty( $this->parent_slug ) ) {
				if ( ! $this->plugin->settings_installed->is_enabled( $this->parent_slug ) ) {
					return null;
				}
			}
		}

		return $this->plugin->settings->get()[$this->slug];
	}

	public function reset_to_defaults( $re_enable_feature = true ) {
		$defaults = $this->get_field_defaults();
		if ( !empty( $re_enable_feature ) ) {
			$defaults['enabled'] = $re_enable_feature;
		}
		return $this->update( $defaults );
	}

	public function update( $new_settings ) {
		// $old_settings = $this->get();
		// $new_settings = apply_filters( 'update_'.$this->slug.'_settings', $new_settings, $old_settings );
		return $this->plugin->settings->update_section( $this->slug, $new_settings );
	}

	public function validate( $new_settings, $old_settings ) {
		if ( empty( $new_settings ) ) {
			return;
		}
		$wp_error = new WP_Error();
		$schema   = $this->get_schema()['fields'];
		foreach ( $new_settings as $field => $value ) {
			if ( isset( $schema[ $field ]['validate_callback'] ) ) {
				$validation_result = call_user_func( $schema[ $field ]['validate_callback'], $value );
				if ( $validation_result !== true ) {
					if ( empty( $wp_error->errors ) ) {
						// populate the general error message
						$wp_error->add( 422, $this->slug . ' ' . __( 'settings are invalid' ) );
					}
					// add the field specific error message - $validation_result is already translated
					$wp_error->add( 422, array( $field, $validation_result ) );
				}
			}
		}
		if ( ! empty( $wp_error->errors ) ) {
			return $wp_error;
		}
	}
}

