<?php
/**
 * Class DhlShipment
 *
 * @package DhlPolska\Shipment
 */

namespace DhlPolska\Shipment;

use DhlPolska\API\WebAPI;
use DhlPolska\API\WebApi\Struct\AuthData as WebApiAuthData;
use DhlPolska\WooCommerceSettings\Templates\TemplatesRepository;
use DhlPolska\WooCommerceSettings\WooCommerceDHLPolskaSettings;
use DhlPolskaVendor\Psr\Log\LoggerInterface;
use Exception;
use WC_Order;
use WPDesk_Flexible_Shipping_Shipment;
use WPDesk_Flexible_Shipping_Shipment_Interface;

/**
 * DHL Shipment.
 */
class DhlShipment extends WPDesk_Flexible_Shipping_Shipment implements WPDesk_Flexible_Shipping_Shipment_Interface {

	private const FIELD_ERROR_MESSAGE = 'error_message';

	/**
	 * @var string|null
	 */
	private ?string $runtime_error_message = null;

	/**
	 * @var WooCommerceDHLPolskaSettings
	 */
	private static $shipping_settings;

	/**
	 * @var LoggerInterface|null
	 */
	private static $logger;

	/**
	 * @param WooCommerceDHLPolskaSettings $shipping_settings
	 *
	 * @return void
	 */
	public static function set_shipping_settings( WooCommerceDHLPolskaSettings $shipping_settings ) {
		self::$shipping_settings = $shipping_settings;
	}

	/**
	 * @param LoggerInterface $logger .
	 *
	 * @return void
	 */
	public static function set_logger( LoggerInterface $logger ) {
		self::$logger = $logger;
	}

	/**
	 * @param array $fs_method .
	 * @param array $package   .
	 *
	 * @return void
	 */
	public function checkout( array $fs_method, $package ) {
		$service          = $this->get_selected_service( $fs_method );
		$point_type       = $this->get_point_type_from_method( $fs_method );
		$template_payload = $this->get_template_from_method( $fs_method );
		$template_id      = $template_payload['id'];
		$template         = is_array( $template_payload['data'] ) ? $template_payload['data'] : null;
		$cod              = $this->get_cod_from_method( $fs_method, $template );
		$insurance        = $this->get_insurance_from_method( $fs_method, $template );

		$this->set_meta( ShipmentData::OPTION_SERVICE, $service );
		$this->set_meta( ShipmentData::OPTION_COD, $cod );
		$this->set_meta( ShipmentData::OPTION_INSURANCE, $insurance );
		if ( DhlServices::SERVICE_POINT === $service ) {
			$this->set_meta( ShipmentData::OPTION_POINT_TYPE, $point_type );
		}

		if ( '' !== $template_id ) {
			$this->set_meta( ShipmentData::OPTION_TEMPLATE, $template_id );
		}

		$packages = $this->build_packages( $package, $template );

		$this->set_meta( ShipmentData::OPTION_PACKAGES, $packages );
	}

	/**
	 * @return void
	 */
	public function order_metabox() {
		$id                        = $this->get_id();
		$shipment_data             = $this->get_shipment_data();
		$tracking_number           = $this->get_tracking_number();
		$tracking_url              = $this->get_tracking_url();
		$label_url                 = $this->get_label_url();
		$return_label_url          = $this->get_return_label_url();
		$has_return_label          = null !== $return_label_url;
		$return_label_number       = $this->get_return_label_number();
		$return_label_tracking_url = $this->get_return_label_tracking_url();
		$error_message             = $this->get_runtime_error_message();
		if ( '' === $error_message ) {
			$error_message = $this->get_error_message();
		}

		include __DIR__ . '/views/html-metabox-integration.php';
	}

	/**
	 * @return string
	 */
	public function get_order_metabox_title() {
		return __( 'DHL Polska', 'dhl-polska' );
	}

	/**
	 * @param string $action .
	 * @param array  $data   .
	 *
	 * @return array
	 */
	public function ajax_request( $action, $data ) {
		$ret = [];

		$data = wp_unslash( $data );

		switch ( $action ) {
			case 'save':
				$this->action_save( $data );
				$ret['message'] = __( 'Przesyłka DHL została zapisana.', 'dhl-polska' );
				break;
			case 'delete':
				try {
					$this->action_delete();
					$ret['message'] = __( 'Przesyłka DHL została usunięta.', 'dhl-polska' );
				} catch ( Exception $exception ) {
					$this->runtime_error_message = $exception->getMessage();
					$ret['status']               = 'fail';
					$ret['message']              = sprintf(
						// Translators: error message.
						__( 'Nie udało się usunąć przesyłki DHL: %s', 'dhl-polska' ),
						$exception->getMessage()
					);
				}
				break;
			case 'create':
				$this->action_save( $data );
				$this->api_create();
				$ret['message'] = __( 'Przesyłka DHL została utworzona.', 'dhl-polska' );
				break;
			case 'create_return_label':
				$this->action_create_return_label();
				$ret['message'] = __( 'Etykieta zwrotna została utworzona.', 'dhl-polska' );
				break;
			case 'delete_return_label':
				try {
					$this->action_delete_return_label();
					$ret['message'] = __( 'Etykieta zwrotna została usunięta.', 'dhl-polska' );
				} catch ( Exception $exception ) {
					$this->runtime_error_message = $exception->getMessage();
					$ret['status']               = 'fail';
					$ret['message']              = sprintf(
						// Translators: error message.
						__( 'Nie udało się usunąć etykiety zwrotnej: %s', 'dhl-polska' ),
						$exception->getMessage()
					);
				}
				break;
		}

		$ret['content'] = $this->get_metabox_content();

		return $ret;
	}

	/**
	 * @return string
	 */
	public function get_error_message() {
		return (string) $this->get_meta( self::FIELD_ERROR_MESSAGE );
	}

	/**
	 * @return string
	 */
	private function get_runtime_error_message(): string {
		return (string) $this->runtime_error_message;
	}

	/**
	 * @return string
	 */
	public function get_tracking_number() {
		return $this->get_shipment_data()->get_tracking_number();
	}

	/**
	 * @param string $booking_id .
	 *
	 * @return void
	 */
	public function set_courier_booking_id( string $booking_id ): void {
		$booking_id = trim( $booking_id );
		if ( '' === $booking_id ) {
			return;
		}

		$this->set_meta( ShipmentData::OPTION_COURIER_BOOKING_ID, $booking_id );
		$this->save();
	}

	/**
	 * @return void
	 */
	public function clear_courier_booking_id(): void {
		$this->delete_meta( ShipmentData::OPTION_COURIER_BOOKING_ID );
		$this->save();
	}

	/**
	 * @return string
	 */
	public function get_tracking_url() {
		$tracking_number = $this->get_tracking_number();
		if ( '' === $tracking_number ) {
			return '';
		}

		return $this->get_tracking_url_for_number( $tracking_number );
	}

	/**
	 * @return string
	 */
	public function get_return_label_number(): string {
		return (string) $this->get_meta( ShipmentData::OPTION_RETURN_LABEL_NUMBER, '' );
	}

	/**
	 * @return string
	 */
	public function get_return_label_tracking_url(): string {
		$tracking_number = $this->get_return_label_number();
		if ( '' === $tracking_number ) {
			return '';
		}

		return $this->get_tracking_url_for_number( $tracking_number );
	}

	/**
	 * @param string $tracking_number .
	 *
	 * @return string
	 */
	private function get_tracking_url_for_number( string $tracking_number ): string {
		if ( '' === $tracking_number ) {
			return '';
		}

		return sprintf( 'https://mojdhl.pl/sprawdz?paczki=%s', $tracking_number );
	}

	/**
	 * @return array
	 * @throws Exception .
	 */
	public function get_label() {
		$shipment_id = $this->get_shipment_data()->get_shipment_id();
		if ( ! $shipment_id ) {
			throw new Exception( __( 'Przesyłka nie została jeszcze utworzona.', 'dhl-polska' ) );
		}

		$content = $this->get_api()->get_label( $shipment_id, self::$shipping_settings->get_label_mode() );

		return [
			'label_format' => 'pdf',
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
			'content'      => base64_decode( $content, true ),
			'file_name'    => 'dhl-polska-' . $shipment_id . '.pdf',
		];
	}

	/**
	 * @return array{label_format: string, content: string, file_name: string}
	 * @throws Exception .
	 */
	public function get_return_label(): array {
		$return_label_number = (string) $this->get_meta( ShipmentData::OPTION_RETURN_LABEL_NUMBER, '' );
		if ( '' === $return_label_number ) {
			throw new Exception( __( 'Etykieta zwrotna nie została jeszcze utworzona.', 'dhl-polska' ) );
		}

		$label_type = self::$shipping_settings->get_label_mode();
		if ( '' === $label_type ) {
			throw new Exception( __( 'Nie udało się odczytać etykiety zwrotnej.', 'dhl-polska' ) );
		}

		$label_content = (string) $this->get_webapi()->get_label( $return_label_number, $label_type );
		$label_format  = 'pdf';
		if ( '' === $label_content ) {
			throw new Exception( __( 'Nie udało się odczytać etykiety zwrotnej.', 'dhl-polska' ) );
		}

		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		$content = base64_decode( $label_content, true );
		if ( false === $content ) {
			throw new Exception( __( 'Nie udało się odczytać etykiety zwrotnej.', 'dhl-polska' ) );
		}

		$shipment_id = $this->get_shipment_data()->get_shipment_id();
		if ( '' === $shipment_id ) {
			$shipment_id = (string) $this->get_id();
		}

		$file_name = sprintf( 'dhl-polska-return-%s.%s', $shipment_id, $label_format );

		return [
			'label_format' => $label_format,
			'content'      => $content,
			'file_name'    => $file_name,
		];
	}

	/**
	 * @return string|null
	 */
	public function get_return_label_url(): ?string {
		if ( '' === (string) $this->get_meta( ShipmentData::OPTION_RETURN_LABEL_NUMBER, '' ) ) {
			return null;
		}

		$label_url_args = [
			'action'      => ReturnLabelDispatcher::ACTION,
			'shipment_id' => $this->get_id(),
		];

		$label_url = add_query_arg( $label_url_args, admin_url( 'admin-post.php' ) );

		return wp_nonce_url( $label_url, ReturnLabelDispatcher::ACTION );
	}

	/**
	 * @return string
	 */
	private function get_metabox_content() {
		ob_start();
		$this->order_metabox();

		return (string) ob_get_clean();
	}

	/**
	 * @param array $data .
	 *
	 * @return void
	 */
	private function action_save( array $data ) {
		if ( isset( $data[ ShipmentData::OPTION_SERVICE ] ) ) {
			$this->set_meta( ShipmentData::OPTION_SERVICE, $data[ ShipmentData::OPTION_SERVICE ] );
		}

		if ( isset( $data[ ShipmentData::OPTION_POINT_TYPE ] ) ) {
			$this->set_meta( ShipmentData::OPTION_POINT_TYPE, $data[ ShipmentData::OPTION_POINT_TYPE ] );
		}

		foreach ( [
			ShipmentData::OPTION_PARCELSHOP_ID,
			ShipmentData::OPTION_PARCELSHOP_SAP,
			ShipmentData::OPTION_PARCELSHOP_NAME,
			ShipmentData::OPTION_PARCELSHOP_ZIP,
			ShipmentData::OPTION_PARCELSHOP_STREET,
			ShipmentData::OPTION_PARCELSHOP_CITY,
			ShipmentData::OPTION_PARCELSHOP_STREET_NO,
			ShipmentData::OPTION_PARCELSHOP_HOUSE_NO,
			ShipmentData::OPTION_PARCELSHOP_POSTNUMMER,
		] as $option_parcelshop ) {
			if ( isset( $data[ $option_parcelshop ] ) ) {
				$this->set_meta( $option_parcelshop, $data[ $option_parcelshop ] );
			}
		}

		if ( isset( $data[ ShipmentData::OPTION_COD ] ) ) {
			$this->set_meta( ShipmentData::OPTION_COD, $data[ ShipmentData::OPTION_COD ] );
		} else {
			$this->set_meta( ShipmentData::OPTION_COD, 'no' );
		}

		if ( isset( $data[ ShipmentData::OPTION_COD_AMOUNT ] ) ) {
			$this->set_meta( ShipmentData::OPTION_COD_AMOUNT, $data[ ShipmentData::OPTION_COD_AMOUNT ] );
		}

		if ( isset( $data[ ShipmentData::OPTION_INSURANCE ] ) ) {
			$this->set_meta( ShipmentData::OPTION_INSURANCE, $data[ ShipmentData::OPTION_INSURANCE ] );
		} else {
			$this->set_meta( ShipmentData::OPTION_INSURANCE, 'no' );
		}

		if ( isset( $data[ ShipmentData::OPTION_INSURANCE_AMOUNT ] ) ) {
			$this->set_meta( ShipmentData::OPTION_INSURANCE_AMOUNT, $data[ ShipmentData::OPTION_INSURANCE_AMOUNT ] );
		}

		if ( isset( $data[ ShipmentData::OPTION_REFERENCES ] ) ) {
			$this->set_meta( ShipmentData::OPTION_REFERENCES, $data[ ShipmentData::OPTION_REFERENCES ] );
		}

		if ( isset( $data[ ShipmentData::OPTION_NOTES ] ) ) {
			$this->set_meta( ShipmentData::OPTION_NOTES, $data[ ShipmentData::OPTION_NOTES ] );
		}

		if ( isset( $data[ ShipmentData::OPTION_SHIPMENT_DATE ] ) ) {
			$shipment_date = trim( (string) $data[ ShipmentData::OPTION_SHIPMENT_DATE ] );
			if ( '' === $shipment_date ) {
				$this->delete_meta( ShipmentData::OPTION_SHIPMENT_DATE );
			} else {
				$this->set_meta( ShipmentData::OPTION_SHIPMENT_DATE, $shipment_date );
			}
		}

		if ( isset( $data[ ShipmentData::OPTION_PACKAGES ] ) ) {
			$packages = array_values( $data[ ShipmentData::OPTION_PACKAGES ] );
			if ( empty( $packages ) ) {
				$packages = [
					[
						'weight' => self::$shipping_settings->get_default_weight(),
						'type'   => 'PACKAGE',
					],
				];
			}
			$this->set_meta( ShipmentData::OPTION_PACKAGES, $packages );
		}

		$this->save();
	}

	/**
	 * @return void
	 * @throws Exception .
	 */
	private function action_delete() {
		try {
			$shipment_id = $this->get_shipment_data()->get_shipment_id();
			if ( $shipment_id ) {
				$this->get_api()->delete_shipment( $shipment_id );
			}
		} catch ( Exception $e ) {
			if ( self::$logger ) {
				self::$logger->error( 'DHL API delete shipment error: ' . $e->getMessage() );
			}
			throw $e;
		}

		$this->delete_meta( ShipmentData::OPTION_SHIPMENT_ID );
		$this->delete_meta( ShipmentData::OPTION_PARCEL_NUMBER );
		$this->delete_meta( ShipmentData::OPTION_RETURN_LABEL_CONTENT );
		$this->delete_meta( ShipmentData::OPTION_RETURN_LABEL_FORMAT );
		$this->delete_meta( ShipmentData::OPTION_RETURN_LABEL_TYPE );
		$this->delete_meta( ShipmentData::OPTION_RETURN_LABEL_NUMBER );
		$this->delete_meta( self::FIELD_ERROR_MESSAGE );

		$this->update_status( 'fs-new' );
		$this->save();
	}

	/**
	 * @return void
	 * @throws Exception .
	 */
	private function action_create_return_label(): void {
		$shipment_id = $this->get_shipment_data()->get_shipment_id();
		$waybill     = $this->get_shipment_data()->get_tracking_number();
		if ( '' === $shipment_id || '' === $waybill ) {
			throw new Exception( __( 'Przesyłka nie została jeszcze utworzona.', 'dhl-polska' ) );
		}

		$return_shipment = ( new PrepareReturnShipmentData( $this->get_shipment_data(), $this->get_order(), self::$shipping_settings ) )->get_prepared_data();
		$label_data      = $this->get_webapi()->create_return_shipment( $return_shipment );

		$this->set_meta( ShipmentData::OPTION_RETURN_LABEL_NUMBER, $label_data['tracking_number'] );
		$this->save();
	}

	/**
	 * @return void
	 * @throws Exception .
	 */
	private function action_delete_return_label(): void {
		$return_label_number = (string) $this->get_meta( ShipmentData::OPTION_RETURN_LABEL_NUMBER, '' );
		if ( '' === $return_label_number ) {
			throw new Exception( __( 'Etykieta zwrotna nie została jeszcze utworzona.', 'dhl-polska' ) );
		}

		try {
			$this->get_webapi()->delete_shipment( $return_label_number );
		} catch ( Exception $e ) {
			if ( self::$logger ) {
				self::$logger->error( 'DHL API delete return label error: ' . $e->getMessage() );
			}
			throw $e;
		}

		$this->delete_meta( ShipmentData::OPTION_RETURN_LABEL_CONTENT );
		$this->delete_meta( ShipmentData::OPTION_RETURN_LABEL_FORMAT );
		$this->delete_meta( ShipmentData::OPTION_RETURN_LABEL_TYPE );
		$this->delete_meta( ShipmentData::OPTION_RETURN_LABEL_NUMBER );
		$this->save();
	}

	/**
	 * @return void
	 * @throws Exception .
	 */
	public function api_create() {
		try {
			$api           = $this->get_api();
			$prepared_data = ( new PrepareCreateShipmentRequest( $this->get_shipment_data(), $this->get_order(), self::$shipping_settings ) )->get_prepared_data();

			$shipment_id = $api->create_shipment( $prepared_data );

			$this->set_meta( ShipmentData::OPTION_SHIPMENT_ID, $shipment_id );
			$this->set_meta( ShipmentData::OPTION_PARCEL_NUMBER, $shipment_id ); // Tracking number returned by DHL WebAPI.
			$this->delete_meta( self::FIELD_ERROR_MESSAGE );
			$this->update_status( 'fs-confirmed' );
			$this->save();

			do_action( 'flexible_shipping_shipment_confirmed', $this );

		} catch ( Exception $e ) {
			$this->set_meta( self::FIELD_ERROR_MESSAGE, $e->getMessage() );
			$this->update_status( 'fs-failed' );
			$this->save();

			throw $e;
		}
	}

	/**
	 * @return WebAPI
	 */
	protected function get_api(): WebAPI {
		return $this->get_webapi();
	}

	/**
	 * @return WebAPI
	 */
	protected function get_webapi(): WebAPI {
		return new WebAPI(
			new WebApiAuthData( self::$shipping_settings->get_webapi_login(), self::$shipping_settings->get_webapi_password() ),
			self::$shipping_settings->get_webapi_sap_number(),
			self::$shipping_settings->is_test_mode(),
			self::$logger
		);
	}

	/**
	 * @return ShipmentData
	 */
	protected function get_shipment_data(): ShipmentData {
		return new ShipmentData( $this, $this->get_order(), self::$shipping_settings );
	}

	/**
	 * @param array $fs_method .
	 *
	 * @return string
	 */
	private function get_selected_service( array $fs_method ): string {
		if ( ! empty( $fs_method[ ShipmentData::OPTION_SERVICE ] ) ) {
			return (string) $fs_method[ ShipmentData::OPTION_SERVICE ];
		}

		if ( ! empty( $fs_method['dhl_polska_service'] ) ) {
			return (string) $fs_method['dhl_polska_service'];
		}

		return DhlServices::SERVICE_AH;
	}

	/**
	 * @param array $fs_method .
	 *
	 * @return array{id: string, data: array<string, mixed>|null}
	 */
	private function get_template_from_method( array $fs_method ): array {
		$template_id = (string) ( $fs_method[ ShipmentData::OPTION_TEMPLATE ] ?? ( $fs_method['dhl_polska_template_id'] ?? '' ) );
		if ( '' === $template_id ) {
			return [
				'id'   => '',
				'data' => null,
			];
		}

		$template = ( new TemplatesRepository( self::$shipping_settings, new ShipmentTypes() ) )->get_template( $template_id );

		return [
			'id'   => $template_id,
			'data' => $template,
		];
	}

	/**
	 * @param array                     $fs_method .
	 * @param array<string, mixed>|null $template .
	 *
	 * @return string
	 */
	private function get_cod_from_method( array $fs_method, ?array $template ): string {
		if ( is_array( $template ) ) {
			return (bool) ( $template['services']['cod'] ?? false ) ? 'yes' : 'no';
		}

		return (string) ( $fs_method['cod'] ?? ( $fs_method['dhl_polska_cod'] ?? 'no' ) );
	}

	/**
	 * @param array                     $fs_method .
	 * @param array<string, mixed>|null $template .
	 *
	 * @return string
	 */
	private function get_insurance_from_method( array $fs_method, ?array $template ): string {
		if ( is_array( $template ) ) {
			return (bool) ( $template['services']['insurance'] ?? false ) ? 'yes' : 'no';
		}

		return (string) ( $fs_method['insurance'] ?? ( $fs_method['dhl_polska_insurance'] ?? 'no' ) );
	}

	private function get_point_type_from_method( array $fs_method ): string {
		if ( ! empty( $fs_method[ ShipmentData::OPTION_POINT_TYPE ] ) ) {
			return ShipmentData::normalize_point_type( (string) $fs_method[ ShipmentData::OPTION_POINT_TYPE ] );
		}

		if ( ! empty( $fs_method['dhl_polska_point_type'] ) ) {
			return ShipmentData::normalize_point_type( (string) $fs_method['dhl_polska_point_type'] );
		}

		return ShipmentData::POINT_TYPE_PARCELSHOP;
	}

	/**
	 * @param array                     $package .
	 * @param array<string, mixed>|null $template .
	 *
	 * @return array<int, array<string, mixed>>
	 */
	private function build_packages( array $package, ?array $template ): array {
		$weight = wc_get_weight( fs_calculate_package_weight( $package ), 'kg' );
		if ( ! $weight ) {
			$weight = is_array( $template ) ? (float) ( $template['weight'] ?? 0 ) : 0.0;
		}

		if ( ! $weight ) {
			$weight = self::$shipping_settings->get_default_weight();
		}

		$package_defaults = $this->get_package_defaults_from_template( $template );

		return [
			[
				'weight' => $weight,
				'type'   => $package_defaults['type'],
				'length' => $package_defaults['length'],
				'width'  => $package_defaults['width'],
				'height' => $package_defaults['height'],
			],
		];
	}

	/**
	 * @param array<string, mixed>|null $template .
	 *
	 * @return array{type: string, length: int|null, width: int|null, height: int|null}
	 */
	private function get_package_defaults_from_template( ?array $template ): array {
		$defaults = [
			'type'   => ShipmentTypes::TYPE_PACKAGE,
			'length' => null,
			'width'  => null,
			'height' => null,
		];

		if ( ! is_array( $template ) ) {
			return $defaults;
		}

		$defaults['type']   = (string) ( $template['type'] ?? ShipmentTypes::TYPE_PACKAGE );
		$defaults['length'] = $template['dimensions']['length'] ?? null;
		$defaults['width']  = $template['dimensions']['width'] ?? null;
		$defaults['height'] = $template['dimensions']['height'] ?? null;

		return $defaults;
	}

	/**
	 * @return string
	 */
	public function get_after_order_table() {
		return '';
	}

	/**
	 * @return string
	 */
	public function get_email_after_order_table() {
		return '';
	}
}
