<?php
/**
 * Options storage.
 *
 * @package Stripe\StripeTaxForWooCommerce\WordPress
 */

namespace Stripe\StripeTaxForWooCommerce\WordPress;

// Exit if script started not from WordPress.
defined( 'ABSPATH' ) || exit;

use stdClass;

/**
 * Class used to get, store and delete any options in database
 */
class Options {
	const OPTION_LIVE_MODE_SECRET_KEY             = 'live_mode_secret_key';
	const OPTION_TEST_MODE_SECRET_KEY             = 'test_mode_secret_key';
	const OPTION_LIVE_MODE_ENABLED                = 'live_mode_enabled';
	const OPTION_LIVE_MODE_ACCOUNT_ID             = 'live_mode_account_id';
	const OPTION_WOOCOMMERCE_CONNECT_LAST_ERROR   = 'woocommerce_connect_last_error';
	const OPTION_WOOCOMMERCE_CONNECT_LAST_STATE   = 'woocommerce_connect_last_state';
	const OPTION_MODE_TYPE                        = 'mode_type';
	const OPTION_TAX_CODE                         = 'tax_code';
	const OPTION_FEE_TAX_CODE                     = 'fee_tax_code';
	const OPTION_NON_TAXABLE_FEE_TAX_CODE         = 'non_taxable_fee_tax_code';
	const OPTION_SHIPPING_TAX_CODE                = 'shipping_tax_code';
	const MODE_LIVE                               = 0;
	const MODE_TEST                               = 1;
	const TEST_KEY_NEEDLE                         = '_test_';
	const LIVE_KEY_NEEDLE                         = '_live_';
	const CACHE_GROUP                             = 'stripe-tax-for-woocommerce';
	const CACHE_KEY                               = 'options';
	const TABLE_NAME                              = STRIPE_TAX_FOR_WOOCOMMERCE_DB_PREFIX . 'options';
	const DEFAULT_OPTION_FEE_TAX_CODE             = 'txcd_20030000';
	const DEFAULT_OPTION_NON_TAXABLE_FEE_TAX_CODE = 'txcd_00000000';
	const DEFAULT_OPTION_SHIPPING_TAX_CODE        = 'txcd_92010001';
	const DEFAULT_OPTION_TAX_CODE                 = 'txcd_99999999';

	/**
	 * Initializes the cache.
	 *
	 * @param bool $force_update_from_db Force update from database flag.
	 */
	protected static function init_cache( bool $force_update_from_db = false ) {
		$cache = wp_cache_get( static::CACHE_KEY, static::CACHE_GROUP );
		if ( is_array( $cache ) && ! $force_update_from_db ) {
			return;
		}

		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
		$cached = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT %i, %i FROM %i',
				array(
					'option_name',
					'option_value',
					static::TABLE_NAME,
				)
			),
			OBJECT_K
		);
		wp_cache_set( static::CACHE_KEY, $cached, static::CACHE_GROUP, 2 * HOUR_IN_SECONDS );
	}

	/**
	 * Gets from cache.
	 *
	 * @param string $option_name Option name.
	 * @param bool   $force_update_from_db Force update from database flag.
	 */
	protected static function get_from_cache( string $option_name, bool $force_update_from_db = false ): string {
		static::init_cache( $force_update_from_db );
		$cached = wp_cache_get( static::CACHE_KEY, static::CACHE_GROUP );

		return (string) ( array_key_exists( $option_name, $cached ) && isset( $cached[ $option_name ]->option_value ) ? $cached[ $option_name ]->option_value : '' );
	}

	/**
	 * Updates into cache.
	 *
	 * @param string $option_name Option name.
	 * @param string $option_value Option value.
	 * @param bool   $force_update_from_db Force update from database flag.
	 */
	protected static function update_into_cache( string $option_name, string $option_value, bool $force_update_from_db = false ) {
		static::init_cache( $force_update_from_db );
		$cached                               = wp_cache_get( static::CACHE_KEY, static::CACHE_GROUP );
		$cached[ $option_name ]               = new stdClass();
		$cached[ $option_name ]->option_name  = $option_name;
		$cached[ $option_name ]->option_value = $option_value;
		wp_cache_set( static::CACHE_KEY, $cached, static::CACHE_GROUP, 2 * HOUR_IN_SECONDS );
	}

	/**
	 * Deletes an option value from the cache.
	 *
	 * @param string $option_name Option name.
	 * @param bool   $force_update_from_db Force update from database flag.
	 */
	protected static function delete_from_cache( string $option_name, bool $force_update_from_db = false ) {
		static::init_cache( $force_update_from_db );
		$cached = wp_cache_get( static::CACHE_KEY, static::CACHE_GROUP );
		unset( $cached[ $option_name ] );
		wp_cache_set( static::CACHE_KEY, $cached, static::CACHE_GROUP, 2 * HOUR_IN_SECONDS );
	}

	/**
	 * Return option value by option name
	 *
	 * @param string $option_name Option name.
	 * @param bool   $force_update_from_db Force update from database flag.
	 *
	 * @return string Option value
	 */
	public static function get_option( string $option_name, bool $force_update_from_db = false ): string {
		return static::get_from_cache( $option_name, $force_update_from_db );
	}

	/**
	 * Creates new or updates existing option.
	 *
	 * @param string $option_name Option name.
	 * @param string $option_value Option value.
	 * @param bool   $force_update_from_db Force update from database flag.
	 *
	 * @return void
	 */
	public static function update_option( string $option_name, string $option_value, bool $force_update_from_db = false ) {
		global $wpdb;
		$table_name = static::TABLE_NAME;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare(
				'INSERT INTO %i (%i, %i) VALUES (%s, %s) ON DUPLICATE KEY UPDATE %i = %s',
				array(
					$table_name,
					'option_name',
					'option_value',
					$option_name,
					$option_value,
					'option_value',
					$option_value,
				)
			)
		);
		static::update_into_cache( $option_name, $option_value, $force_update_from_db );
	}

	/**
	 * Deletes option with provided option name.
	 *
	 * @param string $option_name Option name.
	 * @param bool   $force_update_from_db Force update from database flag.
	 *
	 * @return void
	 */
	public static function delete_option( string $option_name, bool $force_update_from_db = false ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare(
				'DELETE FROM %i WHERE %i = %s',
				array(
					static::TABLE_NAME,
					'option_name',
					$option_name,
				)
			)
		);
		static::delete_from_cache( $option_name, $force_update_from_db );
	}

	/**
	 * Gets live mode secret key from database
	 *
	 * @return string Live mode secret key
	 */
	public static function get_live_mode_key(): string {
		return static::get_option( static::OPTION_LIVE_MODE_SECRET_KEY );
	}

	/**
	 * Gets live/test mode secret key from database
	 *
	 * @return string Live/test mode secret key
	 * @phpstan-return string
	 */
	public static function get_current_mode_key(): string {
		$mode_type = static::get_mode_type();
		switch ( $mode_type ) {
			case static::MODE_TEST:
				return static::get_option( static::OPTION_TEST_MODE_SECRET_KEY );
			case static::MODE_LIVE:
			default:
				return static::get_option( static::OPTION_LIVE_MODE_SECRET_KEY );
		}
	}

	/**
	 * Gets test mode secret key from database
	 *
	 * @return string Test mode secret key
	 */
	public static function get_test_mode_key(): string {
		return static::get_option( static::OPTION_TEST_MODE_SECRET_KEY );
	}

	/**
	 * Gets mode type key from database
	 *
	 * @return self::MODE_LIVE|self::MODE_TEST
	 */
	public static function get_mode_type(): int {
		$mode_type = static::get_option( static::OPTION_MODE_TYPE );
		if ( '' === $mode_type ) {
			$mode_type = static::MODE_LIVE;
		}
		return $mode_type;
	}

	/**
	 * Gets live mode masked secret key
	 *
	 * @return string Live mode masked secret key
	 */
	public static function get_live_mode_masked_key(): string {
		$key = static::get_live_mode_key();
		if ( '' === $key ) {
			return '';
		}
		if ( mb_strlen( $key ) <= 16 ) {
			return str_repeat( '*', 12 );
		}

		return mb_substr( $key, 0, 8 ) . '...' . mb_substr( $key, - 4 );
	}

	/**
	 * Gets test mode masked secret key
	 *
	 * @return string Test mode masked secret key
	 */
	public static function get_test_mode_masked_key(): string {
		$key = static::get_test_mode_key();
		if ( '' === $key ) {
			return '';
		}
		if ( mb_strlen( $key ) <= 16 ) {
			return str_repeat( '*', 12 );
		}

		return mb_substr( $key, 0, 8 ) . '...' . mb_substr( $key, - 4 );
	}

	/**
	 * Gets live/test mode secret key from database
	 *
	 * @return string Live/test mode secret key
	 */
	public static function get_current_mode_masked_key(): string {
		$mode_type = static::get_mode_type();
		switch ( $mode_type ) {
			case static::MODE_TEST:
				return static::get_test_mode_masked_key();
			case static::MODE_LIVE:
			default:
				return static::get_live_mode_masked_key();
		}
	}

	/**
	 * Checks if live mode enabled in Stripe Tax settings
	 *
	 * @return bool True, if live mode enabled, false if not
	 */
	public static function is_live_mode_enabled(): bool {
		return ( static::get_option( static::OPTION_LIVE_MODE_ENABLED ) === '1' );
	}

	/**
	 * Enables or disables live mode, depending on $enable function argument.
	 *
	 * @param bool $enable True, to enable, and false, to disable live mode.
	 *
	 * @return void
	 */
	public static function enable_live_mode( bool $enable ) {
		if ( $enable ) {
			static::update_option( static::OPTION_LIVE_MODE_ENABLED, '1' );

			return;
		}
		static::update_option( static::OPTION_LIVE_MODE_ENABLED, '0' );
	}

	/**
	 * Get fee tax code from database
	 *
	 * @return string Fee tax code
	 */
	public static function get_tax_code(): string {
		$tax_code = static::get_option( static::OPTION_TAX_CODE );
		if ( '' === $tax_code ) {
			return static::DEFAULT_OPTION_TAX_CODE;
		}
		return $tax_code;
	}


	/**
	 * Get fee tax code from database
	 *
	 * @return string Fee tax code
	 */
	public static function get_fee_tax_code(): string {
		$fee_tax_code = static::get_option( static::OPTION_FEE_TAX_CODE );
		if ( '' === $fee_tax_code ) {
			return static::DEFAULT_OPTION_FEE_TAX_CODE;
		}
		return $fee_tax_code;
	}

	/**
	 * Stripe tax for WooCommerce output rendered select.
	 *
	 * @param array  $select_array Selected array.
	 * @param string $name The name.
	 * @param string $selected Selected.
	 * @param string $id Id.
	 * @param string $class_list Class list.
	 */
	public static function stripe_tax_for_woocommerce_output_rendered_select( array $select_array, string $name, string $selected = '', string $id = '', string $class_list = '' ) {
		if ( count( $select_array ) < 1 ) {
			return;
		}
		echo '<select autocomplete="off" name="' . esc_attr( $name ) . '" class="wc-enhanced-select' . ( ( '' !== $class_list ) ? ' ' . esc_attr( $class_list ) : '' ) . '"'
			. ( ( '' !== $id ) ? ' id="' . esc_attr( $id ) . '"' : '' )

			. '>';
		foreach ( $select_array as $key => $value ) {
			echo '<option value="' . esc_attr( $key ) . '"' . selected( $selected, $key, false ) . '>' . esc_html( $value ) . '</option>';
		}
		echo '</select>';
	}
}