<?php
/**
 * Tax Settings service
 *
 * @package Stripe\StripeTaxForWooCommerce\Stripe
 */

namespace Stripe\StripeTaxForWooCommerce\Stripe;

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

use stdClass;
use Stripe\StripeTaxForWooCommerce\SDK\lib\Exception\ApiErrorException;
use Stripe\StripeTaxForWooCommerce\SDK\lib\Service\Tax\SettingsService;
use Exception;
use Stripe\StripeTaxForWooCommerce\SDK\lib\Tax\Settings;
use Stripe\StripeTaxForWooCommerce\SDK\lib\Util\Util;
use Stripe\StripeTaxForWooCommerce\Stripe\Exception\CountryStateException;
use Stripe\StripeTaxForWooCommerce\Stripe\Exception\CountrySupportException;
use Stripe\StripeTaxForWooCommerce\Stripe\Exception\TaxBehaviorException;
use Stripe\StripeTaxForWooCommerce\WooCommerce\ErrorRenderer;

/**
 * Tax Setting service
 */
class TaxSettings {
	use StripeClientTrait;

	const CACHE_TTL_SECONDS      = 86400; // 24 hours.
	const CACHE_TRANSIENT_PREFIX = 'stripe_tax_settings_';

	/**
	 * Stripe API key
	 *
	 * @var string
	 */
	protected $api_key = '';

	/**
	 * Settings array
	 *
	 * @var array
	 */
	protected static $settings = array();

	/**
	 * Countries mapping array.
	 * Maps country name, that may be received from Stripe to ISO 3166-1 alpha-2 country code.
	 *
	 * @var array
	 */
	protected static $countries_mapping = array();

	/**
	 * Creates TaxSettings service
	 *
	 * @param string $api_key API key.
	 */
	public function __construct( string $api_key ) {
		$this->api_key = $api_key;
	}

	/**
	 * Get country mapping, that maps country names, that may be received from Stripe into ISO 3166-1 alpha-2 country code.
	 *
	 * @return array
	 */
	public static function get_mappings() {
		if ( empty( static::$countries_mapping ) ) {
			static::$countries_mapping = wp_json_file_decode( STRIPE_TAX_FOR_WOOCOMMERCE_JSON_DIR . 'countries.json' );
		}

		return static::$countries_mapping;
	}

	/**
	 * Convert state name, that can be received from Stripe into state code
	 *
	 * @param string $country Country.
	 * @param string $state State.
	 *
	 * @return string
	 */
	protected function convert_state_name_into_state_code( $country, $state ) {
		$mappings = static::get_mappings();

		if ( empty( $mappings->$country ) ) {
			return $state;
		}

		foreach ( $mappings->$country as $state_data ) {
			if ( $state_data->key === $state ) {
				$state = $state_data->iso;
				break;
			}
		}

		return $state;
	}

	/**
	 * Get settings from API
	 *
	 * @return object|Settings
	 * @throws ApiErrorException In case of API error.
	 * @see https://stripe.com/docs/api/tax/settings/retrieve
	 */
	protected function get_from_api_call(): object {
		$stripe_client                         = $this->get_stripe_client( $this->api_key );
		$settings                              = StripeTaxPluginHelper::fill_stripe_tax_settings_object( $stripe_client->tax->settings->retrieve( array() ) );
		$settings->head_office->address->state = $this->convert_state_name_into_state_code( $settings->head_office->address->country, $settings->head_office->address->state );

		return $settings;
	}

	/**
	 * Get tax settings from cache or API call
	 *
	 * @param bool $force_api_call Force API call.
	 *
	 * @return object|Settings
	 * @throws ApiErrorException In case of API error.
	 * @see https://stripe.com/docs/api/tax/settings/retrieve
	 */
	public function get_settings( $force_api_call = false ) {
		if ( array_key_exists( $this->api_key, static::$settings ) && ( ! $force_api_call ) ) {
			return static::$settings[ $this->api_key ];
		}

		if ( ! $force_api_call ) {
			$cached_settings = $this->get_from_persistent_cache();
			if ( ! is_null( $cached_settings ) ) {
				static::$settings[ $this->api_key ] = $cached_settings;

				return static::$settings[ $this->api_key ];
			}
		}

		static::$settings[ $this->api_key ] = $this->get_from_api_call();
		$this->set_to_persistent_cache( static::$settings[ $this->api_key ] );

		return static::$settings[ $this->api_key ];
	}

	/**
	 * Get persistent cache key for tax settings.
	 *
	 * @return string
	 */
	protected function get_persistent_cache_key(): string {
		return self::CACHE_TRANSIENT_PREFIX . md5( $this->api_key );
	}

	/**
	 * Read tax settings from persistent cache.
	 *
	 * @return object|null
	 */
	protected function get_from_persistent_cache() {
		$cache = get_transient( $this->get_persistent_cache_key() );

		if ( false === $cache || ! is_array( $cache ) ) {
			return null;
		}

		return Util::convertToStripeObject( $cache, array() );
	}

	/**
	 * Save tax settings to persistent cache.
	 *
	 * @param object $settings Tax settings object.
	 * @return void
	 */
	protected function set_to_persistent_cache( $settings ): void {
		$payload = json_decode( wp_json_encode( $settings ), true );
		set_transient( $this->get_persistent_cache_key(), $payload, self::CACHE_TTL_SECONDS );
	}

	/**
	 * Clear all caches for current API key.
	 *
	 * @return void
	 */
	protected function clear_cache(): void {
		unset( static::$settings[ $this->api_key ] );
		delete_transient( $this->get_persistent_cache_key() );
	}

	/**
	 * Updates Stripe Tax settings in cache and Stripe dashboard
	 *
	 * @param object $tax_settings Tax settings.
	 * @param bool   $no_api_call Set true, if no API call needed.
	 *
	 * @return void
	 * @throws ApiErrorException In case of API error.
	 * @see https://stripe.com/docs/api/tax/settings/update
	 */
	public function set_settings( object $tax_settings, bool $no_api_call = false ): void {
		static::$settings[ $this->api_key ] = $tax_settings;
		$this->set_to_persistent_cache( $tax_settings );
		if ( $no_api_call ) {
			return;
		}
		$is_set_settings_success = false;
		try {
			$client                             = $this->get_stripe_client( $this->api_key );
			$setting_service                    = new SettingsService( $client );
			$save_setting                       = array();
			$save_setting['defaults[tax_code]'] = $this->get_tax_code();
			$setting_service->update( $save_setting );
			$is_set_settings_success = true;
		} catch ( TaxBehaviorException $exception ) {
			ErrorRenderer::set_error_object( 'setting_tax_behavior_error', $exception->getMessage(), 'error' );
		} catch ( Exception $exception ) {
			\WC_Admin_Settings::add_error( $exception->getMessage() );
		} finally {
			if ( ! $is_set_settings_success ) {
				StripeTaxPluginHelper::set_stripe_settings_update_error_flag();
			}
			$this->clear_cache();
			$this->get_settings( true );
		}
	}

	/**
	 * Get Tax Settings country
	 *
	 * @return string
	 * @throws ApiErrorException In case of API error.
	 */
	public function get_country(): string {
		return $this->get_settings()->head_office->address->country ?? '';
	}

	/**
	 * Get Tax Settings address line1
	 *
	 * @return string
	 * @throws ApiErrorException In case of API error.
	 */
	public function get_line1(): string {
		return $this->get_settings()->head_office->address->line1 ?? '';
	}

	/**
	 * Get Tax Settings address line2
	 *
	 * @return string
	 * @throws ApiErrorException In case of API error.
	 */
	public function get_line2(): string {
		return $this->get_settings()->head_office->address->line2 ?? '';
	}

	/**
	 * Get Tax Settings city
	 *
	 * @return string
	 * @throws ApiErrorException In case of API error.
	 */
	public function get_city(): string {
		return $this->get_settings()->head_office->address->city ?? '';
	}

	/**
	 * Get Tax Settings state
	 *
	 * @return string
	 * @throws ApiErrorException In case of API error.
	 */
	public function get_state(): string {
		return $this->get_settings()->head_office->address->state ?? '';
	}

	/**
	 * Get Tax Settings postal code
	 *
	 * @return string
	 * @throws ApiErrorException In case of API error.
	 */
	public function get_postal_code(): string {
		return $this->get_settings()->head_office->address->postal_code ?? '';
	}

	/**
	 * Get Tax Settings tax code
	 *
	 * @return string
	 * @throws ApiErrorException In case of API error.
	 */
	public function get_tax_code(): string {
		return $this->get_settings()->defaults->tax_code ?? '';
	}

	/**
	 * Get Tax Settings tax behavior
	 *
	 * @return string
	 */
	public function get_tax_behavior(): string {
		return wc_prices_include_tax() ? 'inclusive' : 'exclusive';
	}

	/**
	 * Get Tax Settings from POST request
	 *
	 * @param bool $live Live.
	 *
	 * @return stdClass
	 */
	public static function get_from_post_request( bool $live ): object {
		check_admin_referer( 'woocommerce-settings' );
		if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'woocommerce-settings' ) ) {
			exit;
		}

		$prefix   = 'stripe_tax_for_woocommerce_' . ( $live ? 'live' : 'test' ) . '_mode_';
		$tax_code = $prefix . 'tax_code';

		$tax_settings                     = new stdClass();
		$tax_settings->object             = 'tax.settings';
		$tax_settings->defaults           = new stdClass();
		$tax_settings->defaults->tax_code = sanitize_text_field( wp_unslash( $_POST[ $tax_code ] ?? null ) );

		return $tax_settings;
	}
}