<?php

defined( 'ABSPATH' ) || die();

use Sellkit_Elementor_Optin_Ajaxhandler as AjaxHandler;
use \Elementor\Repeater as Repeater;

/**
 * Trait containing all the common or regularly used methods required for handling a CRM action.
 */
trait Sellkit_Elementor_Optin_CRM {
	/**
	 * Holds the name of User Agent.
	 *
	 * @access protected
	 * @var string
	 */
	protected $user_agent = 'Sellkit';

	/**
	 * Holds the name of User Agent.
	 *
	 * @access protected
	 * @var AjaxHandler|null
	 */
	protected $ajax_handler = null;

	/**
	 * Retrieve base URL for making remote request.
	 *
	 * @return string
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	abstract protected function get_base_url();

	/**
	 * Retrieve header parameters for making remote requests.
	 *
	 * @return array
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	abstract protected function get_headers();

	/**
	 * Retrieve parameters for making remote GET requests.
	 *
	 * @return array
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	abstract protected function get_get_request_args();

	/**
	 * Get default remote fields of this specific CRM action.
	 *
	 * @return array Default remote fields. Must include two keys of "required" and "optional".
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	abstract protected function get_default_remote_fields();

	/**
	 * Called by AJAX, retrieves the corresponding list for this CRM action.
	 *
	 * @param AjaxHandler $ajax_handler AJAX Handler instance.
	 * @param array       $params Data passed by AJAX request.
	 * @return array      Must always include one key "lists".
	 *
	 * @access public
	 * @since 1.5.0
	 */
	abstract public function get_list( AjaxHandler $ajax_handler, $params );

	/**
	 * Called by AJAX, retrieves the additional data for this CRM action.
	 *
	 * @param AjaxHandler $ajax_handler AJAX Handler instance.
	 * @param array       $params Data passed by AJAX request.
	 * @return array      Depending on the action it can contain multi keys( "custom_fields", "tags", etc.)
	 *
	 * @access public
	 * @since 1.5.0
	 */
	abstract public function get_additional_data( AjaxHandler $ajax_handler, $params );

	/**
	 * Create subscriber object from submitted data and form settings.
	 *
	 * @return array      Subscriber data. The structure depends on the specific CRM action.
	 *
	 * @since 1.5.0
	 * @access protected
	 */
	abstract protected function create_subscriber_object();

	/**
	 * Get API parameters either from the controls or saved options of settings page.
	 *
	 * @param array   $settings Control settings.
	 * @param string  $param The name of parameter. Defaults to "key".
	 * @return string
	 *
	 * @since 1.5.0
	 * @access protected
	 */
	protected function get_api_param( $settings, $param = 'key' ) {
		$name           = $this->get_name();
		$api_key_source = $settings[ "{$name}_api_key_source" ];

		if ( 'custom' === $api_key_source ) {
			return $settings[ "{$name}_custom_api_{$param}" ];
		}

		$options = get_option( 'sellkit' );

		if ( isset( $options[ "{$name}_api_{$param}" ] ) ) {
			return $options[ "{$name}_api_{$param}" ];
		}

		return '';
	}

	/**
	 * Maps the values in field mapping control of this action, to the form fields.\
	 * Usually called inside of "create_subscriber_object" method.
	 *
	 * @return array
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function get_field_mappings() {
		$field_mappings  = $this->ajax_handler->form['settings'][ $this->get_name() . '_fields_mapping' ];
		$fields_repeater = $this->ajax_handler->form['settings']['fields'];
		$form_fields     = $this->ajax_handler->form_data['fields'];
		$mapped_fields   = [];

		foreach ( $field_mappings as $item ) {
			// Send warning about fields that are required by remote endpoint, but are missing or unrequired in the form.
			$is_remote_required = array_key_exists( $item['remote_field'], $this->get_default_remote_fields()['required'] );

			if ( $is_remote_required ) {
				$field_index    = array_search( $item['local_field'], array_column( $fields_repeater, '_id' ), true );
				$is_local_exist = false !== $field_index;
				$remote_label   = $this->get_default_remote_fields()['required'][ $item['remote_field'] ];

				if ( ! $is_local_exist ) {
					$this->ajax_handler->add_response( 'admin_errors', $this->get_absent_require_notice( $remote_label ) );

				} else {
					$field_setting     = $fields_repeater[ $field_index ];
					$is_local_required = $field_setting['required'] && 'true' === $field_setting['required'];

					if ( ! $is_local_required ) {
						$this->ajax_handler->add_response( 'admin_errors', $this->get_make_require_notice( $remote_label ) );
					}
				}
			}

			// Go on with mapping process.
			if ( empty( $item['local_field'] ) || empty( $item['remote_field'] ) ) {
				continue;
			}

			$mapped_fields[ $item['remote_field'] ] = $form_fields[ $item['local_field'] ];
		}

		return $mapped_fields;
	}

	/**
	 * Send a GET request.
	 *
	 * @param string $endpoint     The piece of URL to append to base URL.
	 * @param array $request_args  Additional data to append to current request arguments.
	 * @return array The result of request, inside three keys "code", "body", "errors".
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function send_get( $endpoint, $additional_args = [] ) {
		$args     = array_merge( $this->get_get_request_args(), $additional_args );
		$response = wp_remote_get( $this->get_base_url() . $endpoint, $args );

		return [
			'code' => (int) wp_remote_retrieve_response_code( $response ),
			'body' => json_decode( wp_remote_retrieve_body( $response ), true ),
		];
	}

	/**
	 * Send a POST request.
	 *
	 * @param string $endpoint        The piece of URL to append to base URL.
	 * @param array  $request_args    Additional data to append to current request arguments.
	 * @param string $admin_error_key Key of admin_errors(if any) inside AJAX handler's response array.
	 * @return array The result of request, inside three keys "code", "body", "errors".
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function send_post( $endpoint, $request_args = [], $admin_error_key = '' ) {
		$response = wp_remote_post( $this->get_base_url() . $endpoint, $request_args );

		$this->handle_errors( $response, $admin_error_key );

		return [
			'code' => (int) wp_remote_retrieve_response_code( $response ),
			'body' => json_decode( wp_remote_retrieve_body( $response ), true ),
		];
	}

	/**
	 * Accepts the response of a remote request, collect its errors and loads them on AJAX response.
	 *
	 * @param array|\WP_Error $response The response of a remote request.
	 * @param string          $admin_error_key Key of admin_errors(if any) inside AJAX handler's response array.
	 *
	 * @access private
	 * @since 1.5.0
	 */
	private function handle_errors( $response, $admin_error_key ) {
		$error        = '';
		$message_desc = esc_html__( ' (issued by endpoint)', 'sellkit' );

		if ( is_wp_error( $response ) ) {
			$error = implode( "</li><li>{$this->get_title()}: ", $response->get_error_messages() );

			$this->ajax_handler->add_response( 'admin_errors', $error . $message_desc, $admin_error_key );
			return;
		}

		$code = (int) wp_remote_retrieve_response_code( $response );

		if ( ( $code < 200 || $code >= 300 ) ) {
			$error = sprintf(
				/* translators: 1: Action name, 2: response code, 3: error message (an unknown English string) */
				esc_html__( '%1$s: Request error-%2$s -- %3$s', 'sellkit' ),
				$this->get_title(),
				$code,
				wp_remote_retrieve_response_message( $response )
			);
		}

		if ( ! empty( $error ) ) {
			$this->ajax_handler->add_response( 'admin_errors', $error . $message_desc, $admin_error_key );
		}
	}

	/**
	 * Get Client IP Address.
	 *
	 * @return string
	 *
	 * @since 1.5.0
	 * @access public
	 * @static
	 */
	public static function get_client_ip() {
		$ip_address     = '';
		$server_headers = [
			'HTTP_CLIENT_IP',
			'HTTP_X_FORWARDED_FOR',
			'HTTP_X_FORWARDED',
			'HTTP_FORWARDED_FOR',
			'HTTP_FORWARDED',
			'REMOTE_ADDR',
		];

		// phpcs:disable WordPress.Security.ValidatedSanitizedInput
		foreach ( $server_headers as $header ) {
			if ( isset( $_SERVER[ $header ] ) ) {
				$ip_address = $_SERVER[ $header ];
				break;
			}
		}
		// phpcs:enable

		return $ip_address;
	}

	/**
	 * Ready made method that creates controls assocciated with API key of this CRM action.
	 *
	 * @param object $widget    Widget instance.
	 * @param string $list_name Name of this CRM action's list (Account, Form, Campaign, etc.)
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function add_api_controls( $widget, $list_name ) {
		$action = $this->get_name();

		$widget->add_control( "{$action}_api_key_source",
			[
				'label'       => esc_html__( 'API key', 'sellkit' ),
				'type'        => 'select',
				'default'     => 'default',
				'render_type' => 'ui',
				'options'     => [
					'default' => esc_html__( 'Default', 'sellkit' ),
					'custom'  => esc_html__( 'Custom', 'sellkit' ),
				],
			]
		);

		$hint = sprintf(
			/* translators: 1: html tag, 2: Action name, 3: html tags, 4: html tags */
			esc_html__( '%1$sSet your %2$s API key in %3$s Sellkit Settings %4$s.', 'sellkit' ),
			'<small>',
			$this->get_title(),
			sprintf( '<a target="_blank" href="%s">', admin_url() . 'admin.php?page=sellkit-settings&crm=' . $this->get_name() . '#/' ),
			'<i class="fa fa-external-link-square"></i></a></small>'
		);

		$widget->add_control( "{$action}_api_key_msg",
			[
				'type'            => 'raw_html',
				'raw'             => $hint,
				'condition'       => [ "{$action}_api_key_source" => 'default' ],
			]
		);

		$widget->add_control( "{$action}_custom_api_key",
			[
				'label'       => esc_html__( 'Custom API Key', 'sellkit' ),
				'type'        => 'text',
				'render_type' => 'ui',
				/* translators: Action name */
				'description' => sprintf( esc_html__( 'Enter your %s API key for only this form.', 'sellkit' ), $this->get_title() ),
				'condition'   => [ "{$action}_api_key_source" => 'custom' ],
			]
		);

		$widget->add_control( "{$action}_list",
			[
				'label'       => $list_name,
				'type'        => 'select',
				'render_type' => 'ui',
				'conditions'  => [
					'relation' => 'or',
					'terms'    => [
						[
							'name'     => "{$action}_custom_api_key",
							'operator' => '!==',
							'value'    => '',
						],
						[
							'name'     => "{$action}_api_key_source",
							'operator' => '=',
							'value'    => 'default',
						],
					],
				],
			]
		);
	}

	/**
	 * Ready made method that creates controls assocciated with field mapping for this CRM action.
	 *
	 * @param object $widget Widget instance.
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function add_field_mapping_controls( $widget ) {
		$action   = $this->get_name();
		$repeater = new Repeater();

		$repeater->add_control( 'remote_field',
			[
				'label'       => $this->get_title() . ' ' . esc_html__( 'Field', 'sellkit' ),
				'type'        => 'select',
				'render_type' => 'ui',
				'label_block' => false,
				'options'     => array_merge(
					$this->get_default_remote_fields()['required'],
					$this->get_default_remote_fields()['optional']
				),
			]
		);

		$repeater->add_control( 'local_field',
			[
				'label'       => esc_html__( 'Form Field', 'sellkit' ),
				'type'        => 'select',
				'render_type' => 'ui',
			]
		);

		$defaults = [];
		foreach ( $this->get_default_remote_fields()['required'] as $key => $value ) {
			$defaults[] = [
				'remote_field' => $key,
				'is_required'  => true,
			];
		}

		$widget->add_control( "{$action}_fields_mapping",
			[
				'label'       => esc_html__( 'Field Mapping', 'sellkit' ),
				'type'        => 'repeater',
				'separator'   => 'before',
				'fields'      => $repeater->get_controls(),
				'default'     => $defaults,
				'conditions'  => [
					'terms' => [
						[
							'name' => "{$action}_list",
							'operator' => '!in',
							'value' => [ 'none', 'fetching', 'noList' ],
						],
					],
				],
			]
		);
	}

	/**
	 * Ready made method that creates the control assocciated with tags for this CRM action.
	 *
	 * @param object $widget Widget instance.
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function add_tag_control( $widget ) {
		$action = $this->get_name();

		$label = esc_html__( 'Tags', 'sellkit' );

		if ( 'mailchimp' === $action ) {
			/* translators: %s: html tag */
			$label = sprintf( esc_html__( 'Tags %s', 'sellkit' ), '<span style="color:#ff0000">*</span>' );
		}

		$widget->add_control( "{$action}_tags",
			[
				'label'       => $label,
				'type'        => 'select2',
				'multiple'    => true,
				'label_block' => true,
				'render_type' => 'ui',
				'conditions'  => [
					'terms' => [
						[
							'name' => "{$action}_list",
							'operator' => '!in',
							'value' => [ 'none', 'fetching', 'noList' ],
						],
					],
				],
			]
		);
	}

	/**
	 * Creates and returns a text that notifies the user that a field should be made required.
	 *
	 * @param string $field_label Label of the field.
	 * @return string
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function get_make_require_notice( $field_label ) {
		return sprintf(
			/* translators: 1: Action name 2: Field name */
			esc_attr__( '%1$s: %2$s is required by api endpoint, but the corresponding field is not made required in your form.', 'sellkit' ),
			$this->get_title(),
			$field_label
		);
	}

	/**
	 * Creates and returns a text that notifies the user that a remote-required field is mapped to none of local form fields.
	 *
	 * @param string $field_label Label of the field.
	 * @return string
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function get_absent_require_notice( $field_label ) {
		return sprintf(
			/* translators: 1: Action name 2: Field name */
			esc_attr__( '%1$s: %2$s is required by api endpoint, but it is not mapped to any field in your form.', 'sellkit' ),
			$this->get_title(),
			$field_label
		);
	}

	/**
	 * Creates and returns a text that notifies the user that not a valid list is selected fo this CRM action.
	 *
	 * @param string $list_name Label of the list (Account, Form, Campaign, etc.).
	 * @return string
	 *
	 * @access protected
	 * @since 1.5.0
	 */
	protected function get_invalid_list_message( $list_name ) {
		return sprintf(
			/* translators: 1: Action name 2: List name */
			esc_html__( '%1$s: Invalid %2$s is selected.', 'sellkit' ),
			$this->get_title(),
			$list_name
		);
	}
}
