<?php

namespace GravityKit\GravityActions\Actions;

use GF_Field;
use GFAPI;
use GFCommon;

use GFFormsModel;
use \GravityKitFoundation;

use GravityKit\GravityActions\Assets;
use GravityKit\GravityActions\Template;
use GravityKit\GravityActions\Utils\Arrays;
use function GravityKit\GravityActions\get_request_var;
use function GravityKit\GravityActions\is_truthy;

class EmailAction extends ActionAbstract {
	/**
	 * @inheritDoc
	 */
	public static function get_key() {
		return 'gk-bulk-email';
	}

	/**
	 * @inheritDoc
	 */
	public function hook() {
		parent::hook();
	}

	/**
	 * @inheritDoc
	 */
	public static function get_title() {
		return esc_attr__( 'Bulk Email', 'gk-gravityactions' );
	}

	/**
	 * @inheritDoc
	 */
	public function get_subtitle( Template $template ) {
		return $template->render( 'email/modal-subtitle', [], false );
	}

	/**
	 * @inheritDoc
	 */
	public static function has_view() {
		return true;
	}

	/**
	 * @inheritDoc
	 */
	public static function is_ajax() {
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	public function enqueue_assets() {
		if ( ! Assets::instance()->should_enqueue_admin() ) {
			return;
		}

		wp_enqueue_editor();
		wp_enqueue_media();
		wp_enqueue_script( 'gk-gravityactions-email' );

		$forms   = \GFFormsModel::get_forms( null, 'title' );
		$form_id = rgget( 'id' );

		if ( count( $forms ) === 0 ) {
			return '';
		}

		if ( empty( $form_id ) ) {
			$form_id = $forms[0]->id;
		}

		$form       = \GFAPI::get_form( $form_id );
		$merge_tags = GFCommon::get_merge_tags( $form['fields'], '', false );

		wp_localize_script( 'gk-gravityactions-email', 'gk_email', [
			'form'       => $form,
			'merge_tags' => $merge_tags,
		] );
	}

	/**
	 * Get email fields from entry.
	 *
	 * @since 1.4
	 *
	 * @param array $form
	 *
	 * @return array
	 */
	public static function get_email_fields( $form ) {
		$fields = GFAPI::get_fields_by_type( $form, [ 'email' ] );
		if ( empty( $fields ) ) {
			return [];
		}

		$options   = [];
		$options[] = [
			'text' => __( 'Select a Email Field', 'gk-gravityactions' ),
		];
		foreach ( $fields as $key => $field ) {
			$options[] = [
				'text'  => $field->label,
				'value' => $field->id,
			];
		}

		return $options;
	}

	/**
	 * Given the object of a field, and it's form convert it to not required.
	 *
	 * @since 1.4
	 *
	 * @param object $field
	 * @param array  $form
	 *
	 * @return object
	 */
	public static function make_field_not_required( $field, $form ) {
		$field->required = false;

		return $field;
	}

	/**
	 * Validate model fields.
	 *
	 * @since 1.4
	 *
	 * @param array $template_vars
	 *
	 * @return void|\WP_Error
	 */
	public function validate_modal_request( array $template_vars = [] ) {
		if ( $template_vars['view'] !== 'email/step-2' ) {
			return;
		}

		$template_vars['left_button_request'] = 'email/step-1';
		$template_vars['left_button_text'] = esc_html__( 'Back', 'gk-gravityactions' );

		$field_values = $template_vars['field_values'];
		if ( empty( $field_values ) ) {
			return new \WP_Error(
				'gk_bulk_email',
				__( 'Fields are required.', 'gk-gravityactions' ),
				$template_vars
			);
		}

		$form = \GFAPI::get_form( $template_vars['form_id'] );

		// Send to.
		if ( $field_values['toType'] === 'email' ) {
			add_filter( 'gk/gravityactions/email_action/get_field:toField', [ static::class, 'make_field_not_required' ], 10, 2 );
		}

		if ( $field_values['toType'] === 'field' ) {
			add_filter( 'gk/gravityactions/email_action/get_field:toEmail', [ static::class, 'make_field_not_required' ], 10, 2 );
		}

		foreach ( $field_values as $key => $value ) {
			$field = $this->get_field( $key, $form );

			if ( empty( $field ) ) {
				return new \WP_Error(
					'gk_bulk_email',
					sprintf( __( "%s is not a valid field", 'gk-gravityactions' ), $key ),
					$template_vars
				);
			}

			// Required validation.
			if ( $value === '' && $field->required === true ) {
				return new \WP_Error(
					'gk_bulk_email',
					sprintf( __( "%s is required.", 'gk-gravityactions' ), $field->label ),
					$template_vars
				);
			}

			// Email validation.
			if ( $field->type === 'email' && $value != '' && ! is_email( $value ) ) {
				return new \WP_Error(
					'gk_bulk_email',
					sprintf( __( "%s is not a valid email.", 'gk-gravityactions' ), $value ),
					$template_vars
				);
			}
		}
	}

	/**
	 * Get the 'to' email address from user or entry.
	 *
	 * @since 1.4
	 *
	 * @param array $field_values
	 * @param array $form
	 *
	 * @return string|null
	 */
	public static function get_to_email( $field_values, $form ) {
		if ( empty( $field_values['toType'] ) ) {
			return null;
		}

		if ( $field_values['toType'] === 'email' ) {
			return $field_values['toEmail'];
		}

		if ( $field_values['toType'] === 'field' ) {
			$email_fields = static::get_email_fields( $form );
			foreach ( $email_fields as $field ) {
				if ( empty( $field['value'] ) ) {
					continue;
				}

				if ( (int) $field['value'] === (int) $field_values['toField'] ) {
					return '{' . $field['text'] . '}';
				}
			}
		}

		return null;
	}

	/**
	 * Get model fields.
	 *
	 * @since 1.4
	 *
	 * @param array $form
	 *
	 * @return array
	 */
	public function get_fields( $form ) {
		$fields = [
			[
				'label'   => __( 'Send To', 'gk-gravityactions' ),
				'id'      => 'toType',
				'class'   => 'gk-gravityactions-send_to',
				'type'    => 'radio',
				'tooltip' => __( '<strong>Send To Email Address</strong> Enter the email address you would like the notification email sent to.', 'gk-gravityactions' ),
				'choices' => [
					[
						'text'  => __( 'Select a Field', 'gk-gravityactions' ),
						'value' => 'field',
					],
					[
						'text'     => __( 'Enter Email', 'gk-gravityactions' ),
						'value'    => 'email',
						'selected' => true,
					],
				],
			],
			[
				'label'    => __( 'Send To Email', 'gk-gravityactions' ),
				'id'       => 'toEmail',
				'type'     => 'email',
				'class'    => 'gk-gravityactions-toEmail',
				'required' => true,
			],
			[
				'label'         => __( 'Send To Field', 'gk-gravityactions' ),
				'id'            => 'toField',
				'type'          => 'select',
				'class'         => 'gk-gravityactions-toField hidden',
				'empty_message' => __( 'Your form does not have an email field. Add an email field to your form and try again.', 'gk-gravityactions', 'gk-gravityactions' ),
				'choices'       => static::get_email_fields( $form ),
				'required'      => true,
			],
			[
				'label'       => __( 'From Name', 'gk-gravityactions' ),
				'input_class' => 'merge-tag-support mt-position-right manual_position',
				'id'          => 'fromName',
				'type'        => 'text',
				'tooltip'     => __( '<strong>From Name</strong> Enter the name you would like the notification email sent from, or select the name from available name fields.', 'gk-gravityactions' ),
			],
			[
				'label'       => __( 'From Email', 'gk-gravityactions' ),
				'input_class' => 'merge-tag-support mt-position-right manual_position',
				'id'          => 'fromEmail',
				'type'        => 'text',
				'tooltip'     => __( '<strong>From Email Address</strong> Enter an authorized email address you would like the notification email sent from. To avoid deliverability issues, always use your site domain in the from email.', 'gk-gravityactions' ),
			],
			[
				'label'       => __( 'Reply To', 'gk-gravityactions' ),
				'input_class' => 'merge-tag-support mt-position-right manual_position',
				'id'          => 'replyTo',
				'type'        => 'text',
				'tooltip'     => __( '<strong>Reply To</strong> Enter the email address you would like to be used as the reply to address for the notification email.', 'gk-gravityactions' ),
			],
			[
				'label'       => __( 'BCC', 'gk-gravityactions' ),
				'input_class' => 'merge-tag-support mt-position-right manual_position',
				'id'          => 'bcc',
				'type'        => 'text',
				'tooltip'     => __( '<strong>Blind Carbon Copy Addresses</strong> Enter a comma separated list of email addresses you would like to receive a BCC of the notification email.', 'gk-gravityactions' ),
			],
			[
				'label'       => __( 'Subject', 'gk-gravityactions' ),
				'input_class' => 'merge-tag-support mt-position-right manual_position',
				'id'          => 'subject',
				'type'        => 'text',
				'required'    => true,
			],
			[
				'label'       => __( 'Message', 'gk-gravityactions' ),
				'input_class' => 'merge-tag-support mt-position-right manual_position mt-wp_editor',
				'id'          => 'message',
				'type'        => 'textarea',
				'required'    => true,
			],
			[
				'label'   => __( 'Auto-formatting', 'gk-gravityactions' ),
				'id'      => 'disableAutoformat',
				'type'    => 'checkbox',
				'choices' => [
					[
						'text'  => __( 'Disable auto-formatting', 'gk-gravityactions' ),
						'value' => 'disableAutoformat',
					],
				],
				'tooltip' => __( '<strong>Disable Auto-Formatting</strong> When enabled, auto-formatting will insert paragraph breaks automatically. Disable auto-formatting when using HTML to create email notification content.', 'gk-gravityactions' ),
			],
		];

		/**
		 * Filter to allow short-circuiting the modal rendering.
		 *
		 * @since 1.3
		 *
		 * @param array $fields
		 * @param array $metas
		 */
		$fields = apply_filters( 'gk/gravityactions/email_action/get_fields', $fields, $form );

		$fields = array_map( static function ( $field ) use ( $form ) {
			return static::prepare_field( $field, $form );
		}, $fields );

		// Prevent empty fields.
		$fields = array_filter( $fields );

		uasort( $fields, '\GravityKit\GravityActions\sort_by_priority' );

		return $fields;
	}

	/**
	 * Prepares the data for a specific field for this action.
	 *
	 * @since 1.4
	 *
	 * @param array $field
	 * @param array $form
	 *
	 * @return false|mixed|null
	 */
	public static function prepare_field( $field, $form ) {
		if ( empty( $field['type'] ) ) {
			return null;
		}

		$default = [
			'required' => false,
			'label'    => null,
			'priority' => 10,
		];

		if ( empty( $field['id'] ) ) {
			$default['id'] = wp_unique_id( 'gk-email-field-' );
		}

		if ( ! empty( $field['choices'] ) ) {
			$field['choices'] = array_map( static function ( $choice ) {
				return (object) $choice;
			}, $field['choices'] );
		}

		$field = (object) array_merge( $default, $field );

		/**
		 * Filter any field before returning it's values.
		 *
		 * @since 1.3
		 *
		 * @param array $field
		 * @param array $form
		 */
		$field = apply_filters( 'gk/gravityactions/email_action/get_field', $field, $form );

		/**
		 * Filter of a specific field before returning it.
		 *
		 * @since 1.3
		 *
		 * @param array $field
		 * @param array $form
		 */
		return apply_filters( "gk/gravityactions/email_action/get_field:{$field->id}", $field, $form );
	}

	/**
	 * Fetches a specific field from this action.
	 *
	 * @since 1.4
	 *
	 * @param int|string $id
	 * @param array      $form
	 *
	 * @return false|mixed|null
	 */
	public function get_field( $id, $form ) {
		$fields = wp_filter_object_list( $this->get_fields( $form ), [ 'id' => $id ] );

		if ( empty( $fields ) ) {
			return null;
		}

		return reset( $fields );
	}

	/**
	 * @inheritDoc
	 */
	public function prepare_template_vars( array $template_vars = [] ) {
		$template_vars = parent::prepare_template_vars( $template_vars );

		if ( ! empty( $template_vars['selected_fields'] ) ) {
			$template_vars['selected_fields'] = array_unique( $template_vars['selected_fields'] );
			$template_vars['selected_fields'] = array_filter( $template_vars['selected_fields'] );
			foreach ( $template_vars['selected_fields'] as $field_id ) {
				$field = GFAPI::get_field( $template_vars['form_id'], $field_id );

				if ( empty( $field->inputs ) ) {
					continue;
				}
				foreach ( $field->inputs as $input ) {
					// Ignore inputs that are hidden.
					if ( is_truthy( Arrays::get( $input, 'isHidden', false ) ) ) {
						continue;
					}

					// Dont add the same input twice.
					if ( in_array( $input['id'], $template_vars['selected_fields'] ) ) {
						continue;
					}
					$template_vars['selected_fields'][] = $input['id'];
				}
			}
		}

		$template_vars['current_page'] = (int) get_request_var( 'current_page', 0 );
		$template_vars['per_page']     = static::get_batch_size();

		$template_vars['emails_data'] = $this->process_emails( $template_vars );

		return $template_vars;
	}

	/**
	 * fetches from foundation the batch size to be used by the bulk emails action.
	 *
	 * @since 1.4
	 *
	 * @return int
	 */
	public static function get_batch_size(): int {
		return (int) GravityKitFoundation::settings()->get_plugin_setting( 'gravityactions', 'batch-size', 50 );
	}

	/**
	 * Adds a `Precedence: Bulk` header to the emails being sent.
	 *
	 * @since $ver$
	 *
	 * @param array $email The email object.
	 *
	 * @return array The updated email object.
	 */
	public static function add_precedence_bulk_header( array $email ) : array {
		$email['headers']['Precedence'] = 'Precedence: Bulk';

		return $email;
	}

	/**
	 * Process the modifications required by this action.
	 *
	 * @since 1.4
	 *
	 * @param array $template_vars Which template variables were passed down.
	 *
	 */
	public function process_emails( array $template_vars ) {
		if ( $template_vars['view'] !== 'email/step-processing' ) {
			return null;
		}

		if ( empty( $template_vars['entries'] ) || empty( $template_vars['field_values'] ) ) {
			return null;
		}

		require_once( ABSPATH . 'wp-admin' . '/includes/upgrade.php' );

		$form = \GFAPI::get_form( $template_vars['form_id'] );

		$notification            = $template_vars['field_values'];
		$notification['name']    = esc_html__( 'GravityActions - Bulk Email', 'gk-gravityactions' );
		$notification['id']      = 'gravityactions_bulk_email';
		$notification['message'] = wp_kses_post( wpautop( force_balance_tags( deslash( $notification['message'] ) ) ) );

		if ( $notification['toType'] === 'email' ) {
			$notification['to'] = $notification['toEmail'];
		}

		if ( ! empty( $notification['fromEmail'] ) ) {
			$notification['from'] = $notification['fromEmail'];
		}

		$total_entries = count( $template_vars['entries'] );

		// Ensure keys.
		$template_vars['entries'] = array_values( $template_vars['entries'] );

		$offset = 0;
		if ( $total_entries > $template_vars['per_page'] ) {
			$offset = $template_vars['current_page'] * $template_vars['per_page'];
		}
		$end  = $offset + $template_vars['per_page'] - 1;
		$done = [];

		add_filter( 'gform_pre_send_email', [ static::class, 'add_precedence_bulk_header' ] );

		foreach ( $template_vars['entries'] as $index => $entry_id ) {
			// Skip previous pages.
			if ( $index < $offset ) {
				continue;
			}

			// Once we reach the per_page we break.
			if ( $index > $end ) {
				break;
			}

			$entry = \GFAPI::get_entry( $entry_id );

			if ( empty( $entry ) ) {
				$notification_response = new \WP_Error(
					'gk-gravity-actions-bulk-email-invalid-entry-id',
					"Could not find an entry for this ID.",
					[
						'notification' => $notification,
						'form'         => $form,
						'entry_id'     => $entry_id,
					]
				);
			} else {
				$notification_response = \GFCommon::send_notification( $notification, $form, $entry );
			}

			$done[ $entry_id ] = $notification_response;
		}

		return $done;
	}

	/**
	 * Gets the template associated with a given type of field.
	 *
	 * @since 1.0
	 *
	 * @param object $field
	 *
	 * @return string
	 */
	public function get_field_template( $field ) {
		if ( empty( $field->type ) ) {
			return null;
		}

		$fields = [
			'text'     => 'email/fields/text',
			'website'  => 'email/fields/text',
			'phone'    => 'email/fields/text',
			'date'     => 'email/fields/text',
			'number'   => 'email/fields/number',
			'email'    => 'email/fields/email',
			'checkbox' => 'email/fields/checkbox',
			'radio'    => 'email/fields/radio',
			'select'   => 'email/fields/select',
			'textarea' => 'email/fields/textarea',
		];

		if ( ! isset( $fields[ $field->type ] ) ) {
			return null;
		}

		return $fields[ $field->type ];
	}
}