<?php

namespace Ilabs\Inpost_International\API;

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


use Exception;
use Ilabs\Inpost_International\Backend\Backend;
use Ilabs\Inpost_International\Emails\TrackingInfoEmail;
use Ilabs\Inpost_International\Helpers\Helper;

/**
 * API
 */
class API {

	/**
	 * PRODUCTION_ENVIRONMENT
	 */
	const PRODUCTION_ENVIRONMENT = 'https://api.inpost-group.com';
	/**
	 * SANDBOX_ENVIRONMENT
	 */
	const SANDBOX_ENVIRONMENT = 'https://sandbox-api.inpost-group.com';

	/**
	 * PRODUCTION_ENVIRONMENT_OPENID_CONNECT
	 */
	const PRODUCTION_ENVIRONMENT_OPENID_CONNECT = 'https://login.inpost-group.com/realms/external/protocol/openid-connect/auth';
	/**
	 * SANDBOX_ENVIRONMENT_OPENID_CONNECT
	 */
	const SANDBOX_ENVIRONMENT_OPENID_CONNECT = 'https://sandbox-login.inpost-group.com/realms/external/protocol/openid-connect/auth';

	/**
	 * Name of options where client_id and client_secret stored
	 */
	const MAIN_OPTIONS = 'inpost_international_settings_auth';
	/**
	 * TOKEN_EXPIRATION
	 */
	const TOKEN_EXPIRATION = 'api_token_expiration';
	/**
	 * REFRESH_TOKEN
	 */
	const REFRESH_TOKEN = 'api_refresh_token';
	/**
	 * REFRESH_TOKEN_EXPIRATION
	 */
	const REFRESH_TOKEN_EXPIRATION = 'api_refresh_token_expiration';
	/**
	 * API_TOKEN
	 */
	const API_TOKEN = 'api_token';

	/**
	 * CLIENT_ID_FIELD
	 */
	const CLIENT_ID_FIELD = 'client_id';
	/**
	 * CLIENT_SECRET_FIELD
	 */
	const CLIENT_SECRET_FIELD = 'client_secret';
	/**
	 * API_TYPE_FIELD
	 */
	const API_TYPE_FIELD = 'api_type';
	/**
	 * REDIRECT_CALLBACK_URI
	 */
	const REDIRECT_CALLBACK_URI = 'inpost_intl_oauth_token_callback/';

	/**
	 * API Token.
	 *
	 * @var string
	 */
	private $api_token;

	/**
	 * Refresh Token.
	 *
	 * @var string
	 */
	private $refresh_token;

	/**
	 * Token Expires time stamp.
	 *
	 * @var string
	 */
	private $token_expires;

	/**
	 * Helper instance for utility functions.
	 *
	 * @since 1.0.0
	 * @var Helper An instance of the Helper class that provides utility methods.
	 */
	private Helper $helper;


	/**
	 * Constructor.
	 */
	public function __construct() {

		$this->api_token     = $this->get_plugin_option( self::API_TOKEN );
		$this->refresh_token = $this->get_plugin_option( self::REFRESH_TOKEN );
		$this->token_expires = $this->get_plugin_option( self::TOKEN_EXPIRATION );

		$this->helper = Helper::init();

		$this->hooks();

		if ( ! empty( $this->api_token ) ) {
			if ( $this->is_api_token_expired() ) {
				$this->refresh_api_token();
			}
		}
	}


	/**
	 * Hooks
	 *
	 * @return void
	 */
	public function hooks() {
		add_action( 'wp_ajax_inpost_intl_start_connection', array( $this, 'initial_connection_callback' ) );
		add_action( 'wp_loaded', array( $this, 'initial_get_api_token' ) );
	}



	/**
	 * Generate code verifier
	 *
	 * @return string
	 */
	public function generate_code_verifier() {

		$random_bytes  = wp_generate_password( 32, false );
		$code_verifier = rtrim( strtr( base64_encode( $random_bytes ), '+/', '-_' ), '=' );

		return $code_verifier;
	}

	/**
	 * Generate code challenge
	 *
	 * @param string $code_verifier $code_verifier.
	 *
	 * @return string
	 */
	public function generate_code_challenge( $code_verifier ) {

		update_option( 'inpost_intl_code_verifier', $code_verifier );
		$challenge      = hash( 'sha256', $code_verifier, true );
		$code_challenge = rtrim( strtr( base64_encode( $challenge ), '+/', '-_' ), '=' );
		update_option( 'inpost_intl_code_challenge', $code_challenge );

		return $code_challenge;
	}


	/**
	 * Get token expiration time
	 *
	 * @return false|mixed
	 */
	private function get_token_expiration_time() {
		return $this->get_plugin_option( self::TOKEN_EXPIRATION );
	}


	/**
	 * Get refresh token expiration time
	 *
	 * @return false|mixed
	 */
	private function get_refresh_token_expiration_time() {
		return $this->get_plugin_option( self::REFRESH_TOKEN_EXPIRATION );
	}


	/**
	 * Get url with params for initial auth on "login.inpost-group.com"
	 *
	 * @return void
	 */
	public function initial_connection_callback() {

		check_ajax_referer( 'inpost-intl-connection', 'nonce' );

		if ( ! empty( $_POST['client_id'] ) && ! empty( $_POST['client_secret'] ) ) {
			$client_id     = sanitize_text_field( wp_unslash( $_POST['client_id'] ) );
			$client_secret = sanitize_text_field( wp_unslash( $_POST['client_secret'] ) );
			if ( ! empty( $_POST['api_type'] ) ) {
				$api_type = sanitize_text_field( wp_unslash( $_POST['api_type'] ) );
			}

			$this->update_plugin_option( 'client_id', trim( $client_id ) );
			$this->update_plugin_option( 'client_secret', trim( $client_secret ) );
			$this->update_plugin_option( 'api_type', $api_type );

			$base_url = ! empty( $_POST['api_type'] ) && 'production' === sanitize_text_field( wp_unslash( $_POST['api_type'] ) )
				? self::PRODUCTION_ENVIRONMENT_OPENID_CONNECT
				: self::SANDBOX_ENVIRONMENT_OPENID_CONNECT;

			$code_verifier  = $this->generate_code_verifier();
			$code_challenge = $this->generate_code_challenge( $code_verifier );

			$redirect_uri = get_site_url( null, self::REDIRECT_CALLBACK_URI );

			$params = array(
				'client_id'             => $client_id,
				'redirect_uri'          => $redirect_uri,
				'response_type'         => 'code',
				'scope'                 => 'api:shipments:write offline_access api:tracking:read api:one-time-pickups:write api:points:read',
				'code_challenge'        => $code_challenge,
				'code_challenge_method' => 'S256',
			);

			$query_string = http_build_query( $params );
			$login_link   = $base_url . '?' . $query_string;

		}

		wp_send_json( array( 'login_link' => $login_link ) );
	}


	/**
	 * Checks if the API token has expired.
	 *
	 * @return bool
	 */
	public function is_api_token_expired() {
		$maybe_need_timeout = get_option( 'inpost_international_connection_timeout' );
		if ( $maybe_need_timeout && ( $maybe_need_timeout >= time() ) ) {
			return false;
		}
		if ( $this->get_token_expiration_time() <= time() ) {
			return true;
		} else {
			return false;
		}
	}


	/**
	 * Setup connection time-out if error is occured during exchange tokens proccess
	 *
	 * @return void
	 */
	private function setup_connection_timeout() {
		$timestamp = time() + 60;
		update_option( 'inpost_international_connection_timeout', $timestamp, true );
	}


	/**
	 * Get or refresh API token
	 *
	 * @return void
	 */
	public function initial_get_api_token() {

		if ( isset( $_SERVER['REQUEST_URI'] ) ) {
			$url                   = wp_parse_url( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
			$callback_redirect_uri = '/' . self::REDIRECT_CALLBACK_URI;
			if ( defined( 'WP_ENV' ) && file_exists( dirname( ABSPATH ) . '/config' ) ) {
				// fix for bedrock WP installs.
				$callback_redirect_uri = '/wp' . $callback_redirect_uri;
			}
			if ( isset( $url['path'] ) && $callback_redirect_uri === $url['path'] ) {
				if ( is_user_logged_in() && isset( $_GET['code'] ) ) {
					$code = ! empty( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : null;
					if ( $code ) {
						$this->exchange_one_time_code_to_tokens( $code );
					}
				}
			}
		}
	}


	/**
	 * Exchange one-time-code to tokens
	 *
	 * @param string $code one-time-code.
	 *
	 * @return void
	 */
	public function exchange_one_time_code_to_tokens( $code ) {

		$code_verifier = get_option( 'inpost_intl_code_verifier' );
		$redirect_uri  = get_site_url( null, self::REDIRECT_CALLBACK_URI );
		$environment   = $this->get_plugin_option( self::API_TYPE_FIELD );
		$client_id     = $this->get_plugin_option( self::CLIENT_ID_FIELD );
		$client_secret = $this->get_plugin_option( self::CLIENT_SECRET_FIELD );

		if ( $environment && ! empty( $client_id ) && ! empty( $client_secret ) ) {
			$api_url = self::SANDBOX_ENVIRONMENT;
			if ( 'production' === $environment ) {
				$api_url = self::PRODUCTION_ENVIRONMENT;
			}
		}

		$api_url = $api_url . '/auth/token';

		$args = array(
			'method'  => 'POST',
			'headers' => array(
				'Content-Type' => 'application/x-www-form-urlencoded',
			),
			'body'    => array(
				'client_id'     => $client_id,
				'client_secret' => $client_secret,
				'grant_type'    => 'authorization_code',
				'redirect_uri'  => $redirect_uri,
				'code'          => $code,
				'code_verifier' => $code_verifier,
			),
		);

		$response = wp_remote_post( $api_url, $args );

		$is_connected = false;

		$is_error_occurred = $this->maybe_initial_connection_error( $response );

		if ( $is_error_occurred ) {
			$redirect_url = 'admin.php?page=inpost-international-api&auth=failed&details=' . sanitize_text_field( $is_error_occurred );
			wp_safe_redirect( admin_url( $redirect_url ) );
			exit;
		} else {
			$token_data = json_decode( $response['body'] );
			if ( isset( $token_data->access_token ) && isset( $token_data->refresh_token ) && isset( $token_data->expires_in ) ) {
				$access_token_expires_in  = time() + (int) $token_data->expires_in - 60;
				$refresh_token_expires_in = time() + $token_data->refresh_expires_in;

				$acces_token_updated              = $this->update_plugin_option( self::API_TOKEN, $token_data->access_token );
				$acces_token_expiartion_updated   = $this->update_plugin_option( self::TOKEN_EXPIRATION, $access_token_expires_in );
				$refresh_token_updated            = $this->update_plugin_option( self::REFRESH_TOKEN, $token_data->refresh_token );
				$refresh_token_expiartion_updated = $this->update_plugin_option( self::REFRESH_TOKEN_EXPIRATION, $refresh_token_expires_in );

				if ( $acces_token_updated && $acces_token_expiartion_updated && $refresh_token_updated && $refresh_token_expiartion_updated ) {
					$is_connected = true;
				}
			}
		}

		if ( $is_connected ) {
			wp_safe_redirect( admin_url( 'admin.php?page=inpost-international-api&auth=success' ) );
		} else {
			wp_safe_redirect( admin_url( 'admin.php?page=inpost-international-api&auth=failed' ) );
		}
		exit;
	}


	/**
	 * Gets and saves a new API token
	 *
	 * @return void
	 */
	public function refresh_api_token() {

        if ( get_transient( 'inpost_intl_api_token_refreshing' ) ) {
            return;
        }

		$environment   = $this->get_plugin_option( self::API_TYPE_FIELD );
		$client_id     = $this->get_plugin_option( self::CLIENT_ID_FIELD );
		$client_secret = $this->get_plugin_option( self::CLIENT_SECRET_FIELD );
		$refresh_token = $this->get_plugin_option( self::REFRESH_TOKEN );

		if ( $environment && ! empty( $client_id ) && ! empty( $client_secret ) && ! empty( $refresh_token ) ) {
			$api_url = self::SANDBOX_ENVIRONMENT;
			if ( 'production' === $environment ) {
				$api_url = self::PRODUCTION_ENVIRONMENT;
			}

			$api_url = $api_url . '/auth/token';

			$args = array(
				'timeout' => 30,
				'method'  => 'POST',
				'headers' => array(
					'Content-Type' => 'application/x-www-form-urlencoded',
				),
				'body'    => array(
					'client_id'     => $client_id,
					'client_secret' => $client_secret,
					'grant_type'    => 'refresh_token',
					'refresh_token' => $this->refresh_token,
				),
			);

			set_transient( 'inpost_intl_api_token_refreshing', 1, 10 );

			$response = wp_remote_post( $api_url, $args );

			if ( is_wp_error( $response ) ) {

				$error_message = $response->get_error_message();

				inpost_intl_core()->inpost_logger(
					'Inpost International - refresh API token error',
					__METHOD__ . ': ' . __LINE__,
					array(
						'error_type'    => 'is_wp_error',
						'error_message' => $error_message,
					),
					'inpost-connection',
					'debug'
				);

				$this->setup_connection_timeout();

				delete_transient( 'inpost_intl_api_token_refreshing' );

				return;

			}

			$body      = wp_remote_retrieve_body( $response );
			$http_code = wp_remote_retrieve_response_code( $response );

			if ( 200 !== $http_code ) {
				$error_code_details = $this->get_api_status_code_details( $http_code );
				$additional_details = array();
				if ( ! empty( $response['body'] ) ) {
					$additional_details = json_decode( $response['body'] );
				}
				$logger = inpost_intl_core()->get_wc_logger();
				if ( $logger ) {
					$logger->log(
						'debug',
						'Inpost International - refresh API token error',
						array(
							'source'        => 'InPost International: refresh API token error',
							'error_details' => array(
								'debug_place'           => __METHOD__ . ': ' . __LINE__,
								'error_type'            => 'response code is not 200',
								'error_code'            => $http_code,
								'error_code_details'    => $error_code_details,
								'response_body_details' => ! empty( $additional_details ) ? $additional_details : $body,
							),
						)
					);
				}

				inpost_intl_core()->inpost_logger(
					'Inpost International - refresh API token error',
					__METHOD__ . ': ' . __LINE__,
					array(
						'debug_place'           => __METHOD__ . ': ' . __LINE__,
						'error_type'            => 'response code is not 200',
						'error_code'            => $http_code,
						'error_code_details'    => $error_code_details,
						'response_body_details' => ! empty( $additional_details ) ? $additional_details : $body,
					),
					'inpost-connection',
					'debug'
				);

				$this->setup_connection_timeout();

				delete_transient( 'inpost_intl_api_token_refreshing' );

				return;

			} elseif ( empty( $response['body'] ) ) {
				$logger = inpost_intl_core()->get_wc_logger();
				if ( $logger ) {
					$logger->log(
						'debug',
						'Inpost International - refresh API token error',
						array(
							'source'        => 'InPost International: refresh API token error',
							'error_details' => array(
								'debug_place' => __METHOD__ . ': ' . __LINE__,
								'error_type'  => 'response body is empty',
								'http_code'   => $http_code,
							),
						)
					);
				}

				inpost_intl_core()->inpost_logger(
					'Inpost International - refresh API token error',
					__METHOD__ . ': ' . __LINE__,
					array(
						'debug_place' => __METHOD__ . ': ' . __LINE__,
						'error_type'  => 'response body is empty',
						'http_code'   => $http_code,
					),
					'inpost-connection',
					'debug'
				);

				$this->setup_connection_timeout();

				delete_transient( 'inpost_intl_api_token_refreshing' );

				return;

			}

			$token_data = json_decode( $response['body'] );

			if ( ! isset( $token_data->access_token ) || ! isset( $token_data->refresh_token ) || ! isset( $token_data->expires_in ) ) {
				// something wrong with response body content.
				$logger = inpost_intl_core()->get_wc_logger();
				if ( $logger ) {
					$logger->log(
						'debug',
						'Inpost International - refresh API token error',
						array(
							'source'        => 'InPost International: refresh API token error',
							'error_details' => array(
								'debug_place'   => __METHOD__ . ': ' . __LINE__,
								'error_type'    => 'response body has not token data',
								'response_body' => $body,
							),
						)
					);
				}

				inpost_intl_core()->inpost_logger(
					'Inpost International - refresh API token error',
					__METHOD__ . ': ' . __LINE__,
					array(
						'debug_place'   => __METHOD__ . ': ' . __LINE__,
						'error_type'    => 'response body has not token data',
						'response_body' => $body,
					),
					'inpost-connection',
					'debug'
				);

				$this->setup_connection_timeout();

				delete_transient( 'inpost_intl_api_token_refreshing' );

				return;

			}

			$access_token_expires_in = time() + (int) $token_data->expires_in - 60;
			$refresh_token_expires_in = time() + (int) $token_data->refresh_expires_in;
			// update access tokens and time of expiration.

			$acces_token_updated              = $this->update_plugin_option( self::API_TOKEN, $token_data->access_token );
			$acces_token_expiartion_updated   = $this->update_plugin_option( self::TOKEN_EXPIRATION, $access_token_expires_in );
			//$refresh_token_updated            = $this->update_plugin_option( self::REFRESH_TOKEN, $token_data->refresh_token );
			$refresh_token_expiartion_updated = $this->update_plugin_option( self::REFRESH_TOKEN_EXPIRATION, $refresh_token_expires_in );

			delete_transient( 'inpost_intl_api_token_refreshing' );
		}
	}


	/**
	 * Check if some error is occured during initial connection proccess
	 *
	 * @param mixed $response $response.
	 *
	 * @return mixed
	 */
	private function maybe_initial_connection_error( $response ) {

		$is_error = false;

		if ( is_wp_error( $response ) ) {
			$logger = inpost_intl_core()->get_wc_logger();
			if ( $logger ) {
				$logger->log(
					'debug',
					'Inpost International - exchange one-time code to API tokens error',
					array(
						'source'        => 'InPost International: exchange one-time code to API tokens error',
						'error_details' => array(
							'debug_place'   => __METHOD__ . ': ' . __LINE__,
							'error_type'    => 'is_wp_error',
							'error_message' => $response->get_error_message(),
						),
					)
				);
			}

			return ! empty( $response->get_error_message() )
				? $response->get_error_message()
				: esc_html__( 'Initial connection error: type "is_wp_error".', 'inpost-international' );
		}

		if ( empty( $response['body'] ) ) {
			$logger = inpost_intl_core()->get_wc_logger();
			if ( $logger ) {
				$logger->log(
					'debug',
					'Inpost International - exchange one-time code to API tokens error',
					array(
						'source'        => 'InPost International: exchange one-time code to API tokens error',
						'error_details' => array(
							'debug_place' => __METHOD__ . ': ' . __LINE__,
							'error_type'  => 'response body is empty',
						),
					)
				);
			}

			return esc_html__( 'Initial connection error: response body is empty.', 'inpost-international' );

		} else {

			$token_data = json_decode( $response['body'] );

			if ( ! is_object( $token_data ) ) {
				$logger = inpost_intl_core()->get_wc_logger();
				if ( $logger ) {
					$logger->log(
						'debug',
						'Inpost International - exchange one-time code to API tokens error',
						array(
							'source'        => 'InPost International: exchange one-time code to API tokens error',
							'error_details' => array(
								'debug_place'   => __METHOD__ . ': ' . __LINE__,
								'error_type'    => 'response body is not object',
								'response_code' => ! empty( $response['response']['code'] ) ? $response['response']['code'] : '',
								'details'       => ! empty( $response['response']['message'] ) ? $response['response']['message'] : '',
							),
						)
					);

					\wc_get_logger()->debug( 'Response body: ', array( 'source' => 'inpost-international-exchange-one-time-code-to-api-tokens-error' ) );
					\wc_get_logger()->debug( print_r( $response['body'], true ), array( 'source' => 'inpost-international-exchange-one-time-code-to-api-tokens-error' ) );
				}

				return esc_html__( 'Initial connection error: response body is not object.', 'inpost-international' );
			}

			if ( ! property_exists( $token_data, 'access_token' ) || ! property_exists( $token_data, 'refresh_token' ) ) {

				$logger = inpost_intl_core()->get_wc_logger();
				if ( $logger ) {
					$logger->log(
						'debug',
						'Inpost International - exchange one-time code to API tokens error',
						array(
							'source'        => 'InPost International: exchange one-time code to API tokens error',
							'error_details' => array(
								'debug_place'   => __METHOD__ . ': ' . __LINE__,
								'error_type'    => 'response body does not contain token data',
								'response_body' => $response,
							),
						)
					);
				}
				return esc_html__( 'Initial connection error: response body does not contain token data.', 'inpost-international' );
			}
		}

		$http_code = wp_remote_retrieve_response_code( $response );

		if ( 200 !== $http_code ) {
			return $this->get_api_status_code_details( $http_code );
		}

		return $is_error;
	}


	/**
	 * Get API response details based on http code
	 *
	 * @param mixed $http_code $http_code.
	 *
	 * @return mixed
	 */
	private function get_api_status_code_details( $http_code ) {

		$response_code_details = $http_code;

		switch ( $http_code ) {
			case 304:
				return 'Code 304. ' . esc_html__( 'Indicates no changes in the HTTP request - no redirection.', 'inpost-international' );

			case 400:
				return 'Code 400. ' . esc_html__( 'Incorrect JSON data sent.', 'inpost-international' );

			case 401:
				return 'Code 401. ' . esc_html__( 'The user is not authenticated.', 'inpost-international' );

			case 403:
				return 'Code 403. ' . esc_html__( 'The user is authenticated but does not have rights to the requested resource.', 'inpost-international' );

			case 404:
				return 'Code 404. ' . esc_html__( 'The requested resource does not exist in the API.', 'inpost-international' );

			case 406:
				return 'Code 406. ' . esc_html__( 'An unsupported data type was passed in the Accept header.', 'inpost-international' );

			case 415:
				return 'Code 415. ' . esc_html__( 'An unsupported mediatype. An unsupported data type was passed in the Accept header.', 'inpost-international' );

			case 422:
				return 'Code 422. ' . esc_html__( 'Incorrect field values were sent, e.g., object validation returned an error, or some fields do not meet the criteria imposed on them by the resource.', 'inpost-international' );

			case 429:
				return 'Code 429. ' . esc_html__( 'Too many requests. The client exceeded the request limit.', 'inpost-international' );

			case 503:
				return 'Code 503. ' . esc_html__( 'Service Unavailable.', 'inpost-international' );

			default:
				return $response_code_details;

		}
	}


	/**
	 * Get plugin option
	 *
	 * @param string $key name of option.
	 *
	 * @return mixed
	 */
	private function get_plugin_option( $key ) {

		$settings = get_option( self::MAIN_OPTIONS );
		if ( isset( $settings[ $key ] ) && ! empty( $settings[ $key ] ) ) {
			return $settings[ $key ];
		}

		return false;
	}


	/**
	 * Update plugin option
	 *
	 * @param string $key name of option.
	 * @param mixed  $value value to save.
	 *
	 * @return bool
	 */
	private function update_plugin_option( $key, $value ) {

		$settings = get_option( self::MAIN_OPTIONS, null );

		if ( $settings ) {
			$settings[ $key ] = $value;
		} else {
			$settings         = array();
			$settings[ $key ] = $value;
		}

		return update_option( self::MAIN_OPTIONS, $settings, true );
	}


	/**
	 * Delete plugin option
	 *
	 * @param string $key name of option.
	 *
	 * @return bool
	 */
	private function delete_plugin_option( $key ) {

		$settings = get_option( self::MAIN_OPTIONS );

		if ( isset( $settings[ $key ] ) ) {
			unset( $settings[ $key ] );
			return update_option( self::MAIN_OPTIONS, $settings, true );
		}

		return false;
	}


	/**
	 * Create shipment in API
	 *
	 * @param array $request $request.
	 * @param mixed $order_id $order_id.
	 *
	 * @return array
	 */
	public function create_package( $request, $order_id ) {

		if ( empty( $this->api_token ) ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
			/* translators: %1$s is the error message, %2$s is the settings page URL, %3$s is the link text */
				'%1$s <a href="%2$s" target="_blank">%3$s</a>',
				esc_html__( 'Check you API connection', 'inpost-international' ),
				esc_url( $settings_page ),
				esc_html__( 'here', 'inpost-international' )
			);
			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

        /*if ( $this->is_api_token_expired() ) {
            $this->refresh_api_token();
        }*/

		$environment = $this->get_plugin_option( self::API_TYPE_FIELD );

		if ( 'production' === $environment ) {
			$api_url = self::PRODUCTION_ENVIRONMENT;
		} else {
			$api_url = self::SANDBOX_ENVIRONMENT;
		}

		$endpoint = $this->get_endpoint( $request, $order_id );

		$url = $api_url . '/shipments/' . $endpoint;

		$request_args = array(
			'timeout' => 30,
			'method'  => 'POST',
		);

		$request_args['headers'] = array(
			'Authorization'     => 'Bearer ' . $this->api_token,
			'Content-Type'      => 'application/json',
			'X-Origin-Metadata' => 'WooCommerce_INT',
		);

		$request_args['body'] = wp_json_encode( $request );

        inpost_intl_core()->inpost_logger(
            'Inpost International: order-' . $order_id . ' API REQUEST, time: ' . current_time( 'd-F-H:i' ),
            __METHOD__ . ': ' . __LINE__,
            $endpoint,
            'inpost-international-order-' . $order_id . '-request-response',
            'debug'
        );

		$response = wp_remote_post( $url, $request_args );

		if ( ! $response || is_wp_error( $response ) ) {
			return array(
				'error'       => 1,
				'error_title' => 'Something went wrong',
			);
		}
		$body_decoded = array();
		$body         = wp_remote_retrieve_body( $response );

		try {
			$body_decoded = json_decode( $body, true );
		} catch ( Exception $e ) {

			inpost_intl_core()->inpost_logger(
				'Inpost International: order-' . $order_id . ' API RESPONSE DECODE EXCEPTION, time: ' . current_time( 'd-F-H:i' ),
				__METHOD__ . ': ' . __LINE__,
				$e->getMessage(),
				'inpost-international-order-' . $order_id . '-request-response',
				'debug'
			);
		}

		inpost_intl_core()->inpost_logger(
			'Inpost International: order-' . $order_id . ' API REQUEST, time: ' . current_time( 'd-F-H:i' ),
			__METHOD__ . ': ' . __LINE__,
			$request,
			'inpost-international-order-' . $order_id . '-request-response',
			'debug'
		);

		inpost_intl_core()->inpost_logger(
			'Inpost International: order-' . $order_id . ' API RESPONSE, time: ' . current_time( 'd-F-H:i' ),
			__METHOD__ . ': ' . __LINE__,
			! empty( $body_decoded ) ? $body_decoded : $response,
			'inpost-international-order-' . $order_id . '-request-response',
			'debug'
		);

		if ( empty( $body_decoded ) ) {

			inpost_intl_core()->inpost_logger(
				'Inpost International: order-' . $order_id . ' API RESPONSE, time: ' . current_time( 'd-F H:i' ),
				__METHOD__ . ': ' . __LINE__,
				$response,
				'inpost-international-order-' . $order_id . '-request-response',
				'debug'
			);

			return array(
				'error'       => 1,
				'error_title' => 'Something went wrong',
			);
		}

		if ( ! empty( $body_decoded['type'] ) && 'ValidationFailed' === $body_decoded['type'] ) {
			$validation_errors = '';
			if ( ! empty( $body_decoded['errors'] ) ) {
				$helper            = new Helper();
				$validation_errors = $helper->map_validation_errors( $body_decoded['errors'] );
			}

			return array(
				'error'         => 1,
				'error_title'   => esc_html__( 'There are some validation errors: ', 'inpost-international' ),
				'error_details' => $validation_errors,
			);
		}

		if ( ( ! empty( $body_decoded['type'] ) && 'InternalServerError' === $body_decoded['type'] ) || ( ! empty( $body_decoded['status'] ) && 500 === intval( $body_decoded['status'] ) ) ) {

			$maybe_errors = '';
			if ( ! empty( $body_decoded['errors'] ) ) {
				if ( is_array( $body_decoded['errors'] ) ) {
					foreach ( $body_decoded['errors'] as $key => $error ) {
						$maybe_errors = $key . ': ' . $error . ', ';
					}
				} else {
					$maybe_errors = $body_decoded['errors'];
				}
			}

			return array(
				'error'         => 1,
				'error_title'   => ! empty( $body_decoded['title'] ) ? $body_decoded['title'] : esc_html__( 'Unexpected error occurred.', 'inpost-international' ),
				'error_details' => ! empty( $body_decoded['detail'] ) ? $body_decoded['detail'] . '<br>' . $maybe_errors : $maybe_errors,
			);
		}

		if ( ( ! empty( $body_decoded['status'] ) && ! is_numeric( $body_decoded['status'] ) ) || ! empty( $body_decoded['trackingNumber'] ) ) {

			$response = $this->save_package_to_order_meta( $order_id, $request, $body_decoded );

			( new TrackingInfoEmail() )->send_tracking_info_email( $order_id, $body_decoded['trackingNumber'] );

		} else {
			// save to log.
			inpost_intl_core()->inpost_logger(
				'Inpost International: error on package create',
				__METHOD__ . ': ' . __LINE__,
				array(
					'request'  => $request,
					'response' => $response,
				),
				'inpost-international-order-' . $order_id . '-request-response',
				'debug'
			);
		}

		return $response;
	}


	/**
	 * Save request-response to order meta
	 *
	 * @param mixed $order_id $order_id.
	 * @param array $request $request.
	 * @param array $response $response.
	 *
	 * @return array
	 */
	private function save_package_to_order_meta( $order_id, $request, $response ) {

		$order = wc_get_order( $order_id );
		if ( $order && ! is_wp_error( $order ) ) {

			$helper = new Helper();
			if ( $helper->is_creating_parcel_in_single_mode() ) {

				$request['creating']['mode']     = 'single';
				$request['creating']['template'] = ! empty( $_POST['used_template'] ) ? sanitize_text_field( wp_unslash( $_POST['used_template'] ) ) : '';
			} else {
				$shipment_template               = $helper->get_shipment_template();
				$request['creating']['mode']     = 'bulk';
				$request['creating']['template'] = ! empty( $shipment_template['name'] ) ? $shipment_template['name'] : '';
			}

			$order_meta['creating_args']   = $request;
			$order_meta['api_response']    = $response;
			$order_meta['package_created'] = current_time( 'timestamp' );
			$order->update_meta_data( 'inpost_international_package', wp_json_encode( $order_meta ) );
			$order->save();

			$response['creating_args'] = $request;
		}

		return $response;
	}


	/**
	 * Get PDF string (label) by uuid
	 *
	 * @param string $uuid $uuid.
	 *
	 * @return array
	 */
	public function get_label( $uuid ) {

		if ( empty( $this->api_token ) ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
				'%1s <a href="%2s" target="_blank">%2s</a>',
				esc_html__( 'Check you API connection', 'inpost-international' ),
				$settings_page,
				esc_html__( 'here', 'inpost-international' )
			);

			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		$environment = $this->get_plugin_option( self::API_TYPE_FIELD );

		if ( 'production' === $environment ) {
			$api_url = self::PRODUCTION_ENVIRONMENT;
		} else {
			$api_url = self::SANDBOX_ENVIRONMENT;
		}

		$url = $api_url . '/shipments/' . $uuid . '/label';

		$request_args['headers'] = array(
			'Authorization' => 'Bearer ' . $this->api_token,
			'Content-Type'  => 'application/json',
		);

		$response = wp_remote_get( $url, $request_args );

		$http_code = wp_remote_retrieve_response_code( $response );
		if ( 404 === $http_code ) {
			$error_message = sprintf(
				'%1s<br><br> %2s<br><br> %3s',
				esc_html__( 'Label with UUID # ', 'inpost-international' ),
				$uuid,
				esc_html__( 'not found', 'inpost-international' )
			);

			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		$body_decoded = array();
		$body         = wp_remote_retrieve_body( $response );

		try {

			$body_decoded = json_decode( $body, true );

		} catch ( Exception $e ) {

			return array(
				'error'       => 1,
				'error_title' => $e->getMessage(),
			);
		}

		if ( empty( $body_decoded ) ) {

			inpost_intl_core()->inpost_logger(
				'Inpost International: label: ' . $uuid . ' API RESPONSE, time: ' . current_time( 'd-F H:i' ),
				__METHOD__ . ': ' . __LINE__,
				$response,
				'inpost-international-get-label-error',
				'debug'
			);

			return array(
				'error'       => 1,
				'error_title' => 'Something went wrong',
			);

		}

		if ( ! empty( $body_decoded['label']['content'] ) ) {
			$pdf_string = $body_decoded['label']['content'];
			return array( 'pdf' => $pdf_string );
		}
	}



	/**
	 * Get shipment data by id
	 *
	 * @param string $id $id.
	 *
	 * @return array
	 */
	public function get_shipment( $id ) {

		if ( empty( $this->api_token ) ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
				'%1s <a href="%2s" target="_blank">%2s</a>',
				esc_html__( 'Check you API connection', 'inpost-international' ),
				$settings_page,
				esc_html__( 'here', 'inpost-international' )
			);
			header( 'Content-Type: application/json' );
			echo wp_json_encode(
				array(
					'error'            => 1,
					'error_connection' => $error_message,
				)
			);
			wp_die();
		}

		$environment = $this->get_plugin_option( self::API_TYPE_FIELD );

		if ( 'production' === $environment ) {
			$api_url = self::PRODUCTION_ENVIRONMENT;
		} else {
			$api_url = self::SANDBOX_ENVIRONMENT;
		}

		// $id = 'd95e0c65-83be-485d-963e-da1f35044931';

		$url = $api_url . '/shipments/' . $id;

		$request_args['headers'] = array(
			'Authorization' => 'Bearer ' . $this->api_token,
			'Content-Type'  => 'application/json',
		);

		$response = wp_remote_post( $url, $request_args );

		$http_code = wp_remote_retrieve_response_code( $response );
		if ( 404 === $http_code ) {
			$error_message = sprintf(
				'%1s<br><br> %2s<br><br> %3s',
				esc_html__( 'Shipment with ID # ', 'inpost-international' ),
				$id,
				esc_html__( 'not found', 'inpost-international' )
			);
			header( 'Content-Type: application/json' );
			echo wp_json_encode(
				array(
					'error'            => 1,
					'error_connection' => $error_message,
				)
			);
			wp_die();
		}

		$body_decoded = array();
		$body         = wp_remote_retrieve_body( $response );
		try {
			$body_decoded = json_decode( $body, true );
		} catch ( Exception $e ) {

			header( 'Content-Type: application/json' );
			echo wp_json_encode(
				array(
					'error'       => 1,
					'error_title' => $e->getMessage(),
				)
			);
			wp_die();
		}

		if ( empty( $body_decoded ) ) {

			inpost_intl_core()->inpost_logger(
				'Inpost International: label: ' . $id . ' API RESPONSE, time: ' . current_time( 'd-F H:i' ),
				__METHOD__ . ': ' . __LINE__,
				$response,
				'inpost-international-get-label-error',
				'debug'
			);

			header( 'Content-Type: application/json' );
			echo wp_json_encode(
				array(
					'error'       => 1,
					'error_title' => 'Something went wrong',
				)
			);
			wp_die();

		}

		return $body_decoded;
	}


	/**
	 * Get tracking data by tracking_number
	 *
	 * @param string $tracking_number $tracking_number.
	 *
	 * @return array
	 */
	public function get_tracking_data( $tracking_number ) {

		if ( empty( $this->api_token ) ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
				'%1s <a href="%2s" target="_blank">%2s</a>',
				esc_html__( 'Check you API connection', 'inpost-international' ),
				$settings_page,
				esc_html__( 'here', 'inpost-international' )
			);

			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		$environment = $this->get_plugin_option( self::API_TYPE_FIELD );

		if ( 'production' === $environment ) {
			$api_url = self::PRODUCTION_ENVIRONMENT;
		} else {
			$api_url = self::SANDBOX_ENVIRONMENT;
		}

		$url = $api_url . '/tracking/parcels?trackingNumbers=' . $tracking_number . '&includeNonTranslatedEvents=true&returnTranslations=true';

		$helper   = new Helper();
		$language = $helper->get_current_user_language();

		$request_args['headers'] = array(
			'Authorization'   => 'Bearer ' . $this->api_token,
			'Accept-Language' => $language,
		);

		$response = wp_remote_get( $url, $request_args );

		$http_code = wp_remote_retrieve_response_code( $response );
		if ( 404 === $http_code ) {
			$error_message = sprintf(
				'%1s<br><br> %2s<br><br> %3s',
				esc_html__( 'Status for tracking # ', 'inpost-international' ),
				$tracking_number,
				esc_html__( 'not found', 'inpost-international' )
			);

			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		if ( 401 === $http_code ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
				'%1s <a href="%2s" target="_blank">%2s</a>',
				esc_html__( 'Unauthorized: Check you API connection', 'inpost-international' ),
				$settings_page,
				esc_html__( 'here', 'inpost-international' )
			);

			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		$body_decoded = array();
		$body         = wp_remote_retrieve_body( $response );

		try {

			$body_decoded = json_decode( $body, true );

		} catch ( Exception $e ) {
			return array(
				'error'       => 1,
				'error_title' => $e->getMessage(),
			);
		}

		if ( empty( $body_decoded ) ) {

			inpost_intl_core()->inpost_logger(
				'Inpost International: status: ' . $tracking_number . ' API RESPONSE, time: ' . current_time( 'd-F H:i' ),
				__METHOD__ . ': ' . __LINE__,
				$response,
				'inpost-international-get-label-error',
				'debug'
			);

			return array(
				'error'       => 1,
				'error_title' => 'Something went wrong',
			);

		}

		if ( ! empty( $body_decoded[0]['events'] ) ) {
			return $body_decoded;
		}
	}


	/**
	 * Retrieves the cutoff pickup time information for a given postal code.
	 *
	 * This function makes an API request to the InPost service to fetch
	 * the cutoff pickup time based on the provided postal code. It handles
	 * various error conditions including missing API tokens, validation errors,
	 * and authentication issues.
	 *
	 * @since 1.0.0
	 *
	 * @param string $postal_code The postal code for which to retrieve cutoff pickup time.
	 *
	 * @return array|void Returns an array containing error information if the request fails,
	 *                    or sends a JSON response with the cutoff time data if successful.
	 *                    Possible error return values:
	 *                    - array with 'error' => 1 and 'error_connection' => string
	 *                    - array with 'error' => 1, 'error_title' => string, and 'error_details' => string
	 *                    - array with 'error' => 1 and 'error_title' => string
	 *
	 *                    On success, calls wp_send_json_success() with the API response data.
	 */
	public function get_cutoff_pickup_time( $postal_code ) {

		if ( empty( $this->api_token ) ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
				'%1s <a href="%2s" target="_blank">%2s</a>',
				esc_html__( 'Check you API connection', 'inpost-international' ),
				$settings_page,
				esc_html__( 'here', 'inpost-international' )
			);
			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		$environment = $this->get_plugin_option( self::API_TYPE_FIELD );

		if ( 'production' === $environment ) {
			$api_url = self::PRODUCTION_ENVIRONMENT;
		} else {
			$api_url = self::SANDBOX_ENVIRONMENT;
		}

		$postal_code = trim( $postal_code );

		$url = $api_url . '/cutoff-time?postalCode=' . $postal_code . '&countryCode=PL';

		$sender_options = get_option( 'inpost_international_settings_merchant' );
		if ( ! empty( $sender_options['country'] ) ) {
			$country_code = $sender_options['country'];
		}

		if ( 'IT' === $country_code ) {
			$url = $api_url . '/cutoff-time?postalCode=' . $postal_code . '&countryCode=IT';
		}

		$request_args['headers'] = array(
			'Authorization' => 'Bearer ' . $this->api_token,
		);

		$response = wp_remote_get( $url, $request_args );

		$http_code = wp_remote_retrieve_response_code( $response );
		if ( 400 === $http_code ) {
			$body = wp_remote_retrieve_body( $response );
			if ( ! empty( $body ) ) {
				$validation_errors = '';
				$response_body     = json_decode( $body, true );
				if ( ! empty( $response_body['errors'] ) ) {
					$helper            = new Helper();
					$validation_errors = $helper->map_validation_errors( $response_body['errors'] );
				}
			}

			return array(
				'error'         => 1,
				'error_title'   => esc_html__( 'There are some validation errors: ', 'inpost-international' ),
				'error_details' => $validation_errors,
			);
		}

		if ( 401 === $http_code ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
				'%1s <a href="%2s" target="_blank">%2s</a>',
				esc_html__( 'Unauthorized: Check you API connection', 'inpost-international' ),
				$settings_page,
				esc_html__( 'here', 'inpost-international' )
			);

			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		$body_decoded = array();
		$body         = wp_remote_retrieve_body( $response );

		try {

			$body_decoded = json_decode( $body, true );

		} catch ( Exception $e ) {

			return array(
				'error'       => 1,
				'error_title' => $e->getMessage(),
			);
		}

		if ( empty( $body_decoded ) ) {
			return array(
				'error'       => 1,
				'error_title' => 'Something went wrong',
			);
		}

		wp_send_json_success( $body_decoded );
	}

	/**
	 * Create pickup order
	 *
	 * @param array $request $request.
	 *
	 * @return array
	 */
	public function create_pickup_order( $request ) {

		if ( empty( $this->api_token ) ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
				'%1s <a href="%2s" target="_blank">%2s</a>',
				esc_html__( 'Check you API connection', 'inpost-international' ),
				$settings_page,
				esc_html__( 'here', 'inpost-international' )
			);
			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		$environment = $this->get_plugin_option( self::API_TYPE_FIELD );

		if ( 'production' === $environment ) {
			$api_url = self::PRODUCTION_ENVIRONMENT;
		} else {
			$api_url = self::SANDBOX_ENVIRONMENT;
		}

		$url = $api_url . '/one-time-pickups';

		$request_args['headers'] = array(
			'Authorization' => 'Bearer ' . $this->api_token,
			'Content-Type'  => 'application/json',
		);

		$request_args['body'] = wp_json_encode( $request );

		$response = wp_remote_post( $url, $request_args );
		$body     = wp_remote_retrieve_body( $response );

		inpost_intl_core()->inpost_logger(
			'Inpost International: Create Pickup Order, API REQUEST, time: ' . current_time( 'd-F H:i' ),
			__METHOD__ . ': ' . __LINE__,
			$request,
			'inpost-international-create-pickup-order',
			'debug'
		);
		inpost_intl_core()->inpost_logger(
			'Inpost International: Create Pickup Order, API RESPONSE, time: ' . current_time( 'd-F H:i' ),
			__METHOD__ . ': ' . __LINE__,
			$body,
			'inpost-international-create-pickup-order',
			'debug'
		);

		$http_code = wp_remote_retrieve_response_code( $response );
		if ( 400 === $http_code ) {
			$validation_errors = '';
			if ( ! empty( $body ) ) {
				$response_body = json_decode( $body, true );
				if ( ! empty( $response_body['errors'] ) || ! empty( $response_body['messages'] ) ) {
					if ( ! empty( $response_body['errors'] ) ) {
						$helper            = new Helper();
						$validation_errors = $helper->map_validation_errors( $response_body['errors'] );
					}

					if ( ! empty( $response_body['messages'] ) ) {
						foreach ( $response_body['messages'] as $key => $message ) {
							$error_type         = ! empty( $message['type'] ) ? $message['type'] : '';
							$error_details      = ! empty( $message['message'] ) ? $message['message'] : '';
							$validation_errors .= $error_type . ': ' . $error_details;
						}
					}
				}
			}

			return array(
				'error'         => 1,
				'error_title'   => esc_html__( 'There are some validation errors: ', 'inpost-international' ),
				'error_details' => $validation_errors,
			);
		}

		if ( 401 === $http_code ) {
			$settings_page = admin_url( 'admin.php?page=inpost-international-api' );
			$error_message = sprintf(
				'%1s <a href="%2s" target="_blank">%2s</a>',
				esc_html__( 'Unauthorized: Check you API connection', 'inpost-international' ),
				$settings_page,
				esc_html__( 'here', 'inpost-international' )
			);

			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		if ( 403 === $http_code ) {
			$error_message = sprintf(
				'%1s %2s',
				esc_html__( 'Forbidden:', 'inpost-international' ),
				esc_html__( 'User is not allowed to create order.', 'inpost-international' )
			);

			return array(
				'error'            => 1,
				'error_connection' => $error_message,
			);
		}

		try {

			$body_decoded = json_decode( $body, true );

		} catch ( Exception $e ) {

			return array(
				'error'       => 1,
				'error_title' => $e->getMessage(),
			);
		}

		if ( empty( $body_decoded ) ) {
			return array(
				'error'       => 1,
				'error_title' => 'Something went wrong',
			);
		}

		wp_send_json_success( $body_decoded );
	}



	/**
	 * Get correct endpoint.
	 *
	 * @param array $request create shipment request.
	 * @param int   $order_id WC order id.
	 * @return string The correct endpoint.
	 */
	public function get_endpoint( $request, $order_id ) {

		$endpoint = '';

		$order = wc_get_order( $order_id );

		if ( ! $order || is_wp_error( $order ) ) {
			return $endpoint;
		}

		$shipping_method_id = '';
		foreach ( $order->get_items( 'shipping' ) as $item_id => $item ) {
			$shipping_method_id = $item->get_method_id();
		}

		$fs_method_name = $order->get_meta( 'inpost_international_fs_linked' );
		if ( ! empty( $fs_method_name ) ) {
			$shipping_method_id = $fs_method_name;
		}

		if ( 'inpost_international_courier' === $shipping_method_id ) {
			if ( ! empty( $request['shipment']['origin']['address'] ) ) {
				$endpoint = 'address-to-address';
			} else {
				$endpoint = 'point-to-address';
			}
		} elseif ( 'inpost_international_automated_locker_point' === $shipping_method_id ) {
			if ( ! empty( $request['shipment']['origin']['address'] ) ) {
				$endpoint = 'address-to-point';
			} else {
				$endpoint = 'point-to-point';
			}
		}

		return $endpoint;
	}
}
