<?php

namespace Automattic\WCShipping\Connect;

class WC_Connect_Options {
	/**
	 * An array that maps a grouped option type to an option name.
	 *
	 * @var array
	 */
	private static $grouped_options = array(
		'compact' => 'wcshipping_options',
	);

	/**
	 * Returns an array of option names for a given type.
	 *
	 * @param string $type The type of option to return. Defaults to 'compact'.
	 *
	 * @return array
	 */
	public static function get_option_names( $type = 'compact' ) {
		switch ( $type ) {
			case 'non_compact':
				return array(
					'error_notice',
					'services',
					'services_last_update',
					'services_fetch_error',
					'last_heartbeat',
					'origin_address',
					'origin_addresses',
					'origin_normalized',
					'last_rate_request',
					'services_last_result_code',
					'upsdap_strategies',
					'tax_identifiers',
					'use_fulfillment_api',
					'main_origin_store_address_drift',
				);
			case 'shipping_method':
				return array(
					'form_settings',
					'failure_timestamp',
				);
		}

		return array(
			'tos_accepted',
			'store_guid',
			'debug_logging_enabled',
			'debug_display_enabled',
			'add_payment_method_url',
			'payment_methods',
			'account_settings',
			'paper_size',
			'packages',
			'predefined_packages',
			'shipping_methods_migrated',
			'should_display_nux_after_jp_cxn_banner',
			'banner_ppec',
		);
	}

	/**
	 * Deletes all options created by WooCommerce Shipping, including shipping method options
	 */
	public static function delete_all_options() {
		if ( defined( 'WOOCOMMERCE_SHIPPING_DEV_SERVER_URL' ) ) {
			return;
		}

		foreach ( self::$grouped_options as $group_key => $group ) {
			// delete legacy options
			foreach ( self::get_option_names( $group_key ) as $group_option ) {
				delete_option( "wcshipping_$group_option" );
			}

			delete_option( $group );
		}

		$non_compacts = self::get_option_names( 'non_compact' );
		foreach ( $non_compacts as $non_compact ) {
			delete_option( "wcshipping_$non_compact" );
		}
	}

	/**
	 * Returns the requested option.  Looks in wcshipping_options or wcshipping_$name as appropriate.
	 *
	 * @param string $name Option name
	 * @param mixed  $default (optional)
	 *
	 * @return mixed
	 */
	public static function get_option( $name, $default = false ) {
		if ( self::is_valid( $name, 'non_compact' ) ) {
			return get_option( "wcshipping_$name", $default );
		}
		foreach ( array_keys( self::$grouped_options ) as $group ) {
			if ( self::is_valid( $name, $group ) ) {
				return self::get_grouped_option( $group, $name, $default );
			}
		}

		// Show error message for invalid option name.
		wp_trigger_error(
			'wcshipping_invalid_option',
			esc_html( sprintf( 'Invalid WooCommerce Shipping option name: %s', $name ) ),
			E_USER_WARNING
		);
		return $default;
	}

	/**
	 * Updates the single given option.  Updates wcshipping_options or wcshipping_$name as appropriate.
	 *
	 * @param string $name Option name
	 * @param mixed  $value Option value
	 *
	 * @return bool Was the option successfully updated?
	 */
	public static function update_option( $name, $value ) {
		if ( self::is_valid( $name, 'non_compact' ) ) {
			return update_option( "wcshipping_$name", $value );
		}
		foreach ( array_keys( self::$grouped_options ) as $group ) {
			if ( self::is_valid( $name, $group ) ) {
				return self::update_grouped_option( $group, $name, $value );
			}
		}
		// Show error message for invalid option name.
		wp_trigger_error(
			'wcshipping_invalid_option',
			esc_html( sprintf( 'Invalid WooCommerce Shipping option name: %s', $name ) ),
			E_USER_WARNING
		);
		return false;
	}

	/**
	 * Deletes the given option.  May be passed multiple option names as an array.
	 * Updates wcshipping_options and/or deletes wcshipping_$name as appropriate.
	 *
	 * @param string|array $names
	 *
	 * @return bool Was the option successfully deleted?
	 */
	public static function delete_option( $names ) {
		$result = true;
		$names  = (array) $names;
		if ( ! self::is_valid( $names ) ) {
			// Show error message for invalid option names.
			wp_trigger_error(
				'wcshipping_invalid_option',
				esc_html( sprintf( 'Invalid WooCommerce Shipping option names: %s', implode( ', ', $names ) ) ),
				E_USER_WARNING
			);
			return false;
		}
		foreach ( array_intersect( $names, self::get_option_names( 'non_compact' ) ) as $name ) {
			if ( ! delete_option( "wcshipping_$name" ) ) {
				$result = false;
			}
		}
		foreach ( array_keys( self::$grouped_options ) as $group ) {
			if ( ! self::delete_grouped_option( $group, $names ) ) {
				$result = false;
			}
		}
		return $result;
	}

	/**
	 * Gets a shipping method option
	 *
	 * @param $name
	 * @param $default
	 * @param $service_id
	 * @param $service_instance
	 *
	 * @return mixed
	 */
	public static function get_shipping_method_option( $name, $default, $service_id, $service_instance = false ) {
		$option_name = self::get_shipping_method_option_name( $name, $service_id, $service_instance );

		if ( ! $option_name ) {
			// Show error message for invalid option name.
			wp_trigger_error(
				'wcshipping_invalid_option',
				esc_html( sprintf( 'Invalid WooCommerce Shipping shipping method option name: %s', $name ) ),
				E_USER_WARNING
			);
			return $default;
		}

		return get_option( $option_name, $default );
	}

	/**
	 * Updates a shipping method option
	 *
	 * @param $name
	 * @param $value
	 * @param $service_id
	 * @param $service_instance
	 *
	 * @return bool
	 */
	public static function update_shipping_method_option( $name, $value, $service_id, $service_instance = false ) {
		$option_name = self::get_shipping_method_option_name( $name, $service_id, $service_instance );

		if ( ! $option_name ) {
			// Show error message for invalid option name.
			wp_trigger_error(
				'wcshipping_invalid_option',
				esc_html( sprintf( 'Invalid WooCommerce Shipping shipping method option name: %s', $name ) ),
				E_USER_WARNING
			);
			return false;
		}

		return update_option( $option_name, $value );
	}

	/**
	 * Deletes a shipping method option
	 *
	 * @param $name
	 * @param $service_id
	 * @param $service_instance
	 *
	 * @return bool
	 */
	public static function delete_shipping_method_option( $name, $service_id, $service_instance = false ) {
		$option_name = self::get_shipping_method_option_name( $name, $service_id, $service_instance );

		if ( ! $option_name ) {
			// Show error message for invalid option name.
			wp_trigger_error(
				'wcshipping_invalid_option',
				esc_html( sprintf( 'Invalid WooCommerce Shipping shipping method option name: %s', $name ) ),
				E_USER_WARNING
			);
			return false;
		}

		return delete_option( $option_name );
	}

	/**
	 * Deletes all options related to a shipping method
	 *
	 * @param $service_id
	 * @param $service_instance
	 */
	public static function delete_shipping_method_options( $service_id, $service_instance = false ) {
		$option_names = self::get_option_names( 'shipping_method' );

		foreach ( $option_names as $name ) {
			delete_option( self::get_shipping_method_option_name( $name, $service_id, $service_instance ) );
		}
	}

	private static function get_grouped_option( $group, $name, $default ) {
		$options = get_option( self::$grouped_options[ $group ] );
		if ( is_array( $options ) && isset( $options[ $name ] ) ) {
			return $options[ $name ];
		}

		// make the grouped options backwards-compatible and migrate the old options
		$legacy_name   = "wcshipping_$name";
		$legacy_option = get_option( $legacy_name, false );
		if ( ! $legacy_option ) {
			return $default;
		}
		if ( self::update_grouped_option( $group, $name, $legacy_option ) ) {
			delete_option( $legacy_name );
		}

		return $legacy_option;
	}

	private static function update_grouped_option( $group, $name, $value ) {
		$options = get_option( self::$grouped_options[ $group ] );
		if ( ! is_array( $options ) ) {
			$options = array();
		}
		$options[ $name ] = $value;
		return update_option( self::$grouped_options[ $group ], $options );
	}

	private static function delete_grouped_option( $group, $names ) {
		$options   = get_option( self::$grouped_options[ $group ], array() );
		$to_delete = array_intersect( $names, self::get_option_names( $group ), array_keys( $options ) );
		if ( $to_delete ) {
			foreach ( $to_delete as $name ) {
				unset( $options[ $name ] );
			}
			return update_option( self::$grouped_options[ $group ], $options );
		}
		return true;
	}

	/**
	 * Based on the service id and optional instance, generates the option name
	 *
	 * @param $name
	 * @param $service_id
	 * @param $service_instance
	 *
	 * @return string|bool
	 */
	private static function get_shipping_method_option_name( $name, $service_id, $service_instance = false ) {
		if ( ! in_array( $name, self::get_option_names( 'shipping_method' ) ) ) {
			return false;
		}

		if ( ! $service_instance ) {
			return 'woocommerce_' . $service_id . '_' . $name;
		}

		return 'woocommerce_' . $service_id . '_' . $service_instance . '_' . $name;
	}

	/**
	 * Is the option name valid?
	 *
	 * @param string $name  The name of the option
	 * @param string $group The name of the group that the option is in. Defaults to compact.
	 *
	 * @return bool Is the option name valid?
	 */
	private static function is_valid( $name, $group = 'non_compact' ) {
		$group_keys = array_keys( self::$grouped_options );

		if ( is_array( $name ) ) {
			$compact_names = array();
			foreach ( $group_keys as $_group ) {
				$compact_names = array_merge( $compact_names, self::get_option_names( $_group ) );
			}
			$result = array_diff( $name, self::get_option_names( 'non_compact' ), $compact_names );
			return empty( $result );
		}

		if ( is_null( $group ) || 'non_compact' === $group ) {
			if ( in_array( $name, self::get_option_names( $group ) ) ) {
				return true;
			}
		}

		foreach ( array_keys( self::$grouped_options ) as $_group ) {
			if ( is_null( $group ) || $group === $_group ) {
				if ( in_array( $name, self::get_option_names( $_group ) ) ) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Deletes all options of all shipping methods
	 */
	private static function delete_all_shipping_methods_options() {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$methods = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_methods " );

		foreach ( (array) $methods as $method ) {
			self::delete_shipping_method_options( $method->method_id, $method->instance_id );
		}
	}
}