<?php
namespace Aelia\WC\EU_VAT_Assistant\Validation\Services;

use Aelia\WC\EU_VAT_Assistant\Definitions;

if(!defined('ABSPATH')) { exit; } // Exit if accessed directly

/**
 * Interface for a validation service client.
 *
 * @since 2.0.3.201229
 */
interface IVAT_Number_Validation_Service {
	public static function get_id(): string;
	public static function get_name(): string;
	public function get_errors();
	public static function get_supported_countries(): array;
	public static function get_settings(\Aelia\WC\Settings $settings_controller): array;
	// @since 2.0.15.210319
	public static function settings_section_callback();
	public static function validate_settings(array $settings, array &$errors = array()): bool;

	public function is_available(): bool;
	public function validate_vat_number($country, $vat_number, $requester_country, $requester_vat_number);
}
/**
 * Implements a base class to interface with VAT number validation services.
 *
 * @since 2.0.1.201215
 */
abstract class VAT_Number_Validation_Service implements IVAT_Number_Validation_Service{
	/**
	 * Identifies the validator, to distinguish between the ones used for
	 * different countries.
	 */
	public static $id = 'base-vat-number-validation-service';

	/**
	 * Describes the validator.
	 *
	 * @var string
	 */
	public static $name = 'Base VAT Number Validation Service';

	/**
	 * The logger that will be used by the validator.
	 *
	 * @var Aelia\WC\Logger
	 */
	protected static $logger = null;

	/**
	 * Indicates if the debug mode is enabled.
	 *
	 * @var bool
	 */
	protected static $debug_mode = false;

	/**
	 * The errors generated by the class.
	 * @var array
	 */
	protected $errors = array();

	/**
	 * Returns the errors generated by the validation process.
	 *
	 * @return array
	 */
	public function get_errors() {
		return $this->errors;
	}

	/**
	 * Returns the ID of the validator.
	 *
	 * @return string
	 * @since 2.0.3.201229
	 */
	public static function get_id(): string {
		return static::$id;
	}

	/**
	 * Returns the name of the validator.
	 *
	 * @return string
	 * @since 2.0.3.201229
	 */
	public static function get_name(): string {
		return static::$name;
	}

	/**
	 * Constructor.
	 *
	 * @param array $args
	 */
	public function __construct(array $args) {
		static::$logger = $args['logger'];
		static::$debug_mode = $args['debug_mode'] ?? false;
	}

	/**
	 * Calls a remote API and returns the response returned by the service.
	 *
	 * @param string $api_url
	 * @param array $request_args
	 * @param string $method
	 * @return array|WP_Error
	 */
	protected function call_api(string $api_url, array $request_args = array(), $method = 'GET') {
		$request_args['headers'] = $request_args['headers'] ?? array();
		// We might have to specific the user agent, because the HMRC service seems to be "picky" and
		// block WordrPress requests
		$request_args['headers']['User-Agent'] = 'Mozilla/5.0';

		// If we reach this point, the response is a "WP error". We can log it,
		// for debugging purposes
		static::$logger->info(__('Trying to call the remote API.', Definitions::TEXT_DOMAIN), array(
			'Validator ID' => static::get_id(),
			'API URL' => $api_url,
			'Method' => $method,
			'Request Args' => $request_args,
		));

		// Fetch the authorisation token from the remote API, using the specified method
		switch(strtoupper($method)) {
			case 'POST':
				$api_response = wp_remote_post($api_url, $request_args);
				break;
			case 'GET':
			default:
				$api_response = wp_remote_get($api_url, $request_args);
				break;
		}

		static::$logger->info(__('Trying to call the remote API.', Definitions::TEXT_DOMAIN), array(
			'Validator ID' => static::get_id(),
			'API URL' => $api_url,
			'Request Args' => $request_args,
		));

		if(is_wp_error($api_response)) {
			// If we reach this point, the response is a "WP error". We can log it,
			// for debugging purposes
			static::$logger->error($error_msg = __('An error occurred while calling the remote API.', Definitions::TEXT_DOMAIN), array(
				'Validator ID' => static::get_id(),
				'API URL' => $api_url,
				'Request Args' => $request_args,
				'Response' => $api_response,
			));
			$this->errors[] = $error_msg;
		}
		else {
			// Verify that the request was successful
			$response_code = wp_remote_retrieve_response_code($api_response);
			if($response_code != 200) {
				static::$logger->error($error_msg = __('The call to the remote service was successful, but the HTTP response code indicates an error.', Definitions::TEXT_DOMAIN) .
																					' ' .
																					__('Check previous log entries for the details of the call to the remote API and response returned by it.', Definitions::TEXT_DOMAIN), array(
					'Validator ID' => static::get_id(),
					'API URL' => $api_url,
					'Request Args' => $request_args,
					'Response Body' => wp_remote_retrieve_body($api_response),
					'Response' => $api_response,
				));
			}
			else {
				static::$logger->debug(__('The call to the remote service completed successfully.', Definitions::TEXT_DOMAIN), array(
					'Validator ID' => static::get_id(),
					'API URL' => $api_url,
					'Request Args' => $request_args,
					'Response' => $api_response,
				));
			}
		}

		return $api_response;
	}

	/**
	 * Returns the ID of a transient, to be used by this specific validator. This method ensures that
	 * different validator use different transients, and that such transients are
	 *
	 * @param string $key
	 * @return string
	 */
	protected function get_transient_id(array $args) {
		return implode('_', array(
			static::get_id(),
			\WC_Cache_Helper::get_transient_version(Definitions::TRANSIENT_VAT_VALIDATION_SERVICE),
		)) . sha1(json_encode($args));
	}

	/**
	 * Returns an "empty" VAT number validation response, which can be returned in cases like the following:
	 * - Could not reach the remote validation service.
	 * - Could not authenticate the remote validation service.
	 * - Could not get a valid response from the validation service.
	 *
	 * And so on. In all these cases, the validation didn't succeed, nor did it fail. The empty response
	 * means "can't say if the VAT number is valid".
	 *
	 * @param array errors A list of errors to be added to the response.
	 * @return array
	 * @since 2.0.3.201229
	 */
	protected static function get_empty_validation_response(array $errors = array()): array {
		return array(
			'valid' => null,
			'company_name' => '',
			'company_address' => '',
			'requestidentifier' => '',
			'errors' => $errors,
			'raw_response' => '',
			// Add the validation source, for reference
			// @since 2.0.3.201229
			'validation_source' => '',
		);
	}

	/**
	 * Returns the countries supported by the validation service.
	 *
	 * @return array
	 * @since 2.0.3.201229
	 */
	public abstract static function get_supported_countries(): array;

	/**
	 * Returns the settings required by the validation service.
	 *
	 * @return array
	 * @since 2.0.3.201229
	 */
	public static function get_settings(\Aelia\WC\Settings $settings_controller): array {
		return array();
	}

	/**
	 * This method can be used to display some information related to the settings required by
	 * the service in the corresponding settings section.
	 *
	 * @return array
	 * @since 2.0.15.210319
	 */
	public static function settings_section_callback() {
		// This method should be implemented by descendant classes
	}

	/**
	 * Validates the settings required by the validation service.
	 *
	 * @param array $settings
	 * @param array $errors Returns the list of errors occurred while validating the settings.
	 * @return bool
	 * @since 2.0.3.201229
	 */
	public abstract static function validate_settings(array $settings, array &$errors = array()): bool;

	/**
	 * Indicates if the validation service is available (e.g. if it has been initialised correctly).
	 *
	 * @return bool
	 * @since 2.0.3.201229
	 */
	public abstract function is_available(): bool;

	/**
	 * Validates a VAT number.
	 *
	 * @param string $country The country code to which the VAT number belongs.
	 * @param string $vat_number The VAT number to validate.
	 * @param string $requester_country The country code of the requester.
	 * @param string $requester_vat_number The VAT number of the requester.
	 * @return array|bool An array with the validation response returned by the remote service, or false when the request could not be sent for some reason.
	 */
	public abstract function validate_vat_number($country, $vat_number, $requester_country = null, $requester_vat_number = null);
}
