<?php

namespace Inspire_Labs\Alsendo_Connect;

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

use Alsendo\AlsendoWrapper\Api\Ecolet\Model\AdditionalService;
use Alsendo\AlsendoWrapper\Model\Address;
use Alsendo\AlsendoWrapper\Model\Cod;
use Alsendo\AlsendoWrapper\Model\Contact;
use Alsendo\AlsendoWrapper\Model\Location\LocationQueryRequest;
use Alsendo\AlsendoWrapper\Model\Order\OrderRequest;
use Alsendo\AlsendoWrapper\Model\Pickup;
use Alsendo\AlsendoWrapper\Model\Shipment;
use DateTimeImmutable;
use Exception;
use Alsendo\AlsendoWrapper\ApiClientFactory;


// Wrapper autoload.
require_once __DIR__ . '../../vendor/alsendo-wrapper/vendor/autoload.php';

/**
 * Ecolet
 */
class Ecolet {


	/**
	 * ECOLET_PANEL_PRODUCTION
	 */
	const ECOLET_PANEL_PRODUCTION = 'https://panel.ecolet.ro/api/v1/';
	/**
	 * ECOLET_PANEL_SANDBOX
	 */
	const ECOLET_PANEL_SANDBOX = 'https://staging.ecolet.ro/api/v1/';

	/**
	 * ECOLET_PANEL_PRODUCTION V2.
	 */
	const ECOLET_PANEL_PRODUCTION_V2 = 'https://panel.ecolet.ro/api/v2/';
	/**
	 * ECOLET_PANEL_SANDBOX V2.
	 */
	const ECOLET_PANEL_SANDBOX_V2 = 'https://staging.ecolet.ro/api/v2/';

	/**
	 * ECOLET_OAUTH_PRODUCTION
	 */
	const ECOLET_OAUTH_PRODUCTION = 'https://panel.ecolet.ro/api/v1/oauth/token';
	/**
	 * ECOLET_OAUTH_SANDBOX
	 */
	const ECOLET_OAUTH_SANDBOX = 'https://staging.ecolet.ro/api/v1/oauth/token';



	/**
	 * API_TYPE_FIELD
	 */
	const API_TYPE_FIELD = 'alsendo_connect_settings_general_ecolet_api_type';


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

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



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

		$this->api_token     = $this->get_plugin_option( 'alsendo_connect_ecolet_token' );
		$this->refresh_token = $this->get_plugin_option( 'alsendo_connect_ecolet_refresh_token' );

		$this->hooks();
	}

	/**
	 * Hooks.
	 */
	public function hooks() {
		add_action( 'wp_loaded', array( $this, 'get_or_refresh_api_token' ) );
	}


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


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



	/**
	 * Get an order using the API.
	 *
	 * @param int $order_id The ID of the order to get.
	 * //@return array|\WP_Error The API response or WP_Error on failure.
	 * @return mixed The API response.
	 */
	public function get_order( $order_id ) {

		$result = null;

		$api_type = get_option( self::API_TYPE_FIELD );
		if ( 'prod' === $api_type ) {
			$api_type_wrapper = ApiClientFactory::ECOLET;
		} else {
			$api_type_wrapper = ApiClientFactory::ECOLET_TEST;
		}

		// init Wrapper.
		$api_client = ( new ApiClientFactory() )->create(
			$api_type_wrapper,
			array(
				'token' => $this->api_token,
			)
		);

		$failure = null;

		try {

			$result = $api_client->getOrder( $order_id );

		} catch ( Exception $e ) {
			$failure = $e->getMessage();
			if ( function_exists( 'wc_get_logger' ) ) {
				\wc_get_logger()->debug( 'SOME_ERROR_ON_REQUEST: GET PACKAGE DETAILS, package# ' . $order_id, array( 'source' => 'alsendo-connect-get-ecolet-package-' . $order_id ) );
				\wc_get_logger()->debug( print_r( $api_type_wrapper, true ), array( 'source' => 'alsendo-connect-get-ecolet-package-' . $order_id ) );
				\wc_get_logger()->debug( print_r( $e->getMessage(), true ), array( 'source' => 'alsendo-connect-get-ecolet-package-' . $order_id ) );

			}
		}

		if ( $failure ) {

			preg_match( '/^(.*?)(\{.*\})$/s', $failure, $matches );
			if ( ! empty( $matches[1] ) ) {
				$result['data'][] = trim( $matches[1] );
			}
			if ( ! empty( $matches[2] ) ) {
				$result['data'][] = json_decode( $matches[2], true );
			}
		}

		return $result;
	}



	/**
	 * Delete an order using the API.
	 *
	 * @param int|string $order_id The ID of the order to delete.
	 * @return array|\WP_Error The API response or WP_Error on failure.
	 */
	public function cancel_order( $order_id ) {

		$result = array();

		$api_type_wrapper = null;

		$api_type = get_option( self::API_TYPE_FIELD );
		if ( 'prod' === $api_type ) {
			$api_type_wrapper = ApiClientFactory::ECOLET;
		} elseif ( 'stage' === $api_type ) {
			$api_type_wrapper = ApiClientFactory::ECOLET_TEST;
		}

		if ( ! $api_type_wrapper ) {
			return $result;
		}

		// init Wrapper.
		$api_client = ( new ApiClientFactory() )->create(
			$api_type_wrapper,
			array(
				'token' => $this->api_token,
			)
		);

		$failure = null;

		try {

			$result = $api_client->cancelOrder( $order_id );

			if ( is_array( $result ) ) {
				$result['status_code'] = 200;
			}
		} catch ( Exception $e ) {

			$failure     = $e->getMessage();
			$wc_order_id = ! empty( $_POST['order_id'] ) && is_numeric( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : '';

			if ( function_exists( 'wc_get_logger' ) ) {
				\wc_get_logger()->debug( 'SOME_ERROR_ON_REQUEST: CANCEL ORDER, package# ' . $order_id, array( 'source' => 'alsendo-connect-cancel-order-' . $wc_order_id ) );
				\wc_get_logger()->debug( print_r( $api_type_wrapper, true ), array( 'source' => 'alsendo-connect-cancel-order-' . $wc_order_id ) );
				\wc_get_logger()->debug( print_r( $e->getMessage(), true ), array( 'source' => 'alsendo-connect-cancel-order-' . $wc_order_id ) );

			}
		}

		if ( $failure ) {
			$result = alsendo_connect()->parse_api_response( $failure );
			if ( is_array( $result ) ) {
				$result['exception'] = 1;
			} else {
				$result['exception'] = 1;
				$result['error']     = $failure;
			}
		}

		return $result;
	}



	/**
	 * Gets Ecolet service structure with caching mechanism.
	 *
	 * Retrieves available Ecolet services from API with 12-hour cache expiration,
	 * validates API configuration, handles production/staging environments, and
	 * logs errors when service structure requests fail.
	 *
	 * @return array Array of Ecolet services with service IDs as keys and name/slug data.
	 */
	public function get_service_structure_ecolet( $forced = false ) {

		static $is_request_running = false;

		$api_type = alsendo_connect()->get_main_settings_api();
		if ( 'ecolet' !== $api_type ) {
			return array();
		}

		if ( empty( get_option( 'alsendo_connect_settings_general_ecolet_client_id' ) )
			|| empty( get_option( 'alsendo_connect_settings_general_ecolet_app_secret' ) )
		) {
			return array();
		}

		if ( empty( $this->api_token ) ) {
			return array();
		}

		$can_update_services = is_admin() && ( current_user_can( 'manage_woocommerce' ) || current_user_can( 'manage_options' ) );

		if ( ! $can_update_services ) {
			$services = get_option( 'alsendo_connect_ecolet_service_structure' );
			return is_array( $services ) ? $services : array();
		}

		if ( $forced || time() - (int) get_option( 'alsendo_connect_ecolet_service_structure_timestamp' ) > 43200
		) {

			$services = array();

			if ( $forced ) {
				delete_option( 'alsendo_ecolet_api_returned_error' );
			}

			$maybe_timeout = get_option( 'alsendo_ecolet_api_returned_error' );
			$now           = time();
			if ( $maybe_timeout && $maybe_timeout > $now ) {
				return array();
			} else {
				delete_option( 'alsendo_ecolet_api_returned_error' );
			}

			$api_type_wrapper = null;

			$api_type = get_option( self::API_TYPE_FIELD );
			if ( 'prod' === $api_type ) {
				$api_type_wrapper = ApiClientFactory::ECOLET;
			} elseif ( 'stage' === $api_type ) {
				$api_type_wrapper = ApiClientFactory::ECOLET_TEST;
			}

			if ( ! $api_type_wrapper ) {
				return $services;
			}

			// init Wrapper.
			$api_client = ( new ApiClientFactory() )->create(
				$api_type_wrapper,
				array(
					'token' => $this->api_token,
				)
			);

			if ( ! $is_request_running ) {

				$is_request_running = true;

				try {

					$ecolet_services = $api_client->getServiceStructure();

					if ( is_object( $ecolet_services ) && property_exists( $ecolet_services, 'services' ) && is_array( $ecolet_services->services ) ) {
						foreach ( $ecolet_services->services as $service ) {
							if ( property_exists( $service, 'service_id' ) && property_exists( $service, 'name' ) ) {
								$services[ $service->service_id ]['name'] = $service->name;
								if ( property_exists( $service, 'slug' ) ) {
									$services[ $service->service_id ]['slug'] = $service->slug;
								}
							}
						}
					}

					if ( ! empty( $services ) ) {
						update_option( 'alsendo_connect_ecolet_service_structure', $services, false );
						update_option( 'alsendo_connect_ecolet_service_structure_timestamp', time() );
					}

					$is_request_running = false;

				} catch ( Exception $e ) {

					$is_request_running = false;

					$current_timestamp = time();
					$timeout           = $current_timestamp + 300;
					update_option( 'alsendo_ecolet_api_returned_error', $timeout );

					if ( function_exists( 'wc_get_logger' ) ) {
						\wc_get_logger()->debug(
							'SOME_ERROR_ON_REQUEST: ECOLET, GET SERVICE STRUCTURE',
							array( 'source' => 'alsendo-connect-get-ecolet-service-structure' )
						);
						\wc_get_logger()->debug(
							print_r( $api_type_wrapper, true ),
							array( 'source' => 'alsendo-connect-get-ecolet-service-structure' )
						);
						\wc_get_logger()->debug(
							print_r( $e->getMessage(), true ),
							array( 'source' => 'alsendo-connect-get-ecolet-service-structure' )
						);

					}
				}
			}
		} else {
			$services = get_option( 'alsendo_connect_ecolet_service_structure' );
		}

		return is_array( $services ) ? $services : array();
	}


	/**
	 * Performs direct order valuation via Ecolet API.
	 *
	 * Sends order data to Ecolet's add-parcel endpoint for valuation,
	 * handles production/sandbox environment selection, and returns
	 * response with status code and error handling.
	 *
	 * @param array $order The order data for valuation.
	 * @return array API response with status code and valuation data or error.
	 */
	public function order_valuation_direct( $order ) {

		$api_type = get_option( self::API_TYPE_FIELD );
		if ( 'prod' === $api_type ) {
			$api_url = self::ECOLET_PANEL_PRODUCTION_V2;
		} else {
			$api_url = self::ECOLET_PANEL_SANDBOX_V2;
		}

		$url = $api_url . 'add-parcel/reload-form';

		$args = array(
			'method'      => 'POST',
			'timeout'     => 30,
			'redirection' => 5,
			'httpversion' => '1.0',
			'blocking'    => true,
			'headers'     => array(
				'Authorization' => 'Bearer ' . $this->api_token,
				'Accept'        => 'application/json',
				'Content-Type'  => 'application/json',
			),
			'body'        => wp_json_encode( $order ),
		);

		$response      = wp_remote_post( $url, $args );
		$response_code = wp_remote_retrieve_response_code( $response );

		try {
			$response_body         = wp_remote_retrieve_body( $response );
			$result                = json_decode( $response_body, true );
			$result['status_code'] = $response_code;
		} catch ( Exception $e ) {
			$result['status_code'] = $response_code;
			$result['error']       = $e->getMessage();
		}

		return $result;
	}



	/**
	 * Performs order valuation using Ecolet API wrapper.
	 *
	 * Validates locality ID, prepares order request, calls API for valuation,
	 * handles production/staging environments, logs errors with order context,
	 * and parses error messages with JSON content extraction.
	 *
	 * @param array $order The order data for valuation.
	 * @return array Valuation result or error array with detailed error information.
	 */
	public function order_valuation( $order ) {

		$result = array();

		$api_type_wrapper = null;

		$api_type = get_option( self::API_TYPE_FIELD );
		if ( 'prod' === $api_type ) {
			$api_type_wrapper = ApiClientFactory::ECOLET;
		} elseif ( 'stage' === $api_type ) {
			$api_type_wrapper = ApiClientFactory::ECOLET_TEST;
		}

		if ( ! $api_type_wrapper ) {
			return $result;
		}

		// init Wrapper.
		$api_client = ( new ApiClientFactory() )->create(
			$api_type_wrapper,
			array(
				'token' => $this->api_token,
			)
		);

		$failure = null;

		$locality_validation_failed = false;

		$locality_validation_failed = $this->validate_locality_id( $order );

		if ( $locality_validation_failed ) {
			if ( is_array( $locality_validation_failed ) && ! empty( $locality_validation_failed ) ) {
				return array(
					'errors' => array(
						0 => 'Problem with locality_id',
						1 => $locality_validation_failed,
					),
				);
			} else {
				return array( 'errors' => 'Problem with locality_id' );
			}
		}

		try {

			$order_request = $this->prepare_ecolet_order_valuation_request( $order );

			$result = $api_client->getOrderValuation( $order_request );

		} catch ( Exception $e ) {

			$failure = $e->getMessage();

			if ( function_exists( 'wc_get_logger' ) ) {
				$order_id = ! empty( $_POST['order_id'] ) && is_numeric( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : '';
				$logger   = wc_get_logger();
				$logger->log(
					'debug',
					'SOME_ERROR_ON_REQUEST: ECOLET ORDER VALUATION for order #: ' . $order_id,
					array(
						'source'      => 'alsendo-connect-order-' . $order_id,
						'order_id'    => $order_id,
						'debug_place' => __METHOD__ . ': ' . __LINE__,
						'api_type'    => 'API type: ' . $api_type_wrapper,
						'error'       => $e->getMessage(),
					)
				);
			}
		}

		if ( $failure ) {
			preg_match( '/^(.*?)(\{.*\})$/s', $failure, $matches );
			if ( ! empty( $matches[1] ) ) {
				$result['errors'][] = trim( $matches[1] );
				if ( ! empty( $matches[2] ) ) {
					$result['errors'][] = json_decode( $matches[2], true );
				}
			} else {
				$result['errors'][] = $failure;
			}
		}

		return is_array( $result ) ? $result : array();
	}


	/**
	 * Get operator name.
	 *
	 * @param string $service_slug $service_slug.
	 */
	public function get_operator_name( $service_slug ) {

		$operator_name = '';

		$ecolet_services = $this->get_service_structure_ecolet();

		foreach ( $ecolet_services as $key => $service ) {
			if ( $service_slug === $service['slug'] ) {
				$operator_name = $service['name'];
				break;
			}
		}

		return $operator_name;
	}


	/**
	 * Get awb id.
	 *
	 * @param string|int $ecolet_order_id $ecolet_order_id.
	 */
	public function get_awb_id( $ecolet_order_id ) {

		$awb_id            = null;
		$ecolet_order_data = array();

		try {

			$ecolet_order_data = $this->get_order( $ecolet_order_id );

			if ( function_exists( 'wc_get_logger' ) ) {
				$order_id = ! empty( $_POST['order_id'] ) && is_numeric( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : '';
				$logger   = wc_get_logger();
				$logger->log(
					'debug',
					'ECOLET: for get AWB from ecolet shipment data for order #: ' . $order_id,
					array(
						'source'      => 'alsendo-connect-order-' . $order_id,
						'order_id'    => $order_id,
						'debug_place' => __METHOD__ . ': ' . __LINE__,
						'data'        => $ecolet_order_data,
					)
				);
			}
		} catch ( Exception $e ) {
			$ecolet_order_data['error'] = $e->getMessage();

			if ( function_exists( 'wc_get_logger' ) ) {
				$order_id = ! empty( $_POST['order_id'] ) && is_numeric( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : '';
				$logger   = wc_get_logger();
				$logger->log(
					'debug',
					'ECOLET: for get AWB from ecolet shipment data for order #: ' . $order_id,
					array(
						'source'      => 'alsendo-connect-order-' . $order_id,
						'order_id'    => $order_id,
						'debug_place' => __METHOD__ . ': ' . __LINE__,
						'error'       => $e->getMessage(),
					)
				);
			}
		}

		if ( is_object( $ecolet_order_data ) && property_exists( $ecolet_order_data, 'awb' ) ) {
			$awb_id = $ecolet_order_data->awb;
		}

		return $awb_id;
	}


	/**
	 * Updates the AWB (Air Waybill) ID for a specific order.
	 *
	 * Retrieves order metadata, updates the ecolet_awb_id field with the package number,
	 * and saves the updated metadata back to the WooCommerce order.
	 *
	 * @param string $package_number The package/AWB number to store.
	 * @param int    $order_id       The WooCommerce order ID to update.
	 *
	 * @return void
	 */
	public function update_awb_id( $package_number, $order_id ) {

		$meta_data = alsendo_connect()->get_order_meta_data( $order_id );

		if ( ! empty( $meta_data['alsendo_order'] ) ) {
			$meta_data['alsendo_order']['ecolet_awb_id'] = $package_number;
			$order                                       = wc_get_order( $order_id );
			if ( $order && ! is_wp_error( $order ) ) {
				$order->update_meta_data( 'alsendo_connect_wc_order', $meta_data );
				$order->save();
			}
		}
	}


	/**
	 * Generates a tracking URL for a package number.
	 *
	 * Returns the appropriate tracking URL based on the API environment setting.
	 * Uses production URL for 'prod' API type, staging URL otherwise.
	 *
	 * @param string $package_number The package number to track.
	 *
	 * @return string The complete tracking URL for the package.
	 */
	public function get_tracking_url( $package_number = null ) {

		$tracking_url = '';

		$api_type = get_option( self::API_TYPE_FIELD );
		if ( 'prod' === $api_type ) {
			$tracking_url = 'https://panel.ecolet.ro/track/';
		} else {
			$tracking_url = 'https://staging.ecolet.ro/track/';
		}

		if ( ! empty( $package_number ) ) {
			$tracking_url .= $package_number;
		}

		return $tracking_url;
	}


	/**
	 * Retrieves the service slug for a given service ID.
	 *
	 * Searches through the Ecolet service structure to find the corresponding
	 * slug for the provided service ID.
	 *
	 * @param mixed $service_id The service ID to look up.
	 *
	 * @return string The service slug if found, empty string otherwise.
	 */
	public function get_service_slug( $service_id ) {

		$service_slug = '';

		$ecolet_services = $this->get_service_structure_ecolet();
		foreach ( $ecolet_services as $key => $service ) {
			if ( $service_id == $key ) {
				$service_slug = $service['slug'];
				break;
			}
		}

		return $service_slug;
	}




	/**
	 * Sends an order to the Ecolet API using the API client wrapper.
	 *
	 * Creates an appropriate API client based on environment settings, prepares
	 * the order request, and handles the API communication. Logs errors and
	 * exceptions when they occur.
	 *
	 * @param array $order The order data to send to the API.
	 *
	 * @return array API response with order details, or error information with exception flag.
	 */
	public function send_order( $order ) {

		$api_type = get_option( self::API_TYPE_FIELD );
		if ( 'prod' === $api_type ) {
			$api_type_wrapper = ApiClientFactory::ECOLET;
		} else {
			$api_type_wrapper = ApiClientFactory::ECOLET_TEST;
		}

		// init Wrapper.
		$api_client = ( new ApiClientFactory() )->create(
			$api_type_wrapper,
			array(
				'token' => $this->api_token,
			)
		);

		$failure                    = null;
		$locality_validation_failed = false;

		$locality_validation_failed = $this->validate_locality_id( $order );

		if ( $locality_validation_failed ) {
			if ( is_array( $locality_validation_failed ) && ! empty( $locality_validation_failed ) ) {
				return array(
					'errors' => array(
						0 => 'Problem with locality_id',
						1 => $locality_validation_failed,
					),
				);
			} else {
				return array( 'errors' => 'Problem with locality_id' );
			}
		}

		try {

			$order_request = $this->prepare_ecolet_order_valuation_request( $order );
			$result        = $api_client->sendOrder( $order_request );

		} catch ( Exception $e ) {
			$failure = $e->getMessage();
		}

		if ( $failure ) {

			$order_id = ! empty( $_POST['order_id'] ) && is_numeric( $_POST['order_id'] ) ? sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : '';

			if ( function_exists( 'wc_get_logger' ) ) {

				\wc_get_logger()->debug(
					'SOME_ERROR_ON_REQUEST: ECOLET ORDER CREATE',
					array( 'source' => 'alsendo-connect-order-' . $order_id )
				);
				\wc_get_logger()->debug(
					print_r( $api_type_wrapper, true ),
					array( 'source' => 'alsendo-connect-order-' . $order_id )
				);
				\wc_get_logger()->debug(
					print_r( $failure, true ),
					array( 'source' => 'alsendo-connect-order-' . $order_id )
				);
				\wc_get_logger()->debug(
					print_r( 'Place in code: ' . __METHOD__ . ': ' . __LINE__, true ),
					array( 'source' => 'alsendo-connect-order-' . $order_id )
				);
			}

			$result = alsendo_connect()->parse_api_response( $failure );
			if ( is_array( $result ) ) {
				$result['exception'] = 1;
				$result['errors'][]  = $failure;
			} else {
				$result['exception'] = 1;
				$result['details']   = $failure;
				$result['errors'][]  = $failure;
			}
		}

		return $result;
	}



	/**
	 * Retrieves waybill from Ecolet API using the API client wrapper.
	 *
	 * Creates an appropriate API client based on environment settings and fetches
	 * the waybill for the specified order. Logs errors and exceptions when they occur.
	 *
	 * @param string $ecolet_order_id The Ecolet order ID to get waybill for.
	 * @param int    $wc_order_id     The WooCommerce order ID for logging purposes.
	 *
	 * @return mixed Waybill data on success, or error information with exception flag on failure.
	 */
	public function get_waybill( $ecolet_order_id, $wc_order_id ) {

		$api_type = get_option( self::API_TYPE_FIELD );
		if ( 'prod' === $api_type ) {
			$api_type_wrapper = ApiClientFactory::ECOLET;
		} else {
			$api_type_wrapper = ApiClientFactory::ECOLET_TEST;
		}

		// init Wrapper.
		$api_client = ( new ApiClientFactory() )->create(
			$api_type_wrapper,
			array(
				'token' => $this->api_token,
			)
		);

		$failure = null;

		try {
			$result = $api_client->getWayBill( $ecolet_order_id );
		} catch ( Exception $e ) {
			$failure = $e->getMessage();
		}

		if ( $failure ) {
			if ( function_exists( 'wc_get_logger' ) ) {
				\wc_get_logger()->debug(
					'SOME_ERROR_ON_REQUEST: ECOLET GET WAYBILL',
					array( 'source' => 'alsendo-connect-ecolet-get-waybill-order-' . $wc_order_id )
				);
				\wc_get_logger()->debug(
					print_r( $api_type_wrapper, true ),
					array( 'source' => 'alsendo-connect-ecolet-get-waybill-order-' . $wc_order_id )
				);
				\wc_get_logger()->debug(
					print_r( $failure, true ),
					array( 'source' => 'alsendo-connect-ecolet-get-waybill-order-' . $wc_order_id )
				);
				\wc_get_logger()->debug(
					print_r( 'Place in code: ' . __METHOD__ . ': ' . __LINE__, true ),
					array( 'source' => 'alsendo-connect-ecolet-get-waybill-order-' . $wc_order_id )
				);
			}

			$result = alsendo_connect()->parse_api_response( $failure );

			if ( is_array( $result ) ) {
				$result['exception'] = 1;
			} else {
				$result['exception'] = 1;
				$result['details']   = $failure;
			}
		}

		return $result;
	}


	/**
	 * Authenticates with Ecolet OAuth API and stores tokens.
	 *
	 * Handles AJAX authentication request by sending OAuth credentials to Ecolet API.
	 * On success, stores access token, refresh token, and expiration time in WordPress options.
	 * Also saves the authentication credentials for future use.
	 *
	 * @return array Response array with 'ok' => 1 on success, or error information on failure.
	 */
	public function ecolet_auth() {

		check_ajax_referer( 'alsendo_connect_order_metabox', 'security' );

		$response = array();

		$client_id     = '';
		$client_secret = '';
		$username      = '';
		$password      = '';

		if ( ! empty( $_POST['api_type'] ) ) {
			$api_type = sanitize_text_field( wp_unslash( $_POST['api_type'] ) );
			if ( 'prod' === $api_type ) {
				$url = self::ECOLET_OAUTH_PRODUCTION;
			} else {
				$url = self::ECOLET_OAUTH_SANDBOX;
			}
		}

		if ( ! empty( $_POST['client_id'] ) ) {
			$client_id = sanitize_text_field( wp_unslash( $_POST['client_id'] ) );
		}
		if ( ! empty( $_POST['client_secret'] ) ) {
			$client_secret = sanitize_text_field( wp_unslash( $_POST['client_secret'] ) );
		}
		if ( ! empty( $_POST['email'] ) ) {
			$username = sanitize_text_field( wp_unslash( $_POST['email'] ) );
		}
		if ( ! empty( $_POST['password'] ) ) {
			$password = sanitize_text_field( wp_unslash( $_POST['password'] ) );
		}

		$body = array(
			'grant_type'    => 'password',
			'client_id'     => $client_id,
			'client_secret' => $client_secret,
			'username'      => $username,
			'password'      => $password,
			'scope'         => '',
		);

		$args = array(
			'method'      => 'POST',
			'timeout'     => 30,
			'redirection' => 5,
			'httpversion' => '1.0',
			'blocking'    => true,
			'headers'     => array(
				'Accept'       => 'application/json',
				'Content-Type' => 'application/x-www-form-urlencoded',
			),
			'body'        => $body,
		);

		$response      = wp_remote_post( $url, $args );
		$response_code = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );

		/*
		 * Example response
		 *
		{"token_type":"Bearer",
			"expires_in":31536000,
			"access_token":"......",
			"refresh_token":"......"
		}
		*/

		if ( 200 === $response_code ) {
			try {
				$body = json_decode( $response_body, true );
				if ( ! empty( $body['token_type'] ) && 'Bearer' === $body['token_type'] ) {
					if ( ! empty( $body['access_token'] ) && ! empty( $body['refresh_token'] ) ) {
						update_option( 'alsendo_connect_ecolet_token', $body['access_token'] );
						update_option( 'alsendo_connect_ecolet_refresh_token', $body['refresh_token'] );
						if ( ! empty( $body['expires_in'] ) ) {
							$expiration = time() + $body['expires_in'] - 600;
							update_option( 'alsendo_connect_ecolet_token_expiration', $expiration );
						}

						update_option( 'alsendo_connect_settings_general_ecolet_client_id', $client_id );
						update_option( 'alsendo_connect_settings_general_ecolet_app_secret', $client_secret );
						update_option( 'alsendo_connect_settings_general_ecolet_email', $username );
						update_option( 'alsendo_connect_settings_general_ecolet_password', $password );
						update_option( 'alsendo_connect_settings_general_ecolet_api_type', $api_type );

						$response['ok'] = 1;

					} else {
						delete_option( 'alsendo_connect_ecolet_refresh_token' );
					}
				}
			} catch ( Exception $e ) {

				delete_option( 'alsendo_connect_ecolet_refresh_token' );

				if ( function_exists( 'wc_get_logger' ) ) {
					$logger = wc_get_logger();
					$logger->log(
						'debug',
						'SOME_ERROR_ON_ECOLET_AUTH',
						array(
							'source' => 'alsendo-api-oauth',
							'error'  => $e->getMessage(),
						)
					);
				}
				$response['error'] = $e->getMessage();
			}
		} else {

			delete_option( 'alsendo_connect_ecolet_refresh_token' );

			try {
				$body = json_decode( $response_body, true );
				if ( ! empty( $body['error'] ) ) {
					$response['error'] = $body['error'];
					$response['code']  = $response_code;

					if ( ! empty( $body['error_description'] ) ) {
						$response['error_description'] = $body['error_description'];
					}
				}
			} catch ( Exception $e ) {
				if ( function_exists( 'wc_get_logger' ) ) {
					$logger = wc_get_logger();
					$logger->log(
						'debug',
						'SOME_ERROR_ON_ECOLET_AUTH',
						array(
							'source' => 'alsendo-api-oauth',
							'error'  => $e->getMessage(),
						)
					);
				}
				$response['error'] = $e->getMessage();
				$response['code']  = $response_code;

			}
		}

		return $response;
	}


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


	/**
	 * Checks if the refresh token has expired.
	 *
	 * @return bool
	 */
	private function is_refresh_token_expired() {
		if ( $this->get_refresh_token_expiration_time() <= time() ) {
			return true;
		} else {
			return false;
		}
	}


	/**
	 * Refresh API token process.
	 *
	 * @return void
	 */
	public function refresh_api_token_process() {
		if ( $this->is_api_token_expired() ) {
			$this->refresh_api_token();
		}
	}


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


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

		if ( ! empty( $this->refresh_token ) ) {
			$this->refresh_api_token_process();
		}
	}



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

		if ( $this->is_refresh_token_expired() ) {
			delete_option( 'alsendo_connect_ecolet_token' );
			delete_option( 'alsendo_connect_ecolet_token_expiration' );
			delete_option( 'alsendo_connect_ecolet_refresh_token' );
			return;
		}

		$environment   = $this->get_plugin_option( self::API_TYPE_FIELD );
		$client_id     = $this->get_plugin_option( 'alsendo_connect_settings_general_ecolet_client_id' );
		$client_secret = $this->get_plugin_option( 'alsendo_connect_settings_general_ecolet_app_secret' );
		$refresh_token = $this->get_plugin_option( 'alsendo_connect_ecolet_refresh_token' );

		if ( $environment && ! empty( $client_id ) && ! empty( $client_secret ) && ! empty( $refresh_token ) ) {

			if ( 'prod' === $environment ) {
				$api_url = self::ECOLET_OAUTH_PRODUCTION;
			} else {
				$api_url = self::ECOLET_OAUTH_SANDBOX;
			}

			$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'    => 'refresh_token',
					'refresh_token' => $this->refresh_token,
				),
			);

			$response      = wp_remote_post( $api_url, $args );
			$response_body = wp_remote_retrieve_body( $response );
			$response_code = wp_remote_retrieve_response_code( $response );

			if ( 200 === $response_code ) {
				try {
					$body = json_decode( $response_body, true );
					if ( ! empty( $body['token_type'] ) && 'Bearer' === $body['token_type'] ) {
						if ( ! empty( $body['access_token'] ) && ! empty( $body['refresh_token'] ) ) {

							delete_option( 'alsendo_oauth_connection_timeout' );

							update_option( 'alsendo_connect_ecolet_token', $body['access_token'] );
							update_option( 'alsendo_connect_ecolet_refresh_token', $body['refresh_token'] );
							if ( ! empty( $body['expires_in'] ) ) {
								$expiration = time() + $body['expires_in'] - 600;
								update_option( 'alsendo_connect_ecolet_token_expiration', $expiration );

							}
						}
					}
				} catch ( Exception $e ) {
					if ( function_exists( 'wc_get_logger' ) ) {
						$logger = wc_get_logger();
						$logger->log(
							'debug',
							'SOME_ERROR_ON_ECOLET_AUTH',
							array(
								'source' => 'alsendo-api-oauth',
								'error'  => $e->getMessage(),
							)
						);
					}

					$this->setup_connection_timeout();
				}
			} else {
				$this->setup_connection_timeout();
			}
		}
	}



	/**
	 * Get plugin option
	 *
	 * @param string $key name of option.
	 *
	 * @return mixed
	 */
	private function get_plugin_option( $key ) {
		$setting = get_option( $key );
		return ! empty( $setting ) ? $setting : false;
	}



	/**
	 * Prepares an Ecolet order request object from order data array.
	 *
	 * Transforms the order data into an OrderRequest object with all necessary
	 * components including addresses, shipment details, COD information, pickup
	 * settings, and parcel specifications for the Ecolet API.
	 *
	 * @param array $order The order data array containing sender, receiver, parcel, and service information.
	 *
	 * @return OrderRequest The prepared order request object ready for API submission.
	 */
	private function prepare_ecolet_order_valuation_request( $order ) {

		$order_request = new OrderRequest();

		$order_request->serviceId = $order['courier']['service'];

		$sender_phone   = str_replace( ' ', '', $order['sender']['phone'] );
		$sender_phone   = trim( $sender_phone );
		$receiver_phone = str_replace( ' ', '', $order['receiver']['phone'] );
		$receiver_phone = trim( $receiver_phone );

		$order_request->address = new Address(
			new Contact(
				$order['sender']['country'],
				$order['sender']['name'],
				'',
				'',
				$order['sender']['postal_code'],
				$order['sender']['county'],
				$order['sender']['locality'],
				1,
				$order['sender']['contact_person'],
				$order['sender']['email'],
				$sender_phone,
				'',
				$order['sender']['locality_id'],
				$order['sender']['street_name'],
				$order['sender']['street_number'],
				$order['sender']['block'],
				$order['sender']['entrance'],
				$order['sender']['floor'],
				$order['sender']['flat'],
				$order['sender']['has_map_point'],
				! empty( $order['sender']['map_point_id'] ) ? $order['sender']['map_point_id'] : null,
			),
			new Contact(
				$order['receiver']['country'],
				$order['receiver']['name'],
				'',
				'',
				$order['receiver']['postal_code'],
				$order['receiver']['county'],
				$order['receiver']['locality'],
				1,
				$order['receiver']['contact_person'],
				$order['receiver']['email'],
				$receiver_phone,
				'',
				$order['receiver']['locality_id'],
				$order['receiver']['street_name'],
				$order['receiver']['street_number'],
				$order['receiver']['block'],
				$order['receiver']['entrance'],
				$order['receiver']['floor'],
				$order['receiver']['flat'],
				$order['receiver']['has_map_point'],
				! empty( $order['receiver']['map_point_id'] ) ? $order['receiver']['map_point_id'] : null,
			),
		);

		if ( ! empty( $order['additional_services']['cod']['status'] ) ) {
			$order_request->cod              = new Cod();
			$order_request->cod->amount      = ! empty( $order['additional_services']['cod']['amount'] ) ? $order['additional_services']['cod']['amount'] : 0;
			$order_request->cod->bankaccount = '';
		}

		$order_request->pickup = new Pickup();

		$order_request->pickup->type = $order['courier']['pickup']['type'];
		$order_request->pickup->date = ( new DateTimeImmutable( $order['courier']['pickup']['date'] ) )->format( 'Y-m-d' );

		$order_request->pickup->hoursFrom = $order['courier']['pickup']['time'];

		foreach ( $order['parcels'] as $parcel ) {

			$length = '';
			$width  = '';
			$height = '';
			$weight = '';

			if ( ! empty( $parcel['dimensions']['length'] ) ) {
				$length = $parcel['dimensions']['length'];
			} elseif ( 'envelope' === $order['parcel']['type'] ) {
				$length = 15;
			}

			if ( ! empty( $parcel['dimensions']['width'] ) ) {
				$width = $parcel['dimensions']['width'];
			} elseif ( 'envelope' === $order['parcel']['type'] ) {
				$width = 10;
			}

			if ( ! empty( $parcel['dimensions']['height'] ) ) {
				$height = $parcel['dimensions']['height'];
			} elseif ( 'envelope' === $order['parcel']['type'] ) {
				$height = 10;
			}

			if ( ! empty( $parcel['weight'] ) ) {
				$weight = $parcel['weight'];
			} elseif ( 'envelope' === $order['parcel']['type'] ) {
				$weight = 1;
			}

			$shipment                   = new Shipment();
			$shipment->dimension1       = (int) $length;
			$shipment->dimension2       = (int) $width;
			$shipment->dimension3       = (int) $height;
			$shipment->weight           = (int) $weight;
			$shipment->isNstd           = 0;
			$shipment->shipmentTypeCode = $order['parcel']['type'];
			$shipment->shape            = $order['parcel']['shape'];
			$shipment->declaredValue    = ! empty( $parcel['declared_value'] ) ? $parcel['declared_value'] : null;
			$shipment->content          = ! empty( $parcel['content'] ) ? $parcel['content'] : '';

			$order_request->shipment[] = $shipment;
		}

		$order_request->content = $order['parcel']['observations'];

		// $order_request->additionalServices['open_package'] = new AdditionalService(true, 0);
		// $order_request->additionalServices['swap'] = new AdditionalService(true, 0);
		// $order_request->additionalServices['saturday_delivery'] = new AdditionalService(0, 0);
		// $order_request->additionalServices['rod'] = new AdditionalService(true, 0);

		$current_plugin_version = alsendo_connect()->get_plugin_version();
		//$order_request->source = 'WooCommerce Alsendo Connect ver:' . $current_plugin_version;
		$order_request->source = 'WooCommerce Alsendo Connect';

		return $order_request;
	}



	/**
	 * Splits address line into street name and number components.
	 *
	 * Parses address string to separate street name from house number,
	 * handles various formats including alphanumeric combinations,
	 * and returns sanitized components with fallback values.
	 *
	 * @param string $line The address line to split.
	 * @return array Array with 'name' and 'number' keys containing parsed components.
	 */
	public static function split_line_one( $line, $order_id ): array {
		if ( empty( $line ) || 'null' === $line ) {
			return array(
				'name'   => '',
				'number' => '-',
			);
		}

		$line           = trim( $line );
		$separated_line = explode( ' ', $line );

		$street_name   = '';
		$street_number = '';

		// If Blocks additional checkout fields are present on the order, prefer the stored street number meta.
		// This avoids re-parsing a line that may contain the number duplicated in the glued address.
		if ( ! empty( $order_id ) && function_exists( 'wc_get_order' ) ) {
			$order = wc_get_order( $order_id );
			if ( $order && ! is_wp_error( $order ) && method_exists( $order, 'get_meta' ) ) {
				$get_first_non_empty_meta = function ( array $keys ) use ( $order ) {
					foreach ( $keys as $key ) {
						$value = $order->get_meta( $key, true );
						$value = is_string( $value ) ? trim( $value ) : '';
						if ( '' !== $value ) {
							return $value;
						}
					}
					return '';
				};

				// Prefer shipping_* meta first (classic checkout), then Blocks _wc_shipping/<field_id>,
				// then generic keys and billing fallbacks.
				$meta_street_number = $get_first_non_empty_meta(
					array(
						'shipping_alsendo_connect_ecolet_street_number',
						'_wc_shipping/alsendo-connect/ecolet-street-number',
						'_wc_billing/alsendo-connect/ecolet-street-number',
						'alsendo_connect_ecolet_street_number',
						'billing_alsendo_connect_ecolet_street_number',
						'billing_alsendo_connect_ecolet_street_number',
					)
				);

				if ( '' !== $meta_street_number ) {
					$pos = strpos( $line, $meta_street_number );
					if ( false !== $pos ) {
						$street_number = $meta_street_number;
						$street_name   = substr( $line, 0, $pos );
					} else {
						$street_number = $meta_street_number;
						$street_name   = $line;
					}

					$street_name = trim( (string) $street_name );
					$street_name = rtrim( $street_name, " \t\n\r\0\x0B," );

					return array(
						'name'   => trim( sanitize_text_field( $street_name ) ),
						'number' => trim( sanitize_text_field( $street_number ) ),
					);
				}
			}
		}

		if ( count( $separated_line ) > 1 ) {

			$digit_count = count(
				array_filter(
					$separated_line,
					function ( $element ) {
						return preg_match( '/\d/', $element );
					}
				)
			);

			if ( $digit_count > 1 ) {

				foreach ( $separated_line as $index => $element ) {
					if ( preg_match( '/\d/', $element ) ) {
						$street_number = $element;
						$street_name   = implode( ' ', array_slice( $separated_line, 0, $index ) );
						break;
					}
				}
			} else {

				$last_element_of_array = array_pop( $separated_line );

				if ( preg_match( '/\d/', $last_element_of_array ) ) {
					$street_number = $last_element_of_array;
					$street_name   = substr( implode( ' ', $separated_line ), 0, 100 );
				} else {
					array_push( $separated_line, $last_element_of_array );
					$street_name   = implode( ' ', $separated_line );
					$street_number = '-';
				}
			}
		} else {
			// Single word - check if it contains digits.
			if ( preg_match( '/\d/', $line ) ) {
				// Try to split alphanumeric (e.g., "Noua5" -> "Noua" + "5").
				if ( preg_match( '/^(.+?)(\d+[A-Za-z]*)$/', $line, $matches ) ) {
					$street_name   = $matches[1];
					$street_number = $matches[2];
				} else {
					$street_name   = $line;
					$street_number = '-';
				}
			} else {
				$street_name   = $line;
				$street_number = '-';
			}
		}

		if ( empty( $street_name ) ) {
			$street_name   = $line;
			$street_number = '-';
		}

		return array(
			'name'   => trim( sanitize_text_field( $street_name ) ),
			'number' => $street_number ? $street_number : '-',
		);
	}



	/**
	 * Splits address line two into apartment, block, entrance, and floor components.
	 *
	 * Parses Romanian address format with various prefixes to extract apartment number,
	 * block/building identifier, entrance/staircase, and floor information with
	 * fallback handling and special case processing.
	 *
	 * @param string $line The second address line to parse.
	 * @param mixed $first_address_line The first address line to parse.
	 * @return array Array with 'flat', 'block', 'entrance', and 'floor' keys.
	 */
	public static function split_line_two( $line, $first_address_line, $order_id ): array {

		if ( ( empty( $line ) || 'null' === $line ) && ( empty( $first_address_line ) || 'null' === $first_address_line ) ) {
			return array(
				'flat'     => '-',
				'block'    => '-',
				'entrance' => '-',
				'floor'    => '-',
			);
		}

		// If Blocks additional checkout fields are present on the order, prefer stored meta values.
		if ( ! empty( $order_id ) && function_exists( 'wc_get_order' ) ) {
			$order = wc_get_order( $order_id );
			if ( $order && ! is_wp_error( $order ) && method_exists( $order, 'get_meta' ) ) {
				$get_first_non_empty_meta = function ( array $keys ) use ( $order ) {
					foreach ( $keys as $key ) {
						$value = $order->get_meta( $key, true );
						$value = is_string( $value ) ? trim( $value ) : '';
						if ( '' !== $value ) {
							return $value;
						}
					}
					return '';
				};

				$meta_key_map = array(
					'flat'     => array(
						'shipping_alsendo_connect_ecolet_flat',
						'_wc_shipping/alsendo-connect/ecolet-flat',
						'billing_alsendo_connect_ecolet_flat',
						'_wc_billing/alsendo-connect/ecolet-flat',
						'alsendo_connect_ecolet_flat',
					),
					'floor'    => array(
						'shipping_alsendo_connect_ecolet_floor',
						'_wc_shipping/alsendo-connect/ecolet-floor',
						'billing_alsendo_connect_ecolet_floor',
						'_wc_billing/alsendo-connect/ecolet-floor',
						'alsendo_connect_ecolet_floor',
					),
					'block'    => array(
						'shipping_alsendo_connect_ecolet_house_block',
						'_wc_shipping/alsendo-connect/ecolet-house-block',
						'billing_alsendo_connect_ecolet_house_block',
						'_wc_billing/alsendo-connect/ecolet-house-block',
						'alsendo_connect_ecolet_house_block',
					),
					'entrance' => array(
						'shipping_alsendo_connect_ecolet_house_entrance',
						'_wc_shipping/alsendo-connect/ecolet-house-entrance',
						'billing_alsendo_connect_ecolet_house_entrance',
						'_wc_billing/alsendo-connect/ecolet-house-entrance',
						'alsendo_connect_ecolet_house_entrance',
					),
				);

				$meta_values = array();
				$has_any     = false;
				foreach ( $meta_key_map as $result_key => $meta_keys ) {
					$value = $get_first_non_empty_meta( $meta_keys );
					if ( '' !== $value ) {
						$has_any = true;
					}
					$meta_values[ $result_key ] = '' !== $value ? $value : '-';
				}

				if ( $has_any ) {
					return array(
						'flat'     => $meta_values['flat'],
						'block'    => $meta_values['block'],
						'entrance' => $meta_values['entrance'],
						'floor'    => $meta_values['floor'],
					);
				}
			}
		}

		$apartment = '-';
		$block     = '-';
		$entrance  = '-';
		$floor     = '-';

		$line = strtolower( trim( $line ) );

		// Apartment prefixes.
		$apartment_prefixes = array(
			'apartamentul.',
			'apartamentul',
			'apartament.',
			'apartament',
			'apartment',
			'apa.',
			'apa',
			'apt.',
			'apt',
			'ap.',
			'ap ',
			'a.',
		);

		foreach ( $apartment_prefixes as $apartment_prefix ) {
			$result = self::find_matching_prefix( $line, $apartment_prefix, $apartment_prefixes );
			if ( ! empty( $result ) ) {
				$apartment = strtoupper( $result );
				break;
			}
		}

		// Block prefixes.
		$block_prefixes = array(
			'clădirea',
			'cladirea',
			'clădire',
			'cladire',
			'blocul',
			'blocul.',
			'block.',
			'block',
			'bloc',
			'bloc.',
			'bl.',
			'bl ',
			'casa',
			'numarul',
			'numărul',
			'numar',
			'număr',
			'nr.',
			'nr ',
			'n.',
			'vh.',
			'vh',
		);

		foreach ( $block_prefixes as $block_prefix ) {
			$result = self::find_matching_prefix( $line, $block_prefix, $block_prefixes );
			if ( ! empty( $result ) ) {
				$block = strtoupper( $result );
				break;
			}
		}

		// Floor prefixes.
		$floor_prefixes = array(
			'etajul',
			'etaj.',
			'etaj',
			'et.',
			'et ',
			'fl.',
			'fl ',
		);

		if ( strpos( $line, 'parter' ) !== false ) {
			$floor = 'parter';
		} else {
			foreach ( $floor_prefixes as $floor_prefix ) {
				$result = self::find_matching_prefix( $line, $floor_prefix, $floor_prefixes );
				if ( ! empty( $result ) ) {
					$floor = strtolower( $result ); // Keep original case for floors.
					break;
				}
			}
		}

		// Entrance prefixes.
		$entrance_prefixes = array(
			'scară.',
			'scară',
			'scara.',
			'scara',
			'sc.',
			'sc ',
			'intrarea',
			'intrare',
			'intr.',
		);

		foreach ( $entrance_prefixes as $entrance_prefix ) {
			$result = self::find_matching_prefix( $line, $entrance_prefix, $entrance_prefixes );
			if ( ! empty( $result ) ) {
				$entrance = strtoupper( $result );
				break;
			}
		}

		// Handle multiple "et." prefixes - take the last numeric one for floor.
		if ( substr_count( $line, 'et.' ) > 1 || substr_count( $line, 'et ' ) > 1 ) {
			preg_match_all( '/et\.?\s*([a-z0-9]+)/i', $line, $matches );
			if ( ! empty( $matches[1] ) ) {
				// Find the last numeric floor value.
				$floor_values = array_reverse( $matches[1] );
				foreach ( $floor_values as $value ) {
					if ( is_numeric( $value ) ) {
						$floor = $value;
						break;
					}
				}
			}
		}

		if ( empty( $apartment ) && empty( $block ) && empty( $floor ) && empty( $entrance ) ) {
			$excluded_words = array_merge(
				$apartment_prefixes,
				$block_prefixes,
				$entrance_prefixes,
				$floor_prefixes,
			);
			$excluded_words = array_filter(
				$excluded_words,
				function ( $word ) {
					return strlen( $word ) > 2;
				}
			);

			$block = str_replace( array_merge( $excluded_words, array( 'firma', 'magazin' ) ), '', $line );
			$block = substr( trim( $block ), 0, 10 );
		}

		if ( empty( $floor ) && strpos( $line, 'etaj' ) !== false ) {
			$floor = '1';
		}

		if ( '-' === $apartment && '-' === $block && '-' === $floor && '-' === $entrance ) {

			$parts = array_map( 'trim', explode( ',', $line ) );

			if ( count( $parts ) === 4 ) {
				$block     = $parts[0];
				$entrance  = strtoupper( $parts[1] );
				$floor     = $parts[2];
				$apartment = $parts[3];
			} elseif ( ! empty( $first_address_line ) ) {

				$line_to_check = '';

				if ( is_array( $first_address_line ) ) {
					$first_address_line = reset( $first_address_line );
				}

				$separated_line = explode( ' ', $first_address_line );
				foreach ( $separated_line as $index => $element ) {
					if ( preg_match( '/\d/', $element ) ) {
						$street_number = $element;
						$line_to_check = implode( ' ', array_slice( $separated_line, $index + 1 ) );
						break;
					}
				}

				$parts = array_map( 'trim', explode( ',', $line_to_check ) );

				if ( count( $parts ) === 4 ) {
					$block     = $parts[0];
					$entrance  = $parts[1];
					$floor     = $parts[2];
					$apartment = $parts[3];
				} else {
					$apartment = $line_to_check;
				}
			} else {
				$apartment = $line;
			}
		}

		$result = array(
			'flat'     => $apartment,
			'block'    => $block,
			'entrance' => $entrance,
			'floor'    => $floor,
		);

		return $result;
	}


	/**
	 * Finds and extracts value following a specific prefix in address line.
	 *
	 * Locates prefix position, extracts the subsequent value, cleans punctuation,
	 * and validates that the result is not another prefix to avoid false matches.
	 *
	 * @param string $line The address line to search in.
	 * @param string $prefix The prefix to find and extract value from.
	 * @param array  $all_prefixes Array of all possible prefixes for validation.
	 * @return string The extracted value or empty string if not found.
	 */
	private static function find_matching_prefix( string $line, string $prefix, array $all_prefixes ): string {
		// Check if the line contains this prefix.
		if ( strpos( $line, $prefix ) === false ) {
			return '';
		}

		// Find the position where this prefix starts.
		$prefix_pos = strpos( $line, $prefix );

		// Get everything after the prefix.
		$after_prefix = substr( $line, $prefix_pos + strlen( $prefix ) );

		// Clean up the result - remove leading/trailing spaces and punctuation.
		$after_prefix = trim( $after_prefix, " \t\n\r\0\x0B.,;:" );

		// Extract the first word/value after the prefix (stop at comma or other separators).
		$words  = preg_split( '/[,;\s]+/', $after_prefix );
		$result = trim( $words[0] ?? '' );

		// Remove any trailing punctuation from the result.
		$result = rtrim( $result, '.,;:' );

		// Make sure we don't return another prefix as the value.
		foreach ( $all_prefixes as $other_prefix ) {
			if ( strtolower( $result ) === strtolower( trim( $other_prefix, '.' ) ) ) {
				return '';
			}
		}

		return $result;
	}


	/**
	 * Validates locality ID requirements for Romanian and Bulgarian addresses.
	 *
	 * Checks if locality_id is present for both sender and receiver when country
	 * is Romania or Bulgaria, handles AJAX nonce verification, and manages
	 * temporary error storage for order-specific locality validation issues.
	 *
	 * @param array $order The order data containing sender and receiver information.
	 * @return bool|mixed True if locality validation failed, false if valid, or stored error data.
	 */
	private function validate_locality_id( $order ) {
		$is_problem_with_locality = false;

		$receiver_country = strtoupper( $order['receiver']['country'] );
		if ( 'RO' === $receiver_country || 'BG' === $receiver_country ) {
			if ( empty( $order['receiver']['locality_id'] ) ) {
				$is_problem_with_locality = true;
			}
		}

		$sender_country = strtoupper( $order['sender']['country'] );
		if ( 'RO' === $sender_country || 'BG' === $sender_country ) {
			if ( empty( $order['sender']['locality_id'] ) ) {
				$is_problem_with_locality = true;
			}
		}

		check_ajax_referer( 'alsendo_connect_order_metabox', 'security' );

		if ( $is_problem_with_locality ) {
			if ( ! empty( $_POST['action'] ) && 'alsendo_connect' === $_POST['action'] ) {
				if ( ! empty( $_POST['order_id'] ) ) {
					$wc_order_id              = sanitize_text_field( wp_unslash( $_POST['order_id'] ) );
					$locality_id_error        = 'alsendo-connect-locality-error-' . $wc_order_id;
					$is_problem_with_locality = get_option( $locality_id_error );
					delete_option( $locality_id_error );
				}
			}
		}

		return $is_problem_with_locality;
	}


	/**
	 * Gets the carrier name based on the service ID.
	 *
	 * @param int $service_id The service ID to look up.
	 * @return string|null The carrier name or null if not found.
	 *
	 * @since 1.0.1
	 * @access public
	 */
	public function get_carrier_name_by_id( $service_id ) {
		switch ( (int) $service_id ) {
			case 55:
				return 'bookurier_metropolitan';
			case 8:
				return 'sameday';
			case 63:
			case 41:
				return 'sameday';
			case 53:
				return 'bookurier_bucuresti_24';
			case 61:
			case 60:
			case 59:
				return 'fan_courier';
			case 70:
				return 'fan_courier';
			case 73:
			case 72:
			case 71:
			case 69:
			case 57:
			case 56:
			case 48:
			case 47:
			case 46:
			case 45:
			case 44:
			case 43:
			case 42:
			case 19:
			case 17:
			case 16:
			case 14:
			case 13:
			case 12:
				return 'dpd';
			case 49:
			case 81:
			case 82:
			case 83:
			case 84:
				return 'cargus';
			case 4:
			case 85:
				return 'cargus';
			case 1:
				return 'fan_courier';
			case 5:
			case 64:
			case 65:
			case 66:
			case 67:
			case 68:
			case 74:
			case 75:
			case 76:
				return 'gls';
			case 86:
			case 87:
			case 88:
			case 89:
			case 91:
			case 92:
			case 93:
			case 94:
			case 95:
			case 96:
			case 97:
			case 98:
			case 99:
			case 104:
				return 'ups';
			case 78:
			case 79:
			case 80:
			case 111:
				return 'dhl';
			case 90:
				return 'posta-romana';
			default:
				return null;

		}
	}



	/**
	 * Retrieves locality ID from Ecolet API based on search query.
	 *
	 * Makes an API request to find locality information for Romanian locations.
	 * Validates AJAX nonce for security and logs debug information when enabled.
	 * Handles API errors and exceptions with proper logging.
	 *
	 * @param string $search_query The locality search term to query.
	 *
	 * @return int The locality ID if found, mixed otherwise.
	 */
	public function get_locality_id( $country_code, $city, $county, $zip, $type ) {

		$wc_order_id       = '';
		$locality_id       = null;
		$locality_id_error = null;
		$debug_info        = 'Country code: ' . $country_code . ', city: ' . $city . ', postal_code: ' . $zip . ', county: ' . $county;

		if ( ! empty( $_POST['action'] ) && 'alsendo_connect' === $_POST['action'] ) {
			if ( ! empty( $_POST['order_id'] ) ) {
				$wc_order_id       = sanitize_text_field( wp_unslash( $_POST['order_id'] ) );
				$locality_id_error = 'alsendo-connect-locality-error-' . $wc_order_id;
			}
		}

		$locality_id = null;

		$api_type = get_option( self::API_TYPE_FIELD );
		if ( 'prod' === $api_type ) {
			$api_type_wrapper = ApiClientFactory::ECOLET;
		} else {
			$api_type_wrapper = ApiClientFactory::ECOLET_TEST;
		}

		// init Wrapper.
		$api_client = ( new ApiClientFactory() )->create(
			$api_type_wrapper,
			array(
				'token' => $this->api_token,
			)
		);

		$search_query = trim( $city );
		if ( ! empty( $county ) ) {
			$search_query .= '%20' . trim( $county );
		}

		$location_query_request              = new LocationQueryRequest();
		$location_query_request->city        = $search_query;
		$location_query_request->countryCode = $country_code;

		$endpoint = '/api/v1/locations/' . $location_query_request->countryCode . '/localities/' . $location_query_request->city;

		$failure = null;

		try {

			$result = $api_client->queryLocation( $location_query_request );

		} catch ( Exception $e ) {
			$failure = $e->getMessage();
			if ( function_exists( 'wc_get_logger' ) ) {
				\wc_get_logger()->debug( 'SOME_ERROR_ON_REQUEST: GET LOCALITY_ID', array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
				\wc_get_logger()->debug( print_r( $api_type_wrapper, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
				\wc_get_logger()->debug( print_r( $endpoint, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
				\wc_get_logger()->debug( print_r( $e->getMessage(), true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );

			}
		}

		if ( $failure ) {
			if ( $locality_id_error ) {
				$data                      = get_option( $locality_id_error, array() );
				$data[ $type ]['request']  = $debug_info;
				$data[ $type ]['response'] = $failure;
				update_option( $locality_id_error, $data );
			}
		}

		if ( ! is_array( $result ) || empty( $result['localities'] ) || empty( $result['localities'][0] ) ) {
			if ( $locality_id_error ) {
				$data                      = get_option( $locality_id_error, array() );
				$data[ $type ]['request']  = $debug_info;
				$data[ $type ]['response'] = $result;
				update_option( $locality_id_error, $data );

				if ( function_exists( 'wc_get_logger' ) ) {
					\wc_get_logger()->debug( 'SOME_ERROR_ON_REQUEST: GET LOCALITY_ID', array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( print_r( $api_type_wrapper, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( print_r( $debug_info, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( print_r( $endpoint, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( 'LOCALITY_ID API RESPONSE:', array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( print_r( $result, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );

				}
			}
		}

		if ( is_array( $result['localities'] ) ) {
			if ( 1 === count( $result['localities'] ) ) {
				$locality = $result['localities'][0];
				if ( ! empty( $locality['id'] ) ) {
					$locality_id = $locality['id'];
				}
			} else {
				foreach ( $result['localities'] as $locality_data ) {
					if ( $zip === $locality_data['postal_code'] ) {
						$locality_id = $locality_data['id'];
					}
				}

				if ( empty( $locality_id ) && ! empty( $result['localities'][0] ) ) {
					$locality = $result['localities'][0];
					if ( ! empty( $locality['id'] ) ) {
						$locality_id = $locality['id'];
					}
				}
			}
		}

		if ( empty( $locality_id ) ) {
			if ( $locality_id_error ) {
				$data                      = get_option( $locality_id_error, array() );
				$data[ $type ]['request']  = $debug_info;
				$data[ $type ]['response'] = 'Locality_id not found for this request';
				update_option( $locality_id_error, $data );

				if ( function_exists( 'wc_get_logger' ) ) {
					\wc_get_logger()->debug( 'SOME_ERROR_ON_VALIDATE: GET LOCALITY_ID', array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( print_r( $api_type_wrapper, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( print_r( $debug_info, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( print_r( $endpoint, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( 'LOCALITY_ID API RESPONSE:', array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );
					\wc_get_logger()->debug( print_r( $result, true ), array( 'source' => 'alsendo-connect-order-' . $wc_order_id ) );

				}
			}
		}

		return $locality_id;
	}


	/**
	 * Checks Ecolet API connection when settings are saved.
	 *
	 * Validates admin permissions and attempts OAuth authentication with Ecolet API
	 * using stored credentials (client ID, client secret, username, password).
	 * Supports both production and sandbox environments. On successful authentication,
	 * stores access token, refresh token, and expiration time. Cleans up tokens on
	 * failure. Logs errors for debugging purposes.
	 *
	 * @return array Associative array containing 'ok' flag on success, or 'error', 'code', and 'error_description' on failure. Returns empty array if credentials are missing or user lacks permissions.
	 */
	public function check_connection_on_settings_save() {

		$response = array();

		$can_update_services = is_admin() && ( current_user_can( 'manage_woocommerce' ) || current_user_can( 'manage_options' ) );

		if ( ! $can_update_services ) {
			$response['error'] = esc_html__( 'No permissions to check connection', 'alsendo-connect' );
			return $response;
		}

		$client_id     = '';
		$client_secret = '';
		$username      = '';
		$password      = '';

		$client_id     = get_option( 'alsendo_connect_settings_general_ecolet_client_id' );
		$client_secret = get_option( 'alsendo_connect_settings_general_ecolet_app_secret' );
		$username      = get_option( 'alsendo_connect_settings_general_ecolet_email' );
		$password      = get_option( 'alsendo_connect_settings_general_ecolet_password' );

		$api_type = get_option( 'alsendo_connect_settings_general_ecolet_api_type' );

		if ( 'prod' === $api_type ) {
			$url = self::ECOLET_OAUTH_PRODUCTION;
		} else {
			$url = self::ECOLET_OAUTH_SANDBOX;
		}

		if ( empty( $client_id ) || empty( $client_secret ) || empty( $username ) || empty( $password ) ) {
			return;
		}

		$body = array(
			'grant_type'    => 'password',
			'client_id'     => $client_id,
			'client_secret' => $client_secret,
			'username'      => $username,
			'password'      => $password,
			'scope'         => '',
		);

		$args = array(
			'method'      => 'POST',
			'timeout'     => 10,
			'redirection' => 5,
			'httpversion' => '1.0',
			'blocking'    => true,
			'headers'     => array(
				'Accept'       => 'application/json',
				'Content-Type' => 'application/x-www-form-urlencoded',
			),
			'body'        => $body,
		);

		$response      = wp_remote_post( $url, $args );
		$response_code = wp_remote_retrieve_response_code( $response );
		$response_body = wp_remote_retrieve_body( $response );

		/*
		 * Example response
		 *
		{"token_type":"Bearer",
			"expires_in":31536000,
			"access_token":"......",
			"refresh_token":"......"
		}
		*/

		if ( 200 === $response_code ) {
			try {
				$body = json_decode( $response_body, true );
				if ( ! empty( $body['token_type'] ) && 'Bearer' === $body['token_type'] ) {
					if ( ! empty( $body['access_token'] ) && ! empty( $body['refresh_token'] ) ) {
						update_option( 'alsendo_connect_ecolet_token', $body['access_token'] );
						update_option( 'alsendo_connect_ecolet_refresh_token', $body['refresh_token'] );
						if ( ! empty( $body['expires_in'] ) ) {
							$expiration = time() + $body['expires_in'] - 600;
							update_option( 'alsendo_connect_ecolet_token_expiration', $expiration );
						}

						$response['ok'] = 1;

					} else {
						delete_option( 'alsendo_connect_ecolet_refresh_token' );
					}
				}
			} catch ( Exception $e ) {

				delete_option( 'alsendo_connect_ecolet_refresh_token' );

				if ( function_exists( 'wc_get_logger' ) ) {
					$logger = wc_get_logger();
					$logger->log(
						'debug',
						'SOME_ERROR_ON_ECOLET_AUTH',
						array(
							'source' => 'alsendo-api-oauth',
							'error'  => $e->getMessage(),
						)
					);
				}
				$response['error'] = $e->getMessage();
			}
		} else {

			delete_option( 'alsendo_connect_ecolet_refresh_token' );

			try {
				$body = json_decode( $response_body, true );
				if ( ! empty( $body['error'] ) ) {
					$response['error'] = $body['error'];
					$response['code']  = $response_code;

					if ( ! empty( $body['error_description'] ) ) {
						$response['error_description'] = $body['error_description'];
					}
				}
			} catch ( Exception $e ) {
				if ( function_exists( 'wc_get_logger' ) ) {
					$logger = wc_get_logger();
					$logger->log(
						'debug',
						'SOME_ERROR_ON_ECOLET_AUTH',
						array(
							'source' => 'alsendo-api-oauth',
							'error'  => $e->getMessage(),
						)
					);
				}
				$response['error'] = $e->getMessage();
				$response['code']  = $response_code;

			}
		}

		return $response;
	}
}
