<?php
/**
 * FE Admin Bulk Emails controller.
 *
 * @since 2.0
 */

declare( strict_types=1 );
namespace FeData\FastEvents\RestAPI\Public;

defined( 'ABSPATH' ) || exit;

use Exception;
use FeData\FastEvents\Helpers\AuthHelper;
use FeData\FastEvents\Mail\MailerFactory;
use FeData\FastEvents\Storage\Options;
use FeData\FastEvents\Storage\PluginSettings;
use FeData\FastEvents\Utilities\CheckHtml;
use stdClass;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * FE Admin Bulk Emails class.
 */
class BulkEmailsController extends WP_REST_Controller {

	/**
	 * The maximum size of the request buffer with order_ids.
	 *
	 * @since 2.0
	 * @var int
	 */
	private int $batch_size;

	/**
	 * Admin Bulk Emails constructor.
	 *
	 * @param AuthHelper     $auth     The auth helper.
	 * @param Options        $options  The options.
	 * @param PluginSettings $settings The plugin settings.
	 * @param MailerFactory  $mailer   The mailer factory.
	 *
	 * @since 2.0
	 */
	public function __construct(
		private readonly AuthHelper $auth,
		private readonly Options $options,
		private readonly PluginSettings $settings,
		private readonly MailerFactory $mailer
	) {
		$this->namespace  = 'fast-events/v1';
		$this->rest_base  = 'admin/bulk/emails';
		$this->batch_size = $this->settings->as_batch_size();
	}


	/**
	 * Register the routes for the objects of the controller.
	 *
	 * @since 2.0
	 */
	public function register_routes(): void {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/list',
			array(
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::TOOLS_EMAIL );
					},
					'args'                => $this->get_endpoint_args_for_item_schema(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/send',
			array(
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::TOOLS_EMAIL );
					},
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/send-example',
			array(
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'send_example' ),
					'permission_callback' => function () {
						return $this->fast_events_admin_permissions( $this->auth::TOOLS_EMAIL );
					},
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}


	/**
	 * Get the authorization for the Bulk Emails endpoints.
	 *
	 * @param int $action The action on the controller.
	 * @return WP_Error|bool
	 *
	 * @since 2.0
	 */
	public function fast_events_admin_permissions( int $action ): WP_Error|bool {
		return $this->auth->authorize( $action );
	}


	/**
	 * Generate a list of order ids that meet the conditions
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 */
	public function create_item( $request ): WP_Error|WP_REST_Response|bool|array {
		global $wpdb;

		// Subject or body empty?
		if ( empty( $request['subject'] ) || empty( $request['body'] ) ) {
			return new WP_Error( 'missing_param', __( 'Subject or body is empty', 'fast-events' ), array( 'status' => 400 ) );
		}

		// Do we need to check all events?
		$events = $this->fast_events_tools_check_events( $request['event_ids'] ?? null );

		// Look up the orders.
		$order_table = $wpdb->prefix . 'fe_event_orders';
		if ( $request['scanned'] ) {
			// Return the orders that have scanned qrcodes.
			$qrcode_table = $wpdb->prefix . 'fe_event_qrcodes';
			// phpcs:disable
			$result = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT $order_table.id, " .
					"(SELECT COUNT(*) from $qrcode_table WHERE order_id=$order_table.id AND scan_date IS NOT NULL) AS tickets " .
					"FROM $order_table WHERE payment_status IN ('paid','dashboard') " .
					'AND created_at>=%s AND created_at<=%s ' .
					'AND num_tickets>=%d AND num_tickets<=%d ' .
					'AND total>=%f AND total<=%f ' . $events .
					'HAVING tickets>0',
					$request['date_from'],
					$request['date_to'],
					$request['tickets_minimum'],
					$request['tickets_maximum'],
					$request['amount_minimum'],
					$request['amount_maximum']
				)
			);
		} else {
			$result = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT id FROM $order_table WHERE payment_status IN ('paid','dashboard') " .
					'AND created_at>=%s AND created_at<=%s ' .
					'AND num_tickets>=%d AND num_tickets<=%d ' .
					'AND total>=%f AND total<=%f ' .
					$events,
					$request['date_from'],
					$request['date_to'],
					$request['tickets_minimum'],
					$request['tickets_maximum'],
					$request['amount_minimum'],
					$request['amount_maximum']
				)
			);
			// phpcs:enable
		}

		// No result?
		if ( empty( $result ) ) {
			return new WP_Error( 'not_found', __( 'No orders found', 'fast-events' ), array( 'status' => 404 ) );
		}

		$request->set_param( 'context', 'view' );

		return rest_ensure_response( $this->prepare_item_for_response( $result, $request ) );
	}


	/**
	 * Send email to a list of order ids.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 * @since 2.1.0 - Added redirect to query
	 */
	public function update_item( $request ): WP_Error|WP_REST_Response|bool|array {
		global $wpdb;

		if ( ! isset( $request['order_ids'] ) ) {
			return new WP_Error( 'missing_param', __( 'No order ids found', 'fast-events' ), array( 'status' => 400 ) );
		}

		// Subject or body empty?
		if ( empty( $request['subject'] ) || empty( $request['body'] ) ) {
			return new WP_Error( 'missing_param', __( 'Subject or body is empty', 'fast-events' ), array( 'status' => 400 ) );
		}

		// Get the order_ids batch.
		$order_ids = array_map( 'absint', $request['order_ids'] );

		// Check if the batchsize is not exceeded.
		if ( $this->batch_size < count( $order_ids ) ) {
			return new WP_Error( 'wrong_batch_size', __( 'Wrong batch size', 'fast-events' ), array( 'status' => 400 ) );
		}

		// Boot the HTML Purifier.
		$purifier = CheckHtml::purify();

		// Loop through the batch.
		$errors      = array();
		$order_table = $wpdb->prefix . 'fe_event_orders';
		$event_table = $wpdb->prefix . 'fe_events';
		foreach ( $order_ids as $order_id ) {

			// Look up the order.
			// phpcs:disable
			$result = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT $order_table.id, $order_table.uid, $order_table.event_id, $order_table.payment_status, " .
					"$order_table.input_fields, $order_table.name, $order_table.email, $order_table.created_at, " .
					"$event_table.sender_name, $event_table.sender_email, " .
					"$event_table.email_bcc, $event_table.email_subject, " .
					"$event_table.email_body, $event_table.redirect " .
					"FROM $order_table " .
					"INNER JOIN $event_table ON $event_table.id = $order_table.event_id " .
					"WHERE $order_table.id = %d " .
					$this->fast_events_tools_check_events( null, $order_table ),
					$order_id
				)
			);
			// phpcs:enable

			// Does the key exist?
			if ( null === $result ) {
				$errors[] = array(
					'order_id' => $order_id,
					'error'    => __( 'Order not found', 'fast-events' ),
				);
				continue;
			}

			// Inject the new subject and body.
			$result->email_subject = sanitize_text_field( $request['subject'] );
			$result->email_body    = $purifier->purify( $request['body'] );
			$result->email_bcc     = '';

			// Save the message.
			$msg_id = $this->options->save_message( $result );

			// Send the email.
			try {
				$this->mailer->send_email( $result, $msg_id );
			} catch ( Exception $e ) {
				$errors[] = array(
					'order_id' => $order_id,
					'error'    => $e->getMessage(),
				);
			}
		}

		return rest_ensure_response( array( 'errors' => $errors ) );
	}


	/**
	 * Send an example email.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error|bool|array Response object on success, or WP_Error object on failure.
	 *
	 * @since 2.0
	 * @since 2.1.0 - Added redirect to query
	 */
	public function send_example( WP_REST_Request $request ): WP_Error|WP_REST_Response|bool|array {

		// Subject or body empty?
		if ( empty( $request['subject'] ) || empty( $request['body'] ) ) {
			return new WP_Error( 'missing_param', __( 'Subject or body is empty', 'fast-events' ), array( 'status' => 404 ) );
		}

		// Check if the batchsize is not exceeded.
		if ( ! isset( $request['email'] ) ) {
			if ( ! is_email( $request['email'] ) ) {
				return new WP_Error( 'invalid_email', __( 'Invalid emailaddress', 'fast-events' ), array( 'status' => 400 ) );
			}
		}

		// Boot the HTML Purifier.
		$purifier = CheckHtml::purify();
		$errors   = array();

		// Fake a result.
		$result                 = new stdClass();
		$result->id             = '0';
		$result->event_id       = '0';
		$result->uid            = 'R3FETmO6RJtFJ1hysKikMiZLM2DGHC9C3ONSvXUt';
		$result->sender_name    = '';
		$result->sender_email   = '';
		$result->payment_status = '';
		$result->name           = 'John Doe';
		$result->email          = sanitize_email( $request['email'] );
		$result->created_at     = wp_date( 'Y-m-d H:i:s' );
		$result->email_subject  = sanitize_text_field( $request['subject'] );
		$result->email_body     = $purifier->purify( $request['body'] );
		$result->email_bcc      = '';
		$result->redirect       = '';
		$result->input_fields   = '{"fields":[{"name":"Phone","value":"0612345678"},{"name":"City","value":"Amsterdam"}],' .
								'"tickets":[{"name":"VIP ticket","price":"7.00","vat":"21","count":2},{"name":"Normal ticket",' .
								'"price":"5.00","vat":"21","count":1}],"order_amount":"19.00","events":[0],"orders":[0]}';

		// Save the message.
		$msg_id = $this->options->save_message( $result );

		// Send the email.
		try {
			$this->mailer->send_email( $result, $msg_id );
		} catch ( Exception $e ) {
			$errors[] = array(
				'order_id' => 4376,
				'error'    => $e->getMessage(),
			);
		}

		return rest_ensure_response( array( 'errors' => $errors ) );
	}


	/**
	 * Generate a list of events
	 * Create part of a query to check for the right events
	 *
	 * @param ?string $param       Comma separated list of event ids.
	 * @param string  $order_table Optional order tablename indicating if the table name should be part of the query.
	 * @return string
	 *
	 * @since 2.0
	 */
	private function fast_events_tools_check_events( ?string $param, string $order_table = '' ): string {
		$event_ids = array_map( 'absint', $this->auth->get_authorized_event_ids() );
		if ( ! empty( $param ) ) {
			$ids = array_map( 'absint', explode( ',', $param ) );

			// Check if the ids are authorized.
			if ( ! empty( $event_ids ) ) {
				foreach ( $ids as $id ) {
					if ( ! in_array( $id, $event_ids, true ) ) {
						return 'AND ' . ( ! empty( $order_table ) ? "$order_table." : '' ) .
							   'event_id IN (' . implode( ',', $event_ids ) . ')'; // phpcs:ignore
					}
				}
			}

			return 'AND ' . ( ! empty( $order_table ) ? "$order_table." : '' ) .
				   'event_id IN (' . implode( ',', $ids ) . ')'; // phpcs:ignore
		} elseif ( ! empty( $event_ids ) ) {
			return 'AND ' . ( ! empty( $order_table ) ? "$order_table." : '' ) .
				   'event_id IN (' . implode( ',', $event_ids ) . ')'; // phpcs:ignore
		}

		return '';
	}


	/**
	 * Prepare the output for response.
	 *
	 * @param mixed           $item    Array of order_ids.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 *
	 * @since 2.0
	 */
	public function prepare_item_for_response( $item, $request ): WP_REST_Response {

		$data   = array();
		$fields = $this->get_fields_for_response( $request );

		if ( rest_is_field_included( 'event_ids', $fields ) ) {
			$data['event_ids'] = $request['event_ids'];
		}

		if ( rest_is_field_included( 'date_from', $fields ) ) {
			$data['date_from'] = $request['date_from'];
		}

		if ( rest_is_field_included( 'date_to', $fields ) ) {
			$data['date_to'] = $request['date_to'];
		}

		if ( rest_is_field_included( 'tickets_minimum', $fields ) ) {
			$data['tickets_minimum'] = $request['tickets_minimum'];
		}

		if ( rest_is_field_included( 'tickets_maximum', $fields ) ) {
			$data['tickets_maximum'] = $request['tickets_maximum'];
		}

		if ( rest_is_field_included( 'amount_minimum', $fields ) ) {
			$data['amount_minimum'] = $request['amount_minimum'];
		}

		if ( rest_is_field_included( 'amount_maximum', $fields ) ) {
			$data['amount_maximum'] = $request['amount_maximum'];
		}

		if ( rest_is_field_included( 'scanned', $fields ) ) {
			$data['scanned'] = $request['scanned'];
		}

		if ( rest_is_field_included( 'batch_size', $fields ) ) {
			$data['batch_size'] = $this->batch_size;
		}

		if ( rest_is_field_included( 'order_ids', $fields ) ) {
			$data['order_ids'] = array_map( 'absint', $item );
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'embed';

		$data = $this->add_additional_fields_to_object( $data, $request );
		$data = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		return rest_ensure_response( $data );
	}


	/**
	 * Return the bulk emails' schema.
	 *
	 * @return array
	 *
	 * @since 2.0
	 */
	public function get_item_schema(): array {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'bulk-emails',
			'type'       => 'object',
			'properties' => array(
				'event_ids'       => array(
					'description' => __( 'Comma separated list of event ids.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'string',
					'maxLength'   => 256,
					'pattern'     => '^\d*(,\d+)*$',
				),
				'date_from'       => array(
					'description' => __( 'Orders created from this date.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'string',
					'format'      => 'date-time',
				),
				'date_to'         => array(
					'description' => __( 'Orders created up to this date.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'string',
					'format'      => 'date-time',
				),
				'tickets_minimum' => array(
					'description' => __( 'Minimum number of tickets in the order.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'integer',
					'arg_options' => array(
						'sanitize_callback' => 'absint',
					),
				),
				'tickets_maximum' => array(
					'description' => __( 'Maximum number of tickets in the order.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'integer',
					'arg_options' => array(
						'sanitize_callback' => 'absint',
					),
				),
				'amount_minimum'  => array(
					'description' => __( 'Minimum order amount.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'number',
				),
				'amount_maximum'  => array(
					'description' => __( 'Maximum order amount.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'number',
				),
				'scanned'         => array(
					'description' => __( 'Do the orders have to be scanned?', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'boolean',
					'required'    => true,
				),
				'email'           => array(
					'description' => __( 'The emailaddress of the user.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'string',
					'format'      => 'email',
					'maxLength'   => 100,
				),
				'subject'         => array(
					'description' => __( 'The subject of the email.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'string',
					'maxLength'   => 100,
				),
				'body'            => array(
					'description' => __( 'The body of the email.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'required'    => true,
					'type'        => 'string',
					'maxLength'   => 1000000,
				),
				'batch_size'      => array(
					'description' => __( 'Maximum size of the request buffer with order_ids.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
					'type'        => 'integer',
				),
				'order_ids'       => array(
					'description' => __( 'Array of order ids.', 'fast-events' ),
					'context'     => array( 'view', 'edit', 'embed' ),
					'type'        => 'array',
					'uniqueItems' => true,
					'items'       => array(
						'type' => 'integer',
					),
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}
}
