<?php
/**
 * File for checking settings on save.
 *
 * @package Klarna_Checkout/Classes
 */

use Krokedil\Klarna\Features;
use Krokedil\Klarna\PluginFeatures;

/**
 * Class for checking settings on save.
 */
class KP_Settings_Saved {
	const PROD = 'Production';
	const TEST = 'Test';

	/**
	 * If there was an error detected or not.
	 *
	 * @var boolean
	 */
	private $error = false;

	/**
	 * Error message array.
	 *
	 * @var array
	 */
	private $message = array();

	/**
	 * Class constructor.
	 */
	public function __construct() {
		add_action( 'woocommerce_update_options_checkout_klarna_payments', array( $this, 'update_credential_features' ), 5 );
		add_action( 'woocommerce_update_options_checkout_klarna_payments', array( $this, 'check_api_credentials' ), 10 );

		add_filter( 'woocommerce_settings_api_sanitized_fields_klarna_payments', array( $this, 'sanitize_settings' ) );
	}

	/**
	 * Sanitize settings before saving.
	 *
	 * @param array $settings The settings to sanitize.
	 *
	 * @return array The sanitized settings.
	 */
	public function sanitize_settings( $settings ) {
		$client_id_messages = array();

		foreach ( $settings as $key => $value ) {
			// If the key contains the string 'client_id_' then we should sanitize it.
			if ( strpos( $key, 'client_id_' ) !== false ) {
				$sanitized_client_id = klarna_sanitize_client_id( $value );

				if ( ! empty( $value ) && empty( $sanitized_client_id ) ) {
					$client_id_messages[] = $this->get_client_id_error_message( $key );
				}

				$settings[ $key ] = $sanitized_client_id;
				continue;
			}
		}

		$this->maybe_handle_client_id_error( $client_id_messages );

		return $settings;
	}

	/**
	 * Stores client ID validation errors if any were detected.
	 *
	 * @param array $messages Client ID error messages.
	 *
	 * @return void
	 */
	private function maybe_handle_client_id_error( $messages ) {
		if ( empty( $messages ) ) {
			delete_option( 'kp_client_id_error' );
			return;
		}

		$messages = array_values( array_unique( $messages ) );
		if ( false === get_option( 'kp_client_id_error', false ) ) {
			add_option( 'kp_client_id_error', $messages, '', 'no' );
			return;
		}
		update_option( 'kp_client_id_error', $messages, false );
	}

	/**
	 * Gets a readable validation message for an invalid client ID field.
	 *
	 * @param string $key Client ID setting key.
	 *
	 * @return string
	 */
	private function get_client_id_error_message( $key ) {
		$environment = _x( 'production', 'environment', 'klarna-payments-for-woocommerce' );
		$market      = '';
		if ( preg_match( '/^(test_)?client_id_(.+)$/', $key, $matches ) ) {
			$environment = ! empty( $matches[1] ) ? _x( 'test', 'environment', 'klarna-payments-for-woocommerce' ) : _x( 'production', 'environment', 'klarna-payments-for-woocommerce' );
			$market      = strtoupper( $matches[2] );
		}

		if ( ! empty( $market ) ) {
			return sprintf(
				/* translators: 1: environment (production or test), 2: market code (country code) */
				__( 'The Client ID for %1$s credentials in country %2$s is invalid and has been cleared.', 'klarna-payments-for-woocommerce' ),
				$environment,
				$market
			);
		}

		return __( 'A Client ID is invalid and was cleared.', 'klarna-payments-for-woocommerce' );
	}

	/**
	 * Update the credentials feature availability when they are saved.
	 *
	 * @return void
	 */
	public function update_credential_features() {
		KP_WC()->plugin_features()->process_all_api_credentials();
	}

	/**
	 * Clears whitespace from API settings.
	 *
	 * @return void
	 */
	public function check_api_credentials() {
		// Get settings from the Klarna Payments settings.
		$options = get_option( 'woocommerce_klarna_payments_settings', array() );
		update_option( 'kp_has_valid_credentials', 'no' );
		$eu_countries                     = KP_Form_Fields::available_countries( 'eu' );
		$unavailable_features_credentials = array();

		// If not enabled bail.
		if ( $options && 'yes' !== $options['enabled'] ) {
			return;
		}
		$countries              = array_keys( KP_Form_Fields::$kp_form_auto_countries );
		$combine_eu_credentials = $options['combine_eu_credentials'] ?? 'no';

		foreach ( $countries as $cc ) {
			$cc       = 'yes' === $combine_eu_credentials && isset( $eu_countries[ $cc ] ) ? 'eu' : $cc;
			$cc       = 'uk' === $cc ? 'gb' : $cc;
			$password = '';
			$username = '';

			// If the credentials does not have the Payments feature enabled, skip them from testing.
			if ( ! PluginFeatures::is_available( Features::PAYMENTS, $cc ) ) {
				continue;
			}

			if ( 'yes' !== $options['testmode'] ) {
				// Live.
				if ( '' !== $options[ 'merchant_id_' . $cc ] ) {
					$username = $options[ 'merchant_id_' . $cc ];
					$password = $options[ 'shared_secret_' . $cc ];

					// Create request arguments.
					$args = array(
						'username' => $username,
						'password' => $password,
						'country'  => $cc,
						'testmode' => false,
					);

					if ( 'eu' !== $cc ) {
						$test_response = ( new KP_Test_Credentials( $args ) )->request();
						$this->process_test_response( $test_response, self::PROD, $cc );
					}
				}
			} elseif ( '' !== $options[ 'test_merchant_id_' . $cc ] ?? '' ) { // Test.
				$username = $options[ 'test_merchant_id_' . $cc ] ?? '';
				$password = $options[ 'test_shared_secret_' . $cc ] ?? '';

				// Create request arguments.
				$args = array(
					'username' => $username,
					'password' => $password,
					'country'  => $cc,
					'testmode' => true,
				);

				if ( 'eu' !== $cc ) {
					$test_response = ( new KP_Test_Credentials( $args ) )->request();
					$this->process_test_response( $test_response, self::TEST, $cc );
				}
			}

			// If the password and username is set and combined EU credentials are enabled, set the valid credentials flag.
			if ( 'eu' === $cc && ! empty( $password ) && ! empty( $username ) ) {

				if ( 'yes' !== get_option( 'kp_has_valid_credentials', 'no' ) ) {
					update_option( 'kp_has_valid_credentials', 'yes' );
				}
			}

			if ( ! empty( $password ) && ! isset( $unavailable_features_credentials[ $cc ] ) ) {
				$unavailable_features_credentials[ $cc ] = array(
					'mode'          => 'yes' === $options['testmode'] ? 'test' : 'live',
					'shared_secret' => $password,
					'country_code'  => $cc,
				);

				$this->maybe_handle_error();
			}
		}
	}

	/**
	 * Processes the test response.
	 *
	 * @param array|WP_Error $test_response The response from the test.
	 * @param string         $test The test that was run.
	 * @param string         $cc The county code.
	 * @return void
	 */
	public function process_test_response( $test_response, $test, $cc ) {
		// If this is not a WP Error then its ok.
		if ( ! is_wp_error( $test_response ) ) {
			// Set the valid credentials flag as there is at least one valid set of credentials.
			if ( 'yes' !== get_option( 'kp_has_valid_credentials', 'no' ) ) {
				update_option( 'kp_has_valid_credentials', 'yes' );
			}
			return;
		}
		$cc    = strtoupper( $cc );
		$code  = $test_response->get_error_code();
		$error = $test_response->get_error_message();
		$data  = json_decode( $test_response->get_error_data() ?? '', true );

		if ( 400 === $code || 401 === $code ) {
			switch ( $code ) {
				case 400:
					$message = "It seems like your Klarna $cc $test credentials are not configured correctly, please review your Klarna contract and ensure that your account is configured correctly for this country. ";
					break;
				case 401:
					$message = "Your Klarna $cc $test credentials are not authorized. Please verify the credentials and environment (production or test mode) or remove these credentials and save again. API credentials only work in either production or test, not both environments. ";
					break;
			}
			$message .= "API error code: $code, Klarna API error message: $error";

			if ( isset( $data['correlation_id'] ) ) {
				$correlation_id = $data['correlation_id'];
				$message       .= " Klarna correlation_id: $correlation_id";
			}

			$this->message[] = $message;
			$this->error     = true;
		}
	}

	/**
	 * Adds a error message if an error was detected.
	 *
	 * @return void
	 */
	public function maybe_handle_error() {
		// Remove any potential error displays if there are no errors detected.
		if ( ! $this->error ) {
			delete_option( 'kp_credentials_error' );
			return;
		}
		update_option( 'kp_credentials_error', $this->message );
	}

	/**
	 * Displays errors if they exists for the credentials check.
	 *
	 * @return void
	 */
	public static function maybe_show_errors() {
		$error_messages     = get_option( 'kp_credentials_error' );
		$client_id_messages = get_option( 'kp_client_id_error' );

		// If plugin file exists.
		if ( $error_messages ) {
			?>
				<div class="kp-message notice notice-error">
			<?php
			foreach ( $error_messages as $error_message ) {
				?>
					<p><?php echo wp_kses_post( $error_message ); ?></p>
				<?php } ?>
				</div>
				<?php
		}

		if ( $client_id_messages ) {
			?>
				<div class="kp-message notice notice-error">
			<?php
			foreach ( $client_id_messages as $client_id_message ) {
				?>
					<p><?php echo wp_kses_post( $client_id_message ); ?></p>
				<?php } ?>
				</div>
				<?php
		}
	}
}
new KP_Settings_Saved();
