<?php

namespace SugarCalendarBookings\Customers\Admin;

use SugarCalendarBookings\Admin\AbstractListTable;
use SugarCalendarBookings\Customers\CustomerModel;
use SugarCalendarBookings\DeleteManager;
use SugarCalendarBookings\Plugin;
use SugarCalendarBookings\Support\Admin\Notices;
use SugarCalendarBookings\Support\Database\QueryBuilder;
use SugarCalendarBookings\Support\Helpers\UI;
use SugarCalendarBookings\Support\Tags\TagModel;

/**
 * Customers list table.
 *
 * @since 1.0.0
 * @since 1.2.1 Switched to extend abstract list table.
 */
class CustomersListTable 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 = [
		'tags' => null,
	];

	/**
	 * Domain-specific filter rules.
	 *
	 * @since 1.2.1
	 *
	 * @var array
	 */
	protected $filter_rules = [
		'tags.*' => 'id',
	];

	/**
	 * Number of customers 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' => 'customer',
				'plural'   => 'customers',
				'screen'   => 'bookings_page_sugar-calendar-bookings-customers',
			]
		);

		// 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['customer'] = esc_html__( 'Customer Name', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['tags']     = esc_html__( 'Tags', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['email']    = esc_html__( 'Email', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['phone']    = esc_html__( 'Phone', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['notes']    = esc_html__( 'Notes', 'sugar-calendar-bookings-scheduling-appointments-lite' );

		return $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 customers.
			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() {

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

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

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

		if ( isset( $filters['search'] ) ) {
			$base_url = add_query_arg( 's', $filters['search'], $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 ) {

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

		if ( ! empty( $filters['tags'] ) ) {
			$query->join( 'tag_relationships', 'customers.id', '=', 'tag_relationships.object_id' )
				->where( 'tag_relationships.object_type', '=', 'customer', '%s' )
				->where( 'tag_relationships.tag_id', 'IN', $filters['tags'], '%d' );
		}

		if ( ! empty( $filters['search'] ) ) {
			$query->where( 'customers.name', 'like', '%' . $filters['search'] . '%', '%s' );
		}
	}

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

		$filters = [
			'status' => $this->get_selected_status(),
			'tags'   => $this->get_selected_tags(),
			'search' => $this->get_search_term(),
		];

		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 selected tags filter values or null.
	 *
	 * @since 1.0.0
	 *
	 * @return array|null
	 */
	public function get_selected_tags() {

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

	/**
	 * 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(),
			[
				'tags' => [
					'label' => esc_html__( 'Tag', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'model' => TagModel::class,
				],
			]
		);
	}

	/**
	 * Get the customer statuses for filtering purpose.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of customer 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' ),
			'trash' => esc_html__( 'Trash', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
		];

		return $statuses;
	}

	/**
	 * Get the items counts for various statuses of customers.
	 *
	 * @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( 'customers' )
			->select( [ 'customers.status', 'COUNT(DISTINCT id)' ] )
			->group_by( [ 'customers.status' ] );

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

		$stats = $query->get();

		$stats = array_combine( array_column( $stats, 'status' ), array_column( $stats, '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 = CustomerModel::query()
			->with(
				[
					'meta' => fn( $q ) => $q->where( 'meta_key', 'IN', [ 'notes' ], '%s' ),
					'tags' => fn( $q ) => $q->select( [ 'id', 'name' ] ),
				]
			);

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

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

		// Exclude trash customers from 'all' view.
		if ( empty( $filters['status'] ) ) {
			$query->where( 'customers.status', '!=', 'trash', '%s' );
		}

		$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 customers 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="15" height="20" src="<?php echo esc_url( SCBOOKINGS_PLUGIN_URL . 'assets/images/icons/user.svg' ); ?>">
				</div>
				<div class="wp-list-table__no-items-title"><?php esc_html_e( 'No Customers detected!', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></div>
				<?php
				// TODO: Add plus icon.
				UI::button(
					[
						'type'  => 'tertiary',
						'size'  => 'sm',
						'text'  => esc_html__( 'Add New', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
						'class' => 'sugar-calendar-bookings-btn-new-item',
					]
				);
				?>
			</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: Get only tags that are associated with available services.
		$tags = TagModel::query()
			->where( 'object_type', '=', 'customer', '%s' )
			->get();
		?>
		<div class="alignleft actions">
			<select name="tags">
				<option value="">
					<?php esc_html_e( 'All tags', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
				</option>
				<?php foreach ( $tags as $tag ) : ?>
					<option value="<?php echo esc_attr( $tag->id ); ?>" <?php selected( in_array( $tag->id, $this->get_selected_tags() ?? [], true ) ); ?>>
						<?php echo esc_html( $tag->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 'customer';
	}

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

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

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

		$title = '<strong><a href="#" data-customer-id="' . esc_attr( $customer->id ) . '" class="row-title">' . esc_html( $customer->name ) . '</a></strong>';

		$actions = [];

		if ( current_user_can( 'manage_options' ) ) {
			if ( $this->get_selected_status() !== 'trash' ) {
				$actions['edit']  = esc_html__( 'Edit', '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( $customer, $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 CustomerModel $customer CustomerModel object.
	 * @param string        $link     The link type to create.
	 *
	 * @return string
	 */
	private function get_quick_action_url( $customer, $link = 'edit' ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

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

		switch ( $link ) {
			case 'edit':
				$url = '#';
				break;

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

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

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

		return $url;
	}

	/**
	 * Render the Tags column.
	 *
	 * @since 1.0.0
	 *
	 * @param CustomerModel $customer The current row customer.
	 *
	 * @return string
	 */
	protected function column_tags( $customer ) {

		$links = array_map(
			function ( $tag ) {

				// phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
				return sprintf(
					'<a href="%1$s">%2$s</a>',
					esc_url( add_query_arg( 'tags', $tag->id, $this->get_base_url() ) ),
					esc_html( $tag->name )
				);
			},
			$customer->tags
		);

		return implode( ', ', $links );
	}

	/**
	 * Render the Customer Email column.
	 *
	 * @since 1.0.0
	 *
	 * @param CustomerModel $customer The current row customer.
	 *
	 * @return string
	 */
	protected function column_email( $customer ) {

		return '<a href="mailto:' . esc_attr( $customer->email ) . '">' . esc_html( $customer->email ) . '</a>';
	}

	/**
	 * Render the Customer Phone column.
	 *
	 * @since 1.0.0
	 *
	 * @param CustomerModel $customer The current row customer.
	 *
	 * @return string
	 */
	protected function column_phone( $customer ) {

		return '<a href="tel:' . esc_attr( $customer->phone ) . '">' . esc_html( $customer->phone ) . '</a>';
	}

	/**
	 * Render the Customer Notes column.
	 *
	 * @since 1.0.0
	 *
	 * @param CustomerModel $customer The current row customer.
	 *
	 * @return string
	 */
	protected function column_notes( $customer ) {

		return esc_html( $customer->notes );
	}

	/**
	 * 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-customers' ) ) {
			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 customers. */
				$message = _n( '%d customer was successfully trashed.', '%d customers were successfully trashed.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;

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

			case 'bulk_deleted':
				/* translators: %d - number of deleted customers. */
				$message = _n( '%d customer was successfully deleted.', '%d customers 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['customer_id'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['customer_id'] ) ) : false;

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

		$sendback = $this->get_base_url();

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

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

			// Delete customers.
			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;
		$customers = CustomerModel::query()
			->where( 'id', 'IN', $ids, '%d' )
			->get();

		foreach ( $customers as $customer ) {
			$customer->prev_status = $customer->status;
			$customer->status      = 'trash';

			$success = $customer->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;
		$customers = CustomerModel::query()
			->with( [ 'meta' ] )
			->where( 'id', 'IN', $ids, '%d' )
			->get();

		foreach ( $customers as $customer ) {
			$customer->status      = $customer->prev_status ?? 'enabled';
			$customer->prev_status = null;

			$success = $customer->save();

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

			++$restored;
		}

		$trash_count = CustomerModel::query()
			->where( 'status', '=', 'trash', '%s' )
			->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_customers( $ids );

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