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

namespace DhlPolska\Admin\Shipments;

use DhlPolska\Admin\Shipments\BulkAction\BookCourierBulkAction;
use DhlPolska\Admin\Shipments\BulkAction\CancelCourierBookingBulkAction;
use DhlPolska\Admin\Shipments\BulkAction\BulkActionInterface;
use DhlPolska\Admin\Shipments\Filter\CourierBookingFilter;
use DhlPolska\Admin\Shipments\Filter\FilterInterface;
use DhlPolska\Admin\Shipments\Filter\ServiceFilter;
use DhlPolska\Admin\Shipments\Filter\ShipmentDateFilter;
use DhlPolska\Admin\Shipments\Filter\StatusFilter;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebAPI;
use DhlPolska\Integration\FlexibleShipping\FlexibleShippingIntegration;
use DhlPolska\Shipment\ShipmentData;
use DhlPolska\WooCommerceSettings\WooCommerceDHLPolskaSettings;
use DhlPolskaVendor\Psr\Log\LoggerInterface;

if ( ! class_exists( 'WP_List_Table' ) ) {
	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

/**
 * Renders DHL Polska shipments list.
 */
class ShipmentsListTable extends \WP_List_Table {

	public const REQUEST_NONCE_ACTION = 'dhl_polska_shipments_filters';
	public const REQUEST_NONCE_NAME   = 'dhl_polska_shipments_nonce';

	/**
	 * @var int
	 */
	private int $total_items = 0;

	/**
	 * @var WooCommerceDHLPolskaSettings
	 */
	private WooCommerceDHLPolskaSettings $settings;

	/**
	 * @var LoggerInterface
	 */
	private LoggerInterface $logger;

	/**
	 * @var WebAPI
	 */
	private WebAPI $webapi;

	/**
	 * @var array{type: string, message: string}|null
	 */
	private ?array $notice = null;

	/**
	 * @var BulkActionInterface[]
	 */
	private array $bulk_actions;

	/**
	 * @var FilterInterface[]
	 */
	private array $filters;

	/**
	 * @var bool
	 */
	private bool $request_is_trusted;

	public function __construct( WooCommerceDHLPolskaSettings $settings, LoggerInterface $logger, WebAPI $webapi ) {
		$this->settings           = $settings;
		$this->logger             = $logger;
		$this->webapi             = $webapi;
		$this->bulk_actions       = [
			new BookCourierBulkAction( $this->settings, $this->logger, $this->webapi ),
			new CancelCourierBookingBulkAction( $this->logger, $this->webapi ),
		];
		$this->request_is_trusted = $this->is_request_trusted();
		$this->filters            = [
			new StatusFilter( $this->get_verified_request_value( 'status' ) ),
			new ServiceFilter( $this->get_verified_request_value( 'service' ) ),
			new CourierBookingFilter( $this->get_verified_request_value( 'courier_booking' ) ),
			new ShipmentDateFilter( $this->get_verified_request_value( 'shipment_date' ) ),
		];

		parent::__construct(
			[
				'singular' => 'dhl-polska-shipment',
				'plural'   => 'dhl-polska-shipments',
				'ajax'     => false,
			]
		);
	}

	/**
	 * @return void
	 */
	public function render_request_nonce_field(): void {
		wp_nonce_field( self::REQUEST_NONCE_ACTION, self::REQUEST_NONCE_NAME, false );
	}

	/**
	 * @return array<string, string>
	 */
	public function get_columns(): array {
		return [
			'cb'                  => '<input type="checkbox" />',
			'order_id'            => __( 'Zamówienie', 'dhl-polska' ),
			'service'             => __( 'Usługa', 'dhl-polska' ),
			'shipment_date'       => __( 'Data nadania', 'dhl-polska' ),
			'status'              => __( 'Status', 'dhl-polska' ),
			'parcel_number'       => __( 'Numer przesyłki', 'dhl-polska' ),
			'courier_booking_id'  => __( 'Zamówienie kuriera DHL', 'dhl-polska' ),
			'return_label_number' => __( 'Etykieta zwrotna', 'dhl-polska' ),
		];
	}

	/**
	 * @return void
	 */
	public function prepare_items(): void {
		$per_page = (int) $this->get_items_per_page( 'dhl_polska_shipments_per_page', 20 );
		$paged    = $this->get_pagenum();
		$search   = $this->get_search_query();
		$orderby  = $this->get_orderby();
		$order    = $this->get_order();

		$query_args = [
			'post_type'          => 'shipment',
			'post_status'        => 'any',
			'posts_per_page'     => $per_page,
			'paged'              => $paged,
			'orderby'            => 'date',
			'order'              => $order,
			'dhl_polska_orderby' => $orderby,
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Required to filter integration shipments.
			'meta_query'         => [
				[
					'key'   => '_integration',
					'value' => FlexibleShippingIntegration::INTEGRATION_ID,
				],
			],
		];

		foreach ( $this->filters as $filter ) {
			$query_args = $filter->apply( $query_args );
		}

		if ( '' !== $search ) {
			$meta_query = $query_args['meta_query'];

			if ( is_numeric( $search ) ) {
				$query_args['post_parent'] = (int) $search;
			}

			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Search by shipment id/parcel number.
			$meta_query[] = [
				'relation' => 'OR',
				[
					'key'     => ShipmentData::OPTION_SHIPMENT_ID,
					'value'   => $search,
					'compare' => 'LIKE',
				],
				[
					'key'     => ShipmentData::OPTION_PARCEL_NUMBER,
					'value'   => $search,
					'compare' => 'LIKE',
				],
			];

			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Search filters require meta query.
			$query_args['meta_query'] = $meta_query;
		}

		add_filter( 'posts_clauses', [ $this, 'filter_orderby_shipment_date' ], 10, 2 );
		$query = new \WP_Query( $query_args );
		remove_filter( 'posts_clauses', [ $this, 'filter_orderby_shipment_date' ], 10 );

		$this->total_items = (int) $query->found_posts;
		$this->items       = array_map( [ $this, 'map_post_to_item' ], $query->posts );

		$this->set_pagination_args(
			[
				'total_items' => $this->total_items,
				'per_page'    => $per_page,
				'total_pages' => (int) $query->max_num_pages,
			]
		);

		$this->_column_headers = [ $this->get_columns(), [], $this->get_sortable_columns() ];
	}

	/**
	 * @return array<string, array{0: string, 1: bool}>
	 */
	protected function get_sortable_columns(): array {
		return [
			'shipment_date' => [ 'shipment_date', true ],
		];
	}

	/**
	 * @param \WP_Post $post .
	 *
	 * @return array<string, string|int>
	 */
	private function map_post_to_item( \WP_Post $post ): array {
		$parcel_number       = get_post_meta( $post->ID, ShipmentData::OPTION_PARCEL_NUMBER, true );
		$return_label_number = get_post_meta( $post->ID, ShipmentData::OPTION_RETURN_LABEL_NUMBER, true );
		$courier_booking_id  = get_post_meta( $post->ID, ShipmentData::OPTION_COURIER_BOOKING_ID, true );
		$shipment_date       = get_post_meta( $post->ID, ShipmentData::OPTION_SHIPMENT_DATE, true );
		$service             = get_post_meta( $post->ID, ShipmentData::OPTION_SERVICE, true );

		if ( is_array( $parcel_number ) ) {
			$parcel_number = implode( ', ', array_filter( $parcel_number, static fn ( $value ) => $value !== null ) );
		}

		return [
			'shipment_post_id'    => $post->ID,
			'order_id'            => (int) $post->post_parent,
			'parcel_number'       => (string) $parcel_number,
			'return_label_number' => (string) $return_label_number,
			'service'             => (string) $service,
			'courier_booking_id'  => (string) $courier_booking_id,
			'status'              => $post->post_status,
			'shipment_date'       => (string) $shipment_date,
		];
	}

	/**
	 * @param array|object $item .
	 *
	 * @return string
	 */
	protected function column_cb( $item ): string {
		return sprintf(
			'<input type="checkbox" name="shipment_ids[]" value="%d" />',
			isset( $item['shipment_post_id'] ) ? (int) $item['shipment_post_id'] : 0
		);
	}

	/**
	 * @param array|object $item        .
	 * @param string       $column_name .
	 *
	 * @return string
	 */
	protected function column_default( $item, $column_name ): string {
		switch ( $column_name ) {
			case 'order_id':
				return $this->format_order_column( (int) $item['order_id'] );
			case 'parcel_number':
				return $this->format_parcel_number_column(
					(string) $item['parcel_number'],
					(int) $item['order_id'],
					(int) $item['shipment_post_id']
				);
			case 'return_label_number':
				return $this->format_return_label_number_column( (string) $item['return_label_number'] );
			case 'service':
				return $this->format_service_column( (string) $item['service'] );
			case 'courier_booking_id':
				return $this->format_text_column( (string) $item['courier_booking_id'] );
			case 'status':
				return $this->format_status_column( (string) $item['status'] );
			case 'shipment_date':
				return $this->format_shipment_date_column( (string) $item['shipment_date'] );
			default:
				return '';
		}
	}

	/**
	 * @param int $order_id .
	 *
	 * @return string
	 */
	private function format_order_column( int $order_id ): string {
		if ( $order_id <= 0 ) {
			return '-';
		}

		$url = admin_url( 'post.php?post=' . $order_id . '&action=edit' );

		return sprintf(
			'<a href="%s">#%d</a>',
			esc_url( $url ),
			$order_id
		);
	}

	/**
	 * @param string $value .
	 *
	 * @return string
	 */
	private function format_text_column( string $value ): string {
		if ( '' === $value ) {
			return '-';
		}

		return esc_html( $value );
	}

	/**
	 * @param string $parcel_number .
	 * @param int    $order_id .
	 * @param int    $shipment_post_id .
	 *
	 * @return string
	 */
	private function format_parcel_number_column( string $parcel_number, int $order_id, int $shipment_post_id ): string {
		if ( '' === $parcel_number ) {
			$parcel_number = '-';
		}

		$url = admin_url( 'post.php?post=' . $order_id . '&action=edit#shipment_meta_box_' . $shipment_post_id );

		return sprintf(
			'<a href="%s">%s</a>',
			esc_url( $url ),
			esc_html( $parcel_number )
		);
	}

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

		return esc_html( $return_label_number );
	}

	/**
	 * @param string $status .
	 *
	 * @return string
	 */
	private function format_status_column( string $status ): string {
		$status_object = get_post_status_object( $status );
		$label         = $status_object ? $status_object->label : $status;

		return esc_html( $label );
	}

	/**
	 * @param string $service .
	 *
	 * @return string
	 */
	private function format_service_column( string $service ): string {
		if ( '' === $service ) {
			return '-';
		}

		$services = ( new \DhlPolska\Shipment\DhlServices() )->get_all_services();

		return esc_html( $services[ $service ] ?? $service );
	}

	/**
	 * @param string $date .
	 *
	 * @return string
	 */
	private function format_shipment_date_column( string $date ): string {
		if ( '' === $date ) {
			return '-';
		}

		$date_format = get_option( 'date_format' );

		return esc_html( mysql2date( $date_format, $date ) );
	}

	/**
	 * @param array<string, string> $clauses .
	 * @param \WP_Query             $query .
	 *
	 * @return array<string, string>
	 */
	public function filter_orderby_shipment_date( array $clauses, \WP_Query $query ): array {
		if ( 'shipment_date' !== $query->get( 'dhl_polska_orderby' ) ) {
			return $clauses;
		}

		global $wpdb;

		$meta_key         = esc_sql( ShipmentData::OPTION_SHIPMENT_DATE );
		$clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS dhl_shipment_date_meta ON ({$wpdb->posts}.ID = dhl_shipment_date_meta.post_id AND dhl_shipment_date_meta.meta_key = '{$meta_key}')";

		$order = strtoupper( (string) $query->get( 'order' ) );
		$order = in_array( $order, [ 'ASC', 'DESC' ], true ) ? $order : 'DESC';

		$clauses['orderby'] = sprintf(
			'dhl_shipment_date_meta.meta_value IS NULL, dhl_shipment_date_meta.meta_value = \'\', dhl_shipment_date_meta.meta_value %1$s, %2$s.post_date %1$s',
			$order,
			$wpdb->posts
		);

		return $clauses;
	}

	/**
	 * @return array<string, string>
	 */
	protected function get_bulk_actions(): array {
		$actions = [];
		foreach ( $this->bulk_actions as $action ) {
			$actions[ $action->get_action_name() ] = $action->get_action_label();
		}

		return $actions;
	}

	/**
	 * @return void
	 */
	public function no_items(): void {
		esc_html_e( 'Brak przesyłek DHL Polska.', 'dhl-polska' );
	}

	/**
	 * @param string $which .
	 *
	 * @return void
	 */
	protected function extra_tablenav( $which ): void {
		if ( 'top' !== $which ) {
			return;
		}

		$filters = $this->filters;

		include __DIR__ . '/views/html-shipments-filters.php';
	}

	/**
	 * @return string
	 */
	private function get_search_query(): string {
		$search = $this->get_verified_request_value( 's' );

		return (string) $search;
	}

	private function get_orderby(): string {
		$orderby = $this->get_verified_request_value( 'orderby' );

		if ( '' === $orderby ) {
			return 'shipment_date';
		}

		return (string) $orderby;
	}

	private function get_order(): string {
		$order = strtoupper( $this->get_verified_request_value( 'order' ) );

		return in_array( $order, [ 'ASC', 'DESC' ], true ) ? $order : 'DESC';
	}

	private function is_request_trusted(): bool {
		$nonce = $this->get_raw_request_value( self::REQUEST_NONCE_NAME );
		if ( '' === $nonce ) {
			return false;
		}

		return false !== wp_verify_nonce( sanitize_text_field( $nonce ), self::REQUEST_NONCE_ACTION );
	}

	private function get_verified_request_value( string $key ): string {
		if ( ! $this->request_is_trusted ) {
			return '';
		}

		$value = $this->get_raw_request_value( $key );
		if ( '' === $value ) {
			return '';
		}

		if ( in_array( $key, [ 'status', 'service', 'courier_booking', 'orderby', 'order' ], true ) ) {
			return sanitize_key( $value );
		}

		return sanitize_text_field( $value );
	}

	private function get_raw_request_value( string $key ): string {
		$value = filter_input( INPUT_GET, $key, FILTER_UNSAFE_RAW );
		if ( ! is_string( $value ) ) {
			return '';
		}

		return sanitize_text_field( $value );
	}

	/**
	 * Handle bulk actions.
	 *
	 * @return void
	 */
	public function handle_bulk_actions(): void {
		$this->process_bulk_action();
	}

	/**
	 * @return array{type: string, message: string}|null
	 */
	public function get_notice(): ?array {
		return $this->notice;
	}

	/**
	 * @return void
	 */
	protected function process_bulk_action(): void {
		$action_name = $this->current_action();
		if ( ! $action_name ) {
			return;
		}

		$handler = $this->get_bulk_action_handler( $action_name );
		if ( null === $handler ) {
			return;
		}

		check_admin_referer( 'bulk-' . $this->_args['plural'] );

		$shipment_ids = $this->get_bulk_action_shipment_ids();

		$this->notice = $handler->handle( $shipment_ids );
	}

	/**
	 * @return int[]
	 */
	private function get_bulk_action_shipment_ids(): array {
		$shipment_ids = filter_input( INPUT_POST, 'shipment_ids', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
		if ( ! is_array( $shipment_ids ) ) {
			$shipment_ids = filter_input( INPUT_GET, 'shipment_ids', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
		}
		if ( ! is_array( $shipment_ids ) ) {
			return [];
		}

		$shipment_ids = array_map( 'absint', $shipment_ids );

		return array_values( array_filter( $shipment_ids ) ); /** @phpstan-ignore-line */
	}

	private function get_bulk_action_handler( string $action_name ): ?BulkActionInterface {
		foreach ( $this->bulk_actions as $action ) {
			if ( $action->get_action_name() === $action_name ) {
				return $action;
			}
		}

		return null;
	}
}
