<?php

namespace SugarCalendarBookings\Payments\Admin;

use DateTimeImmutable;
use SugarCalendarBookings\Admin\AbstractListTable;
use SugarCalendarBookings\Appointments\Admin\AppointmentDetailsPage;
use SugarCalendarBookings\DeleteManager;
use SugarCalendarBookings\Payments\Gateways\PaymentGatewayRegistry;
use SugarCalendarBookings\Payments\PaymentModel;
use SugarCalendarBookings\Payments\PaymentsHelpers;
use SugarCalendarBookings\Plugin;
use SugarCalendarBookings\Services\ServiceModel;
use SugarCalendarBookings\Support\Admin\Notices;
use SugarCalendarBookings\Support\Database\QueryBuilder;
use SugarCalendarBookings\Support\Helpers\Date;
use SugarCalendarBookings\Support\Helpers\UI;

/**
 * Class PaymentsListTable.
 *
 * @since 1.0.0
 * @since 1.2.1 Switched to extend abstract list table.
 */
class PaymentsListTable extends AbstractListTable {

	/**
	 * Domain-specific default filters.
	 *
	 * @since 1.0.0
	 * @since 1.2.1 Only domain-specific filters (s, status, order, orderby moved to parent).
	 *
	 * @var array
	 */
	protected $active_filters = [
		'month'        => null,
		'year'         => null,
		'service_id'   => null,
		'processor_id' => null,
	];

	/**
	 * Domain-specific filter rules.
	 *
	 * @since 1.2.1
	 *
	 * @var array
	 */
	protected $filter_rules = [
		'month'        => 'numeric',
		'year'         => 'numeric',
		'service_id'   => 'id',
		'processor_id' => 'string|trim',
	];

	/**
	 * Number of payments by different statuses.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $counts = [];

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {

		// Set parent defaults.
		parent::__construct(
			[
				'singular' => 'payment',
				'plural'   => 'payments',
				'screen'   => 'bookings_page_sugar-calendar-bookings-payments',
			]
		);

		// Process filters.
		$this->active_filters = $this->setup_active_filters();
	}

	/**
	 * Define the table columns.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of slug=>Name columns data.
	 */
	public function get_columns() {

		$columns = [];

		$columns['cb']        = '<input type="checkbox" />';
		$columns['payment']   = esc_html__( 'Payment', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['total']     = esc_html__( 'Total', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['paid_on']   = esc_html__( 'Paid On', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['service']   = esc_html__( 'Service', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['customer']  = esc_html__( 'Customer', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['processor'] = esc_html__( 'Processor', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['status']    = esc_html__( 'Status', 'sugar-calendar-bookings-scheduling-appointments-lite' );

		return $columns;
	}

	/**
	 * Define which columns are sortable.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of slug=>array( 'orderby', 'desc_first' ) columns data.
	 */
	public function get_sortable_columns() {

		$sortable_columns = [
			'payment' => [ 'id', false ],
			'paid_on' => [ 'created_at', true ],
		];

		return $sortable_columns;
	}

	/**
	 * Retrieve the view statuses.
	 *
	 * @since 1.0.0
	 */
	public function get_views() {

		$base_url       = $this->get_base_url();
		$current_status = $this->get_selected_status();

		$views = [];

		$views['all'] = sprintf(
			'<a href="%1$s" %2$s>%3$s&nbsp;<span class="count">(%4$d)</span></a>',
			esc_url( remove_query_arg( 'status', $base_url ) ),
			$current_status === null ? 'class="current"' : '',
			esc_html__( 'All', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			intval( $this->counts['total'] )
		);

		foreach ( $this->get_statuses() as $status => $status_label ) {
			$count = intval( $this->counts[ 'status_' . $status ] );

			// Skipping status with no payments.
			if ( $count === 0 && $current_status !== $status ) {
				continue;
			}

			$views[ $status ] = sprintf(
				'<a href="%1$s" %2$s>%3$s&nbsp;<span class="count">(%4$d)</span></a>',
				esc_url( add_query_arg( 'status', $status, $base_url ) ),
				$current_status === $status ? 'class="current"' : '',
				esc_html( $status_label ),
				$count
			);
		}

		return $views;
	}

	/**
	 * Get the base url for the views links.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	private function get_base_url() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$base_url = PaymentsListPage::get_url();
		$filters  = $this->get_applied_filters();

		if ( isset( $filters['status'] ) ) {
			$base_url = add_query_arg( 'status', $filters['status'], $base_url );
		}

		if ( isset( $filters['search'] ) ) {
			$base_url = add_query_arg( 's', $filters['search'], $base_url );
		}

		if ( isset( $filters['month'] ) ) {
			$base_url = add_query_arg( 'month', $filters['month'], $base_url );
		}

		if ( isset( $filters['year'] ) ) {
			$base_url = add_query_arg( 'year', $filters['year'], $base_url );
		}

		if ( isset( $filters['service_id'] ) ) {
			$base_url = add_query_arg( 'service_id', $filters['service_id'], $base_url );
		}

		if ( isset( $filters['processor_id'] ) ) {
			$base_url = add_query_arg( 'processor_id', $filters['processor_id'], $base_url );
		}

		return $base_url;
	}

	/**
	 * Apply filters to the query.
	 *
	 * @since 1.0.0
	 *
	 * @param QueryBuilder $query   Query builder.
	 * @param array        $filters Query parameters.
	 */
	private function build_filters_query( $query, $filters ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		if ( ! empty( $filters['status'] ) ) {

			switch ( $filters['status'] ) {
				case 'trash':
					$query->where( 'payments.trashed_at', 'IS NOT NULL' );
					break;

				default:
					$query->where( 'payments.trashed_at', 'IS NULL' )
						->where( 'payments.status', '=', $filters['status'], '%s' );
					break;
			}
		}

		if ( ! empty( $filters['search'] ) || ! empty( $filters['service_id'] ) ) {
			$query->join( 'payment_items', 'payments.id', '=', 'payment_items.payment_id' )
				->join( 'appointments', 'payment_items.item_id', '=', 'appointments.id' );
		}

		if ( ! empty( $filters['search'] ) ) {
			$search_term = $filters['search'];

			$query->join( 'customers', 'payments.customer_id', '=', 'customers.id' )
				->join( 'services', 'appointments.service_id', '=', 'services.id' )
				->join( 'locations', 'appointments.location_id', '=', 'locations.id' )
				->where(
					function ( $q ) use ( $search_term ) {

						$q->where( 'payments.id', 'like', "%$search_term", '%s' )
							->or_where( 'customers.name', 'like', "%$search_term%", '%s' )
							->or_where( 'customers.email', 'like', "%$search_term%", '%s' )
							->or_where( 'services.name', 'like', "%$search_term%", '%s' )
							->or_where( 'locations.name', 'like', "%$search_term%", '%s' );
					}
				);
		}

		if ( ! empty( $filters['month'] ) && ! empty( $filters['year'] ) && $filters['year'] >= 1 ) {
			$timezone = Date::get_default_timezone();

			$start_local = new DateTimeImmutable(
				sprintf( '%04d-%02d-01 00:00:00', $filters['year'], $filters['month'] ),
				$timezone
			);
			$end_local   = $start_local->modify( '+1 month' );

			$start_utc = Date::to_utc( $start_local )->format( Date::DB_DATETIME_FORMAT );
			$end_utc   = Date::to_utc( $end_local )->format( Date::DB_DATETIME_FORMAT );

			$query->where( 'payments.created_at', '>=', $start_utc, '%s' );
			$query->where( 'payments.created_at', '<', $end_utc, '%s' );
		} elseif ( ! empty( $filters['year'] ) && $filters['year'] >= 1 ) {
			$timezone = Date::get_default_timezone();

			$start_local = new DateTimeImmutable(
				sprintf( '%04d-01-01 00:00:00', $filters['year'] ),
				$timezone
			);
			$end_local   = $start_local->modify( '+1 year' );

			$start_utc = Date::to_utc( $start_local )->format( Date::DB_DATETIME_FORMAT );
			$end_utc   = Date::to_utc( $end_local )->format( Date::DB_DATETIME_FORMAT );

			$query->where( 'payments.created_at', '>=', $start_utc, '%s' );
			$query->where( 'payments.created_at', '<', $end_utc, '%s' );
		}

		if ( ! empty( $filters['service_id'] ) ) {
			$query->where( 'appointments.service_id', '=', $filters['service_id'], '%d' );
		}

		if ( ! empty( $filters['processor_id'] ) ) {
			$query->where( 'payments.gateway', '=', $filters['processor_id'], '%s' );
		}
	}

	/**
	 * Get current filters query parameters.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function get_applied_filters() {

		$filters = [
			'status'       => $this->get_selected_status(),
			'search'       => $this->get_search_term(),
			'month'        => $this->get_selected_month(),
			'year'         => $this->get_selected_year(),
			'service_id'   => $this->get_selected_service(),
			'processor_id' => $this->get_selected_processor(),
		];

		return array_filter( $filters, fn( $v ) => $v !== null );
	}

	/**
	 * Return status filter value or null.
	 *
	 * @since 1.0.0
	 *
	 * @return string|null
	 */
	public function get_selected_status() {

		return $this->get_active_filter( 'status' );
	}

	/**
	 * Return search filter values or null.
	 *
	 * @since 1.0.0
	 *
	 * @return string|null
	 */
	public function get_search_term() {

		return $this->get_active_filter( 's' );
	}

	/**
	 * Return month filter value or null.
	 *
	 * @since 1.0.0
	 *
	 * @return int|null
	 */
	public function get_selected_month() {

		return $this->get_active_filter( 'month' );
	}

	/**
	 * Return year filter value or current year.
	 *
	 * @since 1.0.0
	 *
	 * @return int|null
	 */
	public function get_selected_year() {

		$year = $this->get_active_filter( 'year' );

		if ( empty( $year ) ) {
			return $this->get_selected_month() ?
				(int) Date::format_display_date( new DateTimeImmutable(), 'Y' ) :
				null;
		}

		// Validate year is positive (years must be >= 1).
		if ( $year < 1 ) {
			return null;
		}

		return $year;
	}

	/**
	 * Return service filter value or null.
	 *
	 * @since 1.0.0
	 *
	 * @return int|null
	 */
	public function get_selected_service() {

		return $this->get_active_filter( 'service_id' );
	}

	/**
	 * Return processor filter value or null.
	 *
	 * @since 1.0.0
	 *
	 * @return string|null
	 */
	public function get_selected_processor() {

		return $this->get_active_filter( 'processor_id' );
	}

	/**
	 * Get the filter label map for the search results notice.
	 *
	 * @since 1.4.0
	 *
	 * @return array Filter label map.
	 */
	protected function get_filter_label_map() {

		return array_merge(
			parent::get_filter_label_map(),
			[
				'service_id' => [
					'label' => esc_html__( 'Service', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'model' => ServiceModel::class,
				],
			]
		);
	}

	/**
	 * Build resolved filter labels for the search results notice.
	 *
	 * Extends parent to handle the processor filter which uses the gateway registry.
	 *
	 * @since 1.4.0
	 *
	 * @return array Indexed array of resolved filter label entries.
	 */
	protected function get_active_filter_labels() {

		$labels = parent::get_active_filter_labels();

		// Processor filter — uses gateway registry, not a model.
		$processor_id = $this->get_selected_processor();

		if ( ! empty( $processor_id ) ) {
			$gateways = Plugin::get( PaymentGatewayRegistry::class )->get_registered();

			if ( isset( $gateways[ $processor_id ] ) ) {
				$labels[] = [
					'label'        => esc_html__( 'Processor', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'label_plural' => esc_html__( 'Processor', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'value'        => $gateways[ $processor_id ]::get_name(),
				];
			}
		}

		return $labels;
	}

	/**
	 * Get the payment statuses for filtering purpose.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of payment statuses.
	 */
	private function get_statuses() {

		// In this order statuses will appear in filters bar.
		$statuses = [
			'all'       => esc_html__( 'All', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'completed' => esc_html__( 'Completed', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'pending'   => esc_html__( 'Pending', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'failed'    => esc_html__( 'Failed', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'refunded'  => esc_html__( 'Refunded', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'trash'     => esc_html__( 'Trash', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
		];

		return $statuses;
	}

	/**
	 * Get the items counts for various statuses of payments.
	 *
	 * @since 1.0.0
	 */
	private function get_counts() {

		$this->counts = [];

		// Base params with applied filters.
		$filters = $this->get_applied_filters();

		unset( $filters['status'] );

		$query = ( new QueryBuilder() )
			->table( 'payments' )
			->select( [ 'payments.status', 'COUNT(DISTINCT id)' ] )
			->where( 'payments.trashed_at', 'IS NULL' )
			->group_by( [ 'payments.status' ] );

		$this->build_filters_query( $query, $filters );

		$stats = $query->get();
		$stats = array_combine( array_column( $stats, 'status' ), array_column( $stats, 'count' ) );

		$trashed = ( new QueryBuilder() )
			->table( 'payments' )
			->select( [ 'COUNT(DISTINCT id)' ] )
			->where( 'payments.trashed_at', 'IS NOT NULL' )
			->first();

		$stats['trash'] = $trashed['count'];

		$this->counts['total'] = array_sum( $stats ) - ( $stats['trash'] ?? 0 );

		foreach ( $this->get_statuses() as $status => $name ) {
			$count = $stats[ $status ] ?? 0;

			$this->counts[ 'status_' . $status ] = $count;
		}
	}

	/**
	 * Get the data, prepare pagination, process bulk actions.
	 * Prepare columns for display.
	 *
	 * @since 1.0.0
	 */
	public function prepare_items() {

		// Retrieve count.
		$this->get_counts();

		$items_per_page = 20;
		$offset         = ( $this->get_pagenum() - 1 ) * $items_per_page;
		$orderby        = 'id';
		$order          = 'DESC';
		$filters        = $this->get_applied_filters();

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		if (
			! empty( $_REQUEST['orderby'] ) &&
			in_array( $_REQUEST['orderby'], array_keys( $this->get_sortable_columns() ), true )
		) {
			$orderby = sanitize_key( $_REQUEST['orderby'] );
		}

		if (
			! empty( $_REQUEST['order'] ) &&
			in_array( strtoupper( $_REQUEST['order'] ), [ 'ASC', 'DESC' ], true )
		) {
			$order = sanitize_key( $_REQUEST['order'] );
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		$query = PaymentModel::query()
			->with( [ 'customer', 'appointments.service' ] );

		$this->build_filters_query( $query, $filters );

		$query->order_by( 'payments.' . $orderby, $order )
			->offset( $offset )
			->limit( $items_per_page )
			->group_by( [ 'payments.id' ] );

		// Exclude trash payments from 'all' view.
		if ( empty( $filters['status'] ) ) {
			$query->where( 'payments.trashed_at', 'IS NULL' );
		}

		$this->items = $query->get();

		$total_items = $this->counts['total'];

		if (
			! empty( $filters['status'] ) &&
			isset( $this->counts[ 'status_' . $filters['status'] ] )
		) {
			$total_items = $this->counts[ 'status_' . $filters['status'] ];
		}

		$this->set_pagination_args(
			[
				'total_items' => $total_items,
				'per_page'    => $items_per_page,
			]
		);
	}

	/**
	 * Whether the table has items to display or not.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public function has_items() {

		return count( $this->items ) > 0;
	}

	/**
	 * Message to be displayed when there are no items.
	 *
	 * @since 1.0.0
	 */
	public function no_items() {

		if ( ! empty( $this->get_applied_filters() ) ) {
			esc_html_e( 'No payments found.', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		} else {
			?>
			<div class="wp-list-table__no-items">
				<div class="wp-list-table__no-items-icon">
					<img width="23" height="20" src="<?php echo esc_url( SCBOOKINGS_PLUGIN_URL . 'assets/images/icons/money-bill.svg' ); ?>">
				</div>
				<div class="wp-list-table__no-items-title"><?php esc_html_e( 'No Payments Detected!', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></div>
			</div>
			<?php
		}
	}

	/**
	 * Extra controls to be displayed between bulk actions and pagination.
	 *
	 * @since 1.0.0
	 *
	 * @param string $which Which tablenav: top or bottom.
	 */
	protected function extra_tablenav( $which ) {

		if ( $which !== 'top' ) {
			return;
		}

		// TODO: Fetch only services that have payments.
		$services = ServiceModel::query()
			->select( [ 'id', 'name' ] )
			->order_by( 'name' )
			->get();

		$selected_month     = $this->get_selected_month();
		$selected_year      = $this->get_selected_year();
		$selected_service   = $this->get_selected_service();
		$selected_processor = $this->get_selected_processor();
		?>
		<div class="alignleft actions">
			<select name="month">
				<option value=""><?php esc_html_e( 'All Months', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></option>
				<?php for ( $month_index = 1; $month_index <= 12; $month_index++ ) : ?>
					<option value="<?php echo esc_attr( $month_index ); ?>" <?php selected( $month_index, $selected_month ); ?>>
						<?php echo esc_html( $GLOBALS['wp_locale']->get_month( $month_index ) ); ?>
					</option>
				<?php endfor; ?>
			</select>
			<input type="number" name="year" value="<?php echo esc_attr( $selected_year ); ?>" placeholder="<?php esc_html_e( 'Year', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>" min="1">
			<?php submit_button( esc_html__( 'View', 'sugar-calendar-bookings-scheduling-appointments-lite' ), '', 'view_action', false ); ?>
		</div>
		<div class="alignleft actions">
			<select name="service_id">
				<option value=""><?php esc_html_e( 'All Services', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></option>
				<?php foreach ( $services as $service ) : ?>
					<option value="<?php echo esc_attr( $service->id ); ?>" <?php selected( $selected_service, $service->id ); ?>><?php echo esc_html( $service->name ); ?></option>
				<?php endforeach; ?>
			</select>
			<select name="processor_id">
				<option value=""><?php esc_html_e( 'All Processors', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></option>
				<?php foreach ( Plugin::get( PaymentGatewayRegistry::class )->get_registered() as $gateway_id => $gateway_class ) : ?>
					<option value="<?php echo esc_attr( $gateway_id ); ?>" <?php selected( $selected_processor, $gateway_id ); ?>><?php echo esc_html( $gateway_class::get_name() ); ?></option>
				<?php endforeach; ?>
			</select>
			<?php submit_button( esc_html__( 'Filter', 'sugar-calendar-bookings-scheduling-appointments-lite' ), '', 'filter_action', false ); ?>
		</div>
		<?php
	}

	/**
	 * Get the name of the primary column.
	 * Important for the mobile view.
	 *
	 * @since 1.0.0
	 *
	 * @return string The name of the primary column.
	 */
	protected function get_primary_column_name() {

		return 'payment';
	}

	/**
	 * Allow users to select multiple payments at once (to perform a bulk action, for example).
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment PaymentModel object.
	 *
	 * @return string Checkbox for bulk selection.
	 */
	protected function column_cb( $payment ) {

		return sprintf(
			'<input type="checkbox" name="payment_id[]" value="%d" />',
			$payment->get_id()
		);
	}

	/**
	 * Render the Payment column.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment The current row payment.
	 *
	 * @return string
	 */
	protected function column_payment( $payment ) {

		$title = sprintf(
			'<strong><a href="%1$s">#%2$s - %3$s</a></strong>',
			PaymentsDetailsPage::get_url( $payment->id ),
			$payment->id,
			$payment->customer->name
		);

		$actions = [];

		if ( current_user_can( 'manage_options' ) ) {
			if ( $this->get_selected_status() !== 'trash' ) {
				$actions['view']  = esc_html__( 'View', 'sugar-calendar-bookings-scheduling-appointments-lite' );
				$actions['trash'] = esc_html__( 'Trash', 'sugar-calendar-bookings-scheduling-appointments-lite' );
			} else {
				$actions['restore'] = esc_html__( 'Restore', 'sugar-calendar-bookings-scheduling-appointments-lite' );
				$actions['delete']  = esc_html__( 'Delete', 'sugar-calendar-bookings-scheduling-appointments-lite' );
			}
		}

		$actions = array_combine(
			array_keys( $actions ),
			array_map( fn( $label, $action ) => sprintf( '<a href="%1$s">%2$s</a>', $this->get_quick_action_url( $payment, $action ), $label ), $actions, array_keys( $actions ) )
		);

		return sprintf( '%1$s %2$s', $title, $this->row_actions( $actions ) );
	}

	/**
	 * Get the link to a certain action: "edit" or "delete" for row actions.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment PaymentModel object.
	 * @param string       $link    The link type to create.
	 *
	 * @return string
	 */
	public function get_quick_action_url( $payment, $link = 'view' ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		$url  = '';
		$link = sanitize_key( $link );

		switch ( $link ) {
			case 'view':
				$url = PaymentsDetailsPage::get_url( $payment->id );
				break;

			case 'trash':
				$url = esc_url(
					wp_nonce_url(
						add_query_arg(
							[
								'action'     => 'trash',
								'payment_id' => $payment->id,
							],
							$this->get_base_url()
						),
						'bulk-payments'
					)
				);
				break;

			case 'restore':
				$url = esc_url(
					wp_nonce_url(
						add_query_arg(
							[
								'action'     => 'restore',
								'payment_id' => $payment->id,
							],
							$this->get_base_url()
						),
						'bulk-payments'
					)
				);
				break;

			case 'delete':
				$url = esc_url(
					wp_nonce_url(
						add_query_arg(
							[
								'action'     => 'delete',
								'payment_id' => $payment->id,
							],
							$this->get_base_url()
						),
						'bulk-payments'
					)
				);
				break;
		}

		return $url;
	}

	/**
	 * Render the Total column.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment The current row payment.
	 *
	 * @return string
	 */
	protected function column_total( $payment ) {

		return PaymentsHelpers::format_currency( $payment->total_amount );
	}

	/**
	 * Render the Paid On column.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment The current row payment.
	 *
	 * @return string
	 */
	protected function column_paid_on( $payment ) {

		return Date::format_display_date( $payment->created_at );
	}

	/**
	 * Render the Service column.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment The current row payment.
	 *
	 * @return string
	 */
	protected function column_service( $payment ) {

		$appointments = array_map(
			fn( $appointment ) => sprintf( '<a href="%1$s">%2$s</a>', esc_url( AppointmentDetailsPage::get_url( $appointment->id ) ), esc_html( $appointment->service->name ) ),
			$payment->appointments
		);

		return implode( '<br>', $appointments );
	}

	/**
	 * Render the Customer column.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment The current row payment.
	 *
	 * @return string
	 */
	protected function column_customer( $payment ) {

		return sprintf(
			'<a href="#" data-customer-id="%1$s">%2$s</a>',
			$payment->customer->id,
			$payment->customer->name
		);
	}

	/**
	 * Render the Processor column.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment The current row payment.
	 *
	 * @return string
	 */
	protected function column_processor( $payment ) {

		$gateway_name = PaymentsHelpers::get_payment_gateway_display_name( $payment );
		$gateway_id   = $payment->gateway;

		return sprintf(
			'<div><img src="%1$s" alt="%2$s" width="16" height="16"> <span>%2$s</span></div>',
			esc_url( SCBOOKINGS_PLUGIN_URL . 'assets/images/icons/payment-gateways/' . $gateway_id . '.svg' ),
			$gateway_name
		);
	}

	/**
	 * Render the Status column.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment The current row payment.
	 *
	 * @return string
	 */
	protected function column_status( $payment ) {

		return UI::payment_status_badge( $payment, true );
	}

	/**
	 * Define a list of available bulk actions.
	 *
	 * @since 1.0.0
	 *
	 * @return array List of actions: slug=>name.
	 */
	protected function get_bulk_actions() {

		$actions = [];

		if ( current_user_can( 'manage_options' ) ) {
			if ( $this->get_selected_status() !== 'trash' ) {
				$actions['trash'] = esc_html__( 'Trash', 'sugar-calendar-bookings-scheduling-appointments-lite' );
			} else {
				$actions['restore'] = esc_html__( 'Restore', 'sugar-calendar-bookings-scheduling-appointments-lite' );
				$actions['delete']  = esc_html__( 'Delete', 'sugar-calendar-bookings-scheduling-appointments-lite' );
			}
		}

		return $actions;
	}

	/**
	 * Process the bulk actions.
	 *
	 * @since 1.0.0
	 */
	public function process_bulk_actions() {

		$this->display_bulk_action_message();

		if ( empty( $_REQUEST['_wpnonce'] ) ) {
			return;
		}

		if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'bulk-payments' ) ) {
			return;
		}

		$this->process_bulk_action_single();
	}

	/**
	 * Display a bulk action result message.
	 *
	 * @since 1.0.0
	 */
	private function display_bulk_action_message() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$notices = Plugin::get( Notices::class );

		if ( ! $notices->get_query_notice() ) {
			return;
		}

		$message = '';
		$count   = (int) ( $notices->get_query_notice_data()[0] ?? 0 );

		switch ( $notices->get_query_notice() ) {
			case 'bulk_trashed':
				/* translators: %d - number of trashed payments. */
				$message = _n( '%d payment was successfully trashed.', '%d payments were successfully trashed.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;

			case 'bulk_restored':
				/* translators: %d - number of restored payments. */
				$message = _n( '%d payment was successfully restored.', '%d payments were successfully restored.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;

			case 'bulk_deleted':
				/* translators: %d - number of deleted payments. */
				$message = _n( '%d payment was successfully deleted.', '%d payments were successfully deleted.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;
		}

		if ( ! empty( $message ) ) {
			Plugin::get( Notices::class )->success( sprintf( $message, $count ) );
		}
	}

	/**
	 * Process single bulk action.
	 *
	 * @since 1.0.0
	 */
	private function process_bulk_action_single() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.CyclomaticComplexity.TooHigh

		$action = $this->current_action();

		if ( empty( $action ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
		$ids = isset( $_GET['payment_id'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['payment_id'] ) ) : false;

		if ( empty( $ids ) ) {
			return;
		}

		$sendback = $this->get_base_url();

		switch ( $action ) {
			// Trash payments.
			case 'trash':
				$sendback = $this->process_bulk_action_single_trash( $ids, $sendback );
				break;

			// Restore payments.
			case 'restore':
				$sendback = $this->process_bulk_action_single_restore( $ids, $sendback );
				break;

			// Delete payments.
			case 'delete':
				$sendback = $this->process_bulk_action_single_delete( $ids, $sendback );
				break;
		}

		wp_safe_redirect( $sendback );
		exit();
	}

	/**
	 * Process the bulk action trash.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_trash( $ids, $sendback ) {

		$trashed  = 0;
		$payments = PaymentModel::query()
			->where( 'id', 'IN', $ids, '%d' )
			->get();

		foreach ( $payments as $payment ) {
			$payment->trashed_at = Date::create()->format( Date::DB_DATETIME_FORMAT );

			$success = $payment->save();

			// If it didn't work, continue.
			if ( ! $success ) {
				continue;
			}

			++$trashed;
		}

		return add_query_arg(
			[
				'scbookings-notice'      => 'bulk_trashed',
				'scbookings-notice-data' => $trashed,
			],
			$sendback
		);
	}

	/**
	 * Process the bulk action restores.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_restore( $ids, $sendback ) {

		$restored = 0;
		$payments = PaymentModel::query()
			->with( [ 'meta' ] )
			->where( 'id', 'IN', $ids, '%d' )
			->get();

		foreach ( $payments as $payment ) {
			$payment->trashed_at = null;

			$success = $payment->save();

			// If it didn't work, continue.
			if ( ! $success ) {
				continue;
			}

			++$restored;
		}

		$trash_count = PaymentModel::query()
			->where( 'trashed_at', 'IS NOT NULL' )
			->count();

		// If trash is emptied.
		if ( $trash_count < 1 ) {
			$sendback = remove_query_arg( 'status', $sendback );
		}

		return add_query_arg(
			[
				'scbookings-notice'      => 'bulk_restored',
				'scbookings-notice-data' => $restored,
			],
			$sendback
		);
	}

	/**
	 * Process the bulk action delete.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_delete( $ids, $sendback ) {

		Plugin::get( DeleteManager::class )->delete_payments( $ids );

		return add_query_arg(
			[
				'scbookings-notice'      => 'bulk_deleted',
				'scbookings-notice-data' => count( $ids ),
			],
			$sendback
		);
	}
}
