<?php

declare( strict_types=1 );

namespace DocketConnector\GravityForms;

use DocketConnector\Common\Utilities\Arrays;
use DocketConnector\Common\Utilities\Strings;
use DocketConnector\Docket\InvoiceData;
use GF_Field_SingleProduct;
use GFAPI;
use GFCommon;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Abort if this file is called directly.
}

if (
	! class_exists( Invoice::class )
	&& class_exists( AbstractFeedAddOn::class )
) {
	/**
	 * The Gravity Forms Invoice add-on.
	 *
	 * @link https://docs.gravityforms.com/category/user-guides/pricing-fields/
	 * @link https://docs.gravityforms.com/setting-up-the-stripe-feeds/
	 * @link https://www.gravityforms.com/how-to-collect-credit-card-payments-using-stripe-video-tutorial/
	 * @link https://gravitywiz.com/gravity-forms-ecommerce-tutorial-the-comprehensive-guide/
	 * @link https://gravitywiz.com/gravity-forms-payment/
	 */
	class Invoice extends AbstractFeedAddOn {

		public function __construct() {
			$this->_slug  = 'docket_connector_invoice';
			$this->_title = esc_html_x(
				'Docket Connector: Invoice',
				'Gravity Forms Add-On title',
				'docket-connector'
			);

			parent::__construct();
		}

		/**
		 * @inheritDoc
		 */
		public function can_create_feed(): bool {
			return ! empty( $this->get_api_key() ) && $this->form_has_commerce();
		}

		/**
		 * @inheritDoc
		 */
		public function configure_addon_message(): string {
			if ( ! $this->form_has_commerce() ) {
				$message = _x(
					'You cannot setup a Docket invoice feed until your Form has a commerce field.',
					'Gravity Forms feed settings',
					'docket-connector'
				);

				$edit_url = $this->get_current_form_edit_link();

				if ( ! empty( $edit_url ) ) {
					$anchor_text = _x(
						'Edit this form',
						'Gravity Forms feed settings',
						'docket-connector'
					);

					$message .= sprintf( ' <a href="%s">%s</a>', esc_url( $edit_url ), $anchor_text );
				}

				return $message;
			}

			return parent::configure_addon_message();
		}

		/**
		 * @inheritDoc
		 *
		 * @return array
		 */
		public function feed_list_columns(): array {
			$columns = [];

			$columns['feedName'] = esc_html_x(
				'Feed Name',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'sales_rep' ] = esc_html_x(
				'Sales Rep',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'invoice_subtotal' ] = esc_html_x(
				'Invoice Subtotal',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'invoice_tax' ] = esc_html_x(
				'Invoice Tax',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'invoice_total' ] = esc_html_x(
				'Invoice Total',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'email' ] = esc_html_x(
				'Email',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'phone' ] = esc_html_x(
				'Phone Number',
				'Gravity Forms feed column',
				'docket-connector'
			);

			/**
			 * Avoid too many fields being displayed in mapped fields overview.
			$columns[ $this->_mapping_prefix . 'name_entity' ] = esc_html_x(
				'Customer: Business',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'name_person' ] = esc_html_x(
				'Customer: Individual',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'address_billing' ] = esc_html_x(
				'Billing Address',
				'Gravity Forms feed column',
				'docket-connector'
			);

			$columns[ $this->_mapping_prefix . 'address_service' ] = esc_html_x(
				'Service Address',
				'Gravity Forms feed column',
				'docket-connector'
			);
		    */

			return $columns;
		}

		/**
		 * Get the column's selected display value for the "Sales Rep" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_sales_rep( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'sales_rep' );
		}

		/**
		 * Get the column's selected display value for the "Invoice Subtotal" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_invoice_subtotal( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'invoice_subtotal' );
		}

		/**
		 * Get the column's selected display value for the "Invoice Tax" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_invoice_tax( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'invoice_tax' );
		}

		/**
		 * Get the column's selected display value for the "Invoice Total" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_invoice_total( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'invoice_total' );
		}

		/**
		 * Get the column's selected display value for the "Email Address" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_email( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'email' );
		}

		/**
		 * Get the column's selected display value for the "Phone Number" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_phone( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'phone' );
		}

		/**
		 * Get the column's selected display value for the "Customer: Business" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_name_entity( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'name_entity' );
		}

		/**
		 * Get the column's selected display value for the "Customer: Individual" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_name_person( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'name_person' );
		}

		/**
		 * Get the column's selected display value for the "Billing Address" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_address_billing( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'address_billing' );
		}

		/**
		 * Get the column's selected display value for the "Service Address" field.
		 *
		 * @param array $feed
		 *
		 * @return string
		 */
		public function get_column_value_address_service( array $feed ): string {
			return $this->get_column_value_from_column_slug( $feed, $this->_mapping_prefix . 'address_service' );
		}

		// @todo boolean column for "has conditions", maybe in abstract

		/**
		 * @inheritDoc
		 *
		 * @return array[]
		 */
		/*
		public function feed_settings_fields(): array {
			// Get default payment feed settings fields.
			$settings = parent::feed_settings_fields();

			// Remove 'subscription' as a supported option. Yuck, but not our fault.
			if (
				! empty( $settings[0]['fields'][1]['choices'][2]['value'] )
				&& 'subscription' === $settings[0]['fields'][1]['choices'][2]['value']
			) {
				unset( $settings[0]['fields'][1]['choices'][2] );
			}

			$settings[0]['description'] = esc_html_x(
				'Map your Gravity Forms fields to Docket invoicing fields.',
				'Gravity Forms feed intro description',
				'docket-connector'
			);

			return $settings;
		}
		*/

		/**
		 * @inheritDoc
		 *
		 * @see \GFPaymentAddOn::billing_info_fields() Notice none are 'required'.
		 *
		 * @return array[]
		 */
		public function get_custom_feed_settings_fields_to_map(): array {
			$address_fields = $this->get_address_field_choices();

			$settings = [];

			$settings[] = [
				'name'        => $this->_mapping_prefix . 'sales_rep',
				'label'       => esc_html_x(
					'Sales Rep',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'description' => esc_html_x(
					'The sales representative.',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'        => 'select',
				'choices'     => $this->get_name_field_choices(),
			];

			$settings[] = [
				'name'        => $this->_mapping_prefix . 'invoice_subtotal',
				'label'       => esc_html_x(
					'Invoice Subtotal',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'tooltip'     => esc_html_x(
					'The amount before any taxes, shipping, etc.',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'description' => esc_html_x(
					'If not set, will be assumed the same as the "Invoice Total" setting.',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'        => 'select',
				'choices'     => $this->get_amount_field_choices(),
			];

			$settings[] = [
				'name'        => $this->_mapping_prefix . 'invoice_tax',
				'label'       => esc_html_x(
					'Invoice Tax',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'description' => esc_html_x(
					'The calculated total tax amount. There is no support for multiple tax line items.',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'        => 'select',
				'choices'     => $this->get_amount_field_choices(),
			];

			$settings[] = [
				'name'        => $this->_mapping_prefix . 'invoice_total',
				'label'       => esc_html_x(
					'Invoice Total',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'tooltip'     => esc_html_x(
					'Should equal "Payment Amount" if this invoice does not allow partial payments or deposits.',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'description' => esc_html_x(
					'The remaining balance amount in the Docket invoice will be calculated as this field value minus the dynamically-detected "Payment Amount" field value.',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'        => 'select',
				'choices'     => $this->get_amount_field_choices(),
				'required'    => true,
			];

			$settings[] = [
				'name'     => $this->_mapping_prefix . 'address_billing',
				'label'    => esc_html_x(
					'Billing Address',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'tooltip'  => esc_html_x(
					'Where to address the invoice',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'     => 'select',
				'choices'  => $address_fields,
				'required' => false,
			];

			$settings[] = [
				'name'    => $this->_mapping_prefix . 'address_service',
				'label'   => esc_html_x(
					'Service Address',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'tooltip' => esc_html_x(
					'If separate from the billing address',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'    => 'select',
				'choices' => $address_fields,
			];

			$settings[] = [
				'name'    => $this->_mapping_prefix . 'email',
				'label'   => esc_html_x(
					'Email Address',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'tooltip' => esc_html_x(
					'Where to send the invoice or to match an existing customer',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'    => 'select',
				'choices' => $this->get_email_address_field_choices(),
			];

			$settings[] = [
				'name'    => $this->_mapping_prefix . 'phone',
				'label'   => esc_html_x(
					'Phone Number',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'    => 'select',
				'choices' => $this->get_phone_field_choices(),
			];

			$settings[] = [
				'name'        => 'controller_customer_names',
				'label'       => esc_html_x(
					'Customer Name Fields',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'description' => esc_html_x(
					'Your data will not be sent to Docket unless at least one of these name fields is set.',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'        => 'select',
				'choices'     => [
					''       => [
						'label' => esc_html_x(
							'— Map the Customer Name Field(s)',
							'Gravity Forms feed option',
							'docket-connector'
						),
						'value' => '',
					],
					'entity' => [
						'label' => esc_html_x(
							'Map only an entity/business name',
							'Gravity Forms feed option',
							'docket-connector'
						),
						'value' => 'entity',
					],
					'person' => [
						'label' => esc_html_x(
							"Map only an individual's name",
							'Gravity Forms feed option',
							'docket-connector'
						),
						'value' => 'person',
					],
					'both'   => [
						'label' => esc_html_x(
							'Map both',
							'Gravity Forms feed option',
							'docket-connector'
						),
						'value' => 'both',
					],
				],
				'required'    => true,
				'onchange'    => 'jQuery( this ).parents( "form" ).submit();',
			];

			$settings[] = [
				'name'       => $this->_mapping_prefix . 'name_entity',
				'dependency' => [
					'field'  => 'controller_customer_names',
					'values' => [ 'entity', 'both' ],
				],
				'label'      => esc_html_x(
					'Customer Name: Business/Entity',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'tooltip'    => esc_html_x(
					'Example: SomeThing, LLC',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'       => 'select',
				'choices'    => $this->get_name_field_choices(),
				'required'   => true,
			];

			$settings[] = [
				'name'       => $this->_mapping_prefix . 'name_person',
				'dependency' => [
					'field'  => 'controller_customer_names',
					'values' => [ 'person', 'both' ],
				],
				'label'      => esc_html_x(
					'Customer Name: Individual',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'tooltip'    => esc_html_x(
					'Example: John Smith',
					'Gravity Forms feed option',
					'docket-connector'
				),
				'type'       => 'select',
				'choices'    => $this->get_name_field_choices(),
				'required'   => true,
			];

			return $settings;
		}

		/**
		 * Get the invoice line items to send to the Docket API.
		 *
		 * @link https://docs.gravityforms.com/gform_order_summary/
		 *
		 * @see  \DocketConnector\Docket\InvoiceData::is_valid_docket_line_items_array()
		 * @see  \GFEntryDetail::lead_detail_grid() How the {pricing_fields} merge tag is generated.
		 *
		 * @param array $form
		 * @param array $entry
		 *
		 * @return array|null
		 */
		private function get_invoice_line_items_for_docket_api( array $form, array $entry ): ?array {
			$products = GFCommon::get_product_fields( $form, $entry, false, false );

			if ( empty( $products['products'] ) ) {
				return null;
			}

			$strings = new Strings();

			$items   = [];
			$counter = 0;
			$inv     = new InvoiceData();

			foreach ( $products['products'] as $field_id => $product ) {
				// Skip major unexpected.
				if (
					! is_numeric( $field_id )
					|| ! isset( $product['name'] ) // Value.
				) {
					continue;
				}

				$counter ++;
				$item['index'] = $counter;

				$price = (float) $product['price'];
				$qty   = (float) $product['quantity'];

				$item['label']    = $this->get_field_option_label_from_value( $form, $field_id, $product['name'] );
				$item['name']     = $product['name'];
				$item['rate']     = round( $price, 2 );
				$item['quantity'] = round( $qty, 2 );
				$item['amount']   = round( $qty * $price, 2 );

				// Example field description of "\n\nhi\n\nbye\n" will become "hi | bye" to send to Docket...

				// A field with a description of only "\n\n" will become an empty string "", even before the trim().
				$description = trim( wp_strip_all_tags( $this->get_field_description( $form, $field_id ) ) );

				if ( ! empty( $description ) ) {
					// Remove sequence of line breaks and replace with a single line break, also removing first and last.
					$description = trim( preg_replace( '/(\r\n)+|\r+|\n+|\t+/', PHP_EOL, $description ) );

					// Remove things like "\n" and replace with something that indicates there was separation. Docket API wants no HTML or line breaks.
					$item['description'] = $strings->stringy( $description )->removeHtmlBreak( ' | ' )->toString();
				}

				/**
				 * Identify a Tax type field from Gravity Perks eCommerce Fields so Docket knows.
				 *
				 * @see \GF_Field_Tax
				 *
				 * @link https://gravitywiz.com/documentation/gravity-forms-ecommerce-fields/
				 */
				if ( class_exists( 'GF_Field_Tax' ) ) {
					$field = GFAPI::get_field( $form, $field_id );

					if (
						$field instanceof GF_Field_SingleProduct
						&& 'tax' === $field->type
					) {
						$item['is_tax'] = '1';
					}
				}

				// @todo Remove if we won't be using.
				// if ( is_array( rgar( $product, 'options' ) ) ) {
				// @link https://share.getcloudapp.com/KoulN5AK Example when not empty.
				// }

				$random = $inv->generate_random_line_item_id();

				$items[ $random ] = $item;
			}

			return $items;
		}

		/**
		 * @inheritDoc
		 *
		 * @since 1.1.0 Unset non-array values if there's a complementary `_text` value set.
		 *
		 * @see   \DocketConnector\Docket\InvoiceData::get_invoice_array_rules() Required keys for the $api_data array.
		 */
		public function get_docket_api_call_body(
			array $mapped_field_ids,
			array $feed,
			array $entry,
			array $form
		): ?array {
			$arrays = new Arrays();

			// Major fail.
			if ( ! $arrays->is_associative_array( $mapped_field_ids ) ) {
				$this->log_debug( __METHOD__ . '(): BAILED at line# ' . __LINE__ . ' having this $mapped_field_ids: ' . print_r( $mapped_field_ids, true ) );

				return null;
			}

			// Process mapped fields.
			$also_text = [
				'sales_rep',
				'name_entity',
				'name_person',
				'address_billing',
				'address_service',
			];

			$api_data = [];

			foreach ( $mapped_field_ids as $k => $v ) {
				// Get raw version (e.g. address array).
				$value = $this->get_field_value_for_docket_api( $form, $entry, $v, true );

				if ( is_null( $value ) ) {
					continue;
				}

				$api_data[ $k ] = $value;

				// Maybe also get concatenated text version.
				if ( in_array( $k, $also_text, true ) ) {
					$value = $this->get_field_value_for_docket_api( $form, $entry, $v, false );

					$api_data[ $k . '_text' ] = $value;

					// Avoid data validation failure, such as if was mapped to non-array field (e.g. name, address).
					if ( ! is_array( $api_data[ $k ] ) ) {
						unset( $api_data[ $k ] );
					}
				}
			}

			// Bail if no data from mapping.
			if ( empty( $api_data ) ) {
				$this->log_debug( __METHOD__ . '(): BAILED at line# ' . __LINE__ . ' having this $mapped_field_ids: ' . print_r( $mapped_field_ids, true ) );

				return null;
			}

			// Set the Docket Task Flag (1/0 string).
			$api_data['task_flag_docket'] = $this->get_task_flag_docket_value( $feed );

			// Bail if not a successfully-completed one-time payment (no subscriptions, pending, failed, or refunded).
			if ( false === $this->entry_has_product_payment( $entry ) ) {
				$this->log_debug( __METHOD__ . '(): BAILED at line# ' . __LINE__ . ' because of not being a completed, successful, one-time payment.' );

				return null;
			}

			$api_data['payment_amount'] = $entry['payment_amount'];

			// Bail if no Total.
			if (
				! isset( $api_data['invoice_total'] )
				|| ! is_numeric( $api_data['invoice_total'] )
			) {
				$this->log_debug( __METHOD__ . '(): BAILED at line# ' . __LINE__ . ' having this $api_data: ' . print_r( $api_data, true ) );

				return null;
			}

			// Bail if no Line Items.
			$line_items = $this->get_invoice_line_items_for_docket_api( $form, $entry );
			if ( empty( $line_items ) ) {
				$this->log_debug( __METHOD__ . '(): BAILED at line# ' . __LINE__ . ' having this $api_data: ' . print_r( $api_data, true ) );

				return null;
			}

			$api_data['line_items'] = $line_items;

			// Process Amounts.
			if (
				! isset( $api_data['invoice_subtotal'] )
				|| ! is_numeric( $api_data['invoice_subtotal'] )
			) {
				$api_data['invoice_subtotal'] = $api_data['invoice_total'];
			}

			// @todo Do we want to allow mapping their own Balance amount field?
			$api_data['invoice_balance'] = $api_data['invoice_total'] - $api_data['payment_amount'];

			// Add Entry Currency.
			if ( ! empty( $entry['currency'] ) ) {
				$api_data['invoice_currency'] = $entry['currency'];
			}

			// Add Shipping.
			$products = GFCommon::get_product_fields( $form, $entry, false, false );

			if ( ! empty( $products['shipping'] ) ) {
				if ( ! empty( $products['shipping']['name'] ) ) {
					$api_data['shipping_description'] = $products['shipping']['name'];
				}
				if ( ! empty( $products['shipping']['price'] ) ) {
					$api_data['shipping_price'] = $products['shipping']['price'];
				}
			}

			// Add Order Date as Entry Date.
			$entry_date = $this->get_gf_entry_date_iso_8601( $entry, 'date_created' );
			if ( ! empty( $entry_date ) ) {
				$api_data['order_date'] = $entry_date;
			}

			// Add Payment Date.
			$payment_date = $this->get_gf_entry_date_iso_8601( $entry, 'payment_date' );
			if (
				! empty( $payment_date )
				&& $payment_date !== $entry_date
			) {
				$api_data['payment_date'] = $payment_date;
			}

			// Add Payment Method.
			if ( ! empty( $entry['payment_method'] ) ) {
				$api_data['payment_method'] = $entry['payment_method'];
			}

			// Add Source URL.
			$source_url = $this->get_entry_source_url( $entry );
			if ( ! empty( $source_url ) ) {
				$api_data['source_url'] = $source_url;
			}

			// @todo Maybe should detect if has payment on form and only if transactionType ['product', 'donation'] (not 'subscription')

			$docket_invoice = new InvoiceData( 'gravityforms', $api_data );

			$this->log_debug( __METHOD__ . '(): Made it this far: line# ' . __LINE__ . ' having this $api_data: ' . print_r( $api_data, true ) );

			return $docket_invoice->get_formatted_docket_invoice_data();
		}

		/**
		 * @inheritDoc
		 */
		public function get_entry_success_note( array $entry ): string {
			return esc_html_x(
				'Successfully sent data to the Docket Invoice API.',
				'Docket feed success message Entry note',
				'docket-connector'
			);
		}

	}
}