<?php
/**
 * Class BookCourierBulkAction
 *
 * @package DhlPolska\Admin\Shipments\BulkAction
 */

namespace DhlPolska\Admin\Shipments\BulkAction;

use DhlPolska\Exception\CourierBookingFailedException;
use DhlPolska\Exception\ExceptionMessageResolver;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebAPI;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\AddressData;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebApi\Struct\ShipmentOrderInfo;
use DhlPolska\Shipment\DhlShipment;
use DhlPolska\Shipment\PostalCodeNormalizer;
use DhlPolska\Shipment\ShipmentDateFallback;
use DhlPolska\Shipment\ShipmentData;
use DhlPolska\Shipment\ShipmentOrderInfoPiecesCounter;
use DhlPolska\Shipment\UnitsConversionTrait;
use DhlPolska\Shipment\WorkingDayDateCalculator;
use DhlPolska\WooCommerceSettings\WooCommerceDHLPolskaSettings;
use DhlPolskaVendor\Psr\Log\LoggerInterface;

/**
 * Bulk action handler for booking courier.
 */
class BookCourierBulkAction implements BulkActionInterface {
	use UnitsConversionTrait;

	private WooCommerceDHLPolskaSettings $settings;

	private LoggerInterface $logger;

	private WebAPI $webapi;

	public function __construct(
		WooCommerceDHLPolskaSettings $settings,
		LoggerInterface $logger,
		WebAPI $webapi
	) {
		$this->settings = $settings;
		$this->logger   = $logger;
		$this->webapi   = $webapi;
	}

	public function get_action_name(): string {
		return 'book_courier';
	}

	public function get_action_label(): string {
		return __( 'Zamów kuriera', 'dhl-polska' );
	}

	public function handle( array $shipment_ids ): array {
		if ( empty( $shipment_ids ) ) {
			return [
				'type'    => 'error',
				'message' => __( 'Nie wybrano żadnych przesyłek.', 'dhl-polska' ),
			];
		}

		$shipment_ids_list   = [];
		$shipment_data_items = [];
		$shipments_to_update = [];

		foreach ( $shipment_ids as $shipment_post_id ) {
			if ( 'fs-confirmed' !== get_post_status( $shipment_post_id ) ) {
				continue;
			}

			$shipment = fs_get_shipment( $shipment_post_id );
			if ( ! $shipment ) { /** @phpstan-ignore-line */
				continue;
			}

			$order = wc_get_order( (int) get_post_field( 'post_parent', $shipment_post_id ) );
			if ( ! $order ) {
				continue;
			}

			$shipment_data = new ShipmentData( $shipment, $order, $this->settings );
			$shipment_id   = $shipment_data->get_shipment_id();
			if ( '' === $shipment_id ) {
				continue;
			}

			$shipment_ids_list[]   = $shipment_id;
			$shipment_data_items[] = $shipment_data;
			$shipments_to_update[] = $shipment;
		}

		if ( empty( $shipment_ids_list ) ) {
			return [
				'type'    => 'error',
				'message' => __( 'Brak przesyłek spełniających warunki do zamówienia kuriera.', 'dhl-polska' ),
			];
		}

		try {
			$booking_ids = $this->get_webapi()->book_courier(
				$this->get_pickup_date(),
				$this->settings->get_courier_pickup_start(),
				$this->settings->get_courier_pickup_end(),
				$shipment_ids_list,
				$this->build_shipment_order_info( $shipment_data_items )
			);
			$this->update_courier_booking_id( $booking_ids, $shipments_to_update );
		} catch ( \Exception $exception ) {
			$this->logger->error( $exception->getMessage() );
			return [
				'type'    => 'error',
				'message' => ExceptionMessageResolver::resolve_throwable( $exception, CourierBookingFailedException::class ),
			];
		}

		return [
			'type'    => 'success',
			'message' => sprintf(
				// Translators: number of shipments.
				__( 'Zamówienie kuriera dla wskazanych przesyłek zostało wykonane. Liczba przesyłek: %d.', 'dhl-polska' ),
				count( $shipment_ids_list )
			),
		];
	}

	protected function get_pickup_date(): string {
		$calculator             = new WorkingDayDateCalculator();
		$shipment_date_fallback = new ShipmentDateFallback();
		$cutoff                 = $this->settings->get_courier_booking_date_cutoff_time();
		$date                   = $calculator->get_date_after_cutoff( $cutoff );

		if ( null !== $date ) {
			return $date;
		}

		return $shipment_date_fallback->today();
	}

	/**
	 * @param ShipmentData[] $shipment_datas .
	 *
	 * @return ShipmentOrderInfo
	 */
	protected function build_shipment_order_info( array $shipment_datas ): ShipmentOrderInfo {
		$postal_code_normalizer = new PostalCodeNormalizer();
		$pieces_counter         = new ShipmentOrderInfoPiecesCounter();
		$pieces_counts          = $pieces_counter->count( $shipment_datas );

		$shipper = new AddressData(
			(string) $this->settings->get_sender_name(),
			(string) $this->settings->get_sender_street(),
			(string) $this->settings->get_sender_house_number(),
			(string) $this->settings->get_sender_city(),
			$postal_code_normalizer->normalize( (string) $this->settings->get_sender_postcode(), 'PL' ),
			null,
			(string) $this->settings->get_sender_name(),
			(string) $this->settings->get_sender_phone(),
			(string) $this->settings->get_sender_email()
		);

		$total_weight          = 0.0;
		$heaviest_piece_weight = null;

		foreach ( $shipment_datas as $shipment_data ) {
			foreach ( $shipment_data->get_packages() as $package ) {
				$quantity = max( 1, (int) ( $package['quantity'] ?? 1 ) );
				$weight   = $this->get_weight_in_kg( (float) ( $package['weight'] ?? 0 ) );

				if ( null !== $weight ) {
					$total_weight         += ( $weight * $quantity );
					$heaviest_piece_weight = max( $heaviest_piece_weight ?? 0.0, $weight );
				}
			}
		}

		$total_weight_int          = $total_weight > 0 ? (int) round( $total_weight ) : null;
		$heaviest_piece_weight_int = null !== $heaviest_piece_weight ? (int) round( $heaviest_piece_weight ) : null;

		return new ShipmentOrderInfo(
			$shipper,
			$pieces_counts['number_of_ex_pieces'],
			$pieces_counts['number_of_dr_pieces'],
			$total_weight_int,
			$heaviest_piece_weight_int
		);
	}

	protected function get_webapi(): WebAPI {
		return $this->webapi;
	}

	/**
	 * @param string[]      $booking_ids .
	 * @param object[]|DhlShipment[] $shipments .
	 *
	 * @return void
	 */
	private function update_courier_booking_id( array $booking_ids, array $shipments ): void {
		if ( empty( $booking_ids ) ) {
			return;
		}

		$booking_id = (string) reset( $booking_ids );
		$booking_id = trim( $booking_id );

		if ( '' === $booking_id ) {
			return;
		}

		foreach ( $shipments as $shipment ) {
			if ( $shipment instanceof DhlShipment ) {
				$shipment->set_courier_booking_id( $booking_id );
			}
		}
	}
}
