<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase, WordPress.Files.FileName.InvalidClassFileName
/**
 * Wholesale pricing bulk operations service.
 *
 * @package Autoship\Modules\WholesalePricing\Services
 * @since   2.13.0
 */

namespace Autoship\Modules\WholesalePricing\Services;

use Autoship\Modules\WholesalePricing\Compatibility\LegacyWholesalePricingAdapter;

/**
 * Handles bulk update, export, and admin utilities for wholesale pricing.
 *
 * Covers the CSV export, QPilot scheduled order search, admin utilities UI,
 * and the bulk update process for applying wholesale prices to scheduled orders.
 *
 * @package Autoship\Modules\WholesalePricing\Services
 * @since   2.13.0
 */
class WholesaleBulkService {

	/**
	 * The pricing service.
	 *
	 * @var WholesalePricingServiceInterface
	 */
	private WholesalePricingServiceInterface $pricing;

	/**
	 * The legacy adapter for validate calls.
	 *
	 * @var LegacyWholesalePricingAdapter
	 */
	private LegacyWholesalePricingAdapter $legacy_adapter;

	/**
	 * Constructor.
	 *
	 * @param WholesalePricingServiceInterface $pricing        The pricing service.
	 * @param LegacyWholesalePricingAdapter    $legacy_adapter The legacy adapter.
	 */
	public function __construct(
		WholesalePricingServiceInterface $pricing,
		LegacyWholesalePricingAdapter $legacy_adapter
	) {
		$this->pricing        = $pricing;
		$this->legacy_adapter = $legacy_adapter;
	}

	/**
	 * Check if the pricing of an order has changed.
	 *
	 * @param array $original The original scheduled order.
	 * @param array $news     The new scheduled order to compare against.
	 *
	 * @return bool True if different, else false.
	 */
	public function scheduled_order_items_wholesale_price_changed( $original, $news ): bool {
		$same = false;
		foreach ( $original['scheduledOrderItems'] as $key => $item ) {
			$same = $news['scheduledOrderItems'][ $key ]['salePrice'] !== $item['salePrice'];
			if ( $same ) {
				break;
			}
		}

		return $same;
	}

	/**
	 * Exports the WC Autoship Scheduled Orders to a CSV file.
	 *
	 * @param array $scheduled_order_data The original and new scheduled order data.
	 *
	 * @return int|\WP_Error The total number of lines exported, or WP_Error on failure.
	 */
	public function bulk_export_scheduled_order( $scheduled_order_data ) {
		$filename = $this->get_wholesale_pricing_updates_export_file();
		if ( empty( $filename ) ) {
			$current_server_time = date( 'Y_m_d_g_i_s_a' ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
			$new_filename        = 'autoship_bulk_scheduled_orders_wholesale_pricing_export_' . $current_server_time . '.csv';
			$fields              = $this->get_export_csv_fields();

			$filename = autoship_bulk_export_create_file( $new_filename, $fields );
		}

		$path = trailingslashit( autoship_get_export_directory() );

		try {
			$file = fopen( $path . $filename, 'a+' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen

			if ( ! $file ) {
				throw new \Exception( 'file-open-failed', 500 );
			}

			$valid_lines = 0;

			foreach ( $scheduled_order_data['original']['scheduledOrderItems'] as $key => $item ) {
				if ( ( $item['price'] === $scheduled_order_data['new']['scheduledOrderItems'][ $key ]['price'] ) && ( $item['salePrice'] === $scheduled_order_data['new']['scheduledOrderItems'][ $key ]['salePrice'] ) ) {
					continue;
				}

				$data                          = array();
				$data['ScheduledOrderId']      = $scheduled_order_data['original']['id'];
				$data['CustomerId']            = $scheduled_order_data['original']['customerId'];
				$data['Email']                 = $scheduled_order_data['original']['customer']['email'];
				$data['Status']                = $scheduled_order_data['original']['status'];
				$data['NextOccurenceDate']     = $scheduled_order_data['original']['nextOccurrenceUtc'];
				$data['FrequencyType']         = $scheduled_order_data['original']['frequencyType'];
				$data['Frequency']             = $scheduled_order_data['original']['frequency'];
				$data['FrequencyDisplayName']  = $scheduled_order_data['original']['frequencyDisplayName'];
				$data['ShippingFirstName']     = $scheduled_order_data['original']['shippingFirstName'];
				$data['ShippingLastName']      = $scheduled_order_data['original']['shippingLastName'];
				$data['OriginalExternalId']    = $scheduled_order_data['original']['originalExternalId'];
				$data['ScheduledOrderItem_id'] = $item['id'];
				$data['ProductId']             = $item['productId'];
				$data['ProductTitle']          = $item['product']['title'];
				$data['Quantity']              = $item['quantity'];
				$data['Price']                 = $item['price'];
				$data['SalePrice']             = $item['salePrice'];
				$data['NewPrice']              = $scheduled_order_data['new']['scheduledOrderItems'][ $key ]['price'];
				$data['NewSalePrice']          = $scheduled_order_data['new']['scheduledOrderItems'][ $key ]['salePrice'];

				$written = fputcsv( $file, $data );

				if ( $written ) {
					++$valid_lines;
				}
			}

			fclose( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
		} catch ( \Exception $e ) {
			// translators: %1$s is the file name, %2$s is the path to the file.
			return new \WP_Error( 'file-open-failed', sprintf( __( 'CSV File %1$s can\'t be opened! Please check rights for %2$s.', 'autoship' ), $filename, $path ) );
		}

		return $valid_lines;
	}

	/**
	 * Saves the latest export file name.
	 *
	 * Hooked to autoship_set_latest_export_file.
	 *
	 * @param string $file The latest export file name.
	 *
	 * @return bool
	 */
	public function set_wholesale_pricing_updates_export_file( $file = '' ) {
		return update_option( '__autoship_bulk_scheduled_orders_wholesale_price_file', $file );
	}

	/**
	 * Retrieves the latest export file name.
	 *
	 * @return string
	 */
	public function get_wholesale_pricing_updates_export_file(): string {
		$lof = get_option( '__autoship_bulk_scheduled_orders_wholesale_price_file', '' );

		return $lof;
	}

	/**
	 * Saves the list of Scheduled Orders updated with Wholesale Prices.
	 *
	 * @param array $orders The array of order IDs to save.
	 *
	 * @return bool True if success, else false.
	 */
	public function record_scheduled_orders_updated_with_wholesale_price( $orders = array() ) {
		return update_option( '__autoship_bulk_scheduled_orders_updated_with_wholesale_price_ext', $orders );
	}

	/**
	 * Retrieves the list of Scheduled Orders updated with Wholesale Prices.
	 *
	 * @return array
	 */
	public function get_scheduled_orders_updated_with_wholesale_price(): array {
		$ids = get_option( '__autoship_bulk_scheduled_orders_updated_with_wholesale_price_ext', array() );

		return empty( $ids ) ? array() : $ids;
	}

	/**
	 * Retrieves and applies wholesale pricing rules to a scheduled order.
	 *
	 * @param array $scheduled_order The scheduled order data.
	 *
	 * @return array The scheduled order with wholesale pricing rules applied.
	 */
	public function update_scheduled_order_with_wholesale_pricing( $scheduled_order ): array {
		$wp_user = get_user_by( 'id', $scheduled_order['customerId'] );
		$wp_user = $wp_user ? $wp_user : null;

		foreach ( $scheduled_order['scheduledOrderItems'] as $key => $value ) {
			$product = wc_get_product( $value['product']['id'] );

			if ( ! $product || empty( $product ) ) {
				continue;
			}

			$user_wholesale_role = $this->pricing->get_user_role( $wp_user );

			if ( empty( $user_wholesale_role ) ) {
				continue;
			}

			$adjusted_price = $this->pricing->adjust_price( (int) $value['product']['id'], null, null, $wp_user );

			if ( ! $this->is_non_zero_empty( $adjusted_price ) && $adjusted_price !== $value['price'] ) {
				$qty   = (int) $value['quantity'];
				$apply = $this->legacy_adapter->autoship_validate_wholesale_price( true, round( $adjusted_price * $qty, 2 ), $user_wholesale_role, $qty, $product->get_id() );
				if ( true === $apply ) {
					$scheduled_order['scheduledOrderItems'][ $key ]['salePrice'] = $adjusted_price;
				}
			}
		}

		return $scheduled_order;
	}

	/**
	 * Retrieves the download link for an export file.
	 *
	 * @param string $file The export file name.
	 *
	 * @return string The URL or empty string.
	 */
	public function get_export_file_download_link( $file ): string {
		$path = autoship_admin_settings_tab_url( 'autoship-utilities' );

		return $path . '&autoship_download_wholesale_pricing_export_file=' . $file;
	}

	/**
	 * Adds the new Wholesale Pricing Bulk Update Action.
	 *
	 * Hooked to autoship_batch_update_products_actions.
	 *
	 * @param array $actions The current bulk actions.
	 *
	 * @return array The filtered actions.
	 */
	public function add_update_scheduled_orders_wholesale_pricing_action( $actions ): array {
		$actions['autoship_bulk_update_scheduled_orders_wholesale_pricing'] = 'autoship_bulk_update_scheduled_orders_wholesale_pricing';

		return $actions;
	}

	/**
	 * Searches for scheduled orders in QPilot.
	 *
	 * @param int|null $customer_id Optional customer ID.
	 * @param int      $index       The page index. Default 1.
	 * @param array    $params      Search parameters.
	 *
	 * @return array|\WP_Error Array of scheduled orders or WP_Error.
	 */
	public function search_scheduled_orders( $customer_id = null, $index = 1, $params = array() ) {
		$params         = wp_parse_args( $params, array( 'pageSize' => 100 ) );
		$params['page'] = $index;

		$client = autoship_get_default_client();

		try {
			$orders = $client->get_orders( $customer_id, $params );
		} catch ( \Exception $e ) {
			$notice = autoship_expand_http_code( $e->getCode() );

			if ( 404 === $e->getCode() ) {
				$orders  = new \WP_Error( 'Order(s) Not Found', __( 'Orders could not be found in QPilot', 'autoship' ) );
				$message = isset( $customer_id ) ? sprintf( '%d Order(s) Not Found for Customer %d. Additional Details: %s', $e->getCode(), $customer_id, $e->getMessage() ) : sprintf( '%d Order(s) Not Found matching the search criteria. Additional Details: %s', $e->getCode(), $e->getMessage() );
				autoship_log_entry( __( 'Autoship Orders', 'autoship' ), $message );
			} else {
				$orders  = new \WP_Error( 'Order Retrieval Failed', __( $notice['desc'], 'autoship' ) ); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
				$message = isset( $customer_id ) ? sprintf( '%d Order Retrieval Failed for Customer %d. Additional Details: %s', $e->getCode(), $customer_id, $e->getMessage() ) : sprintf( '%d Order Retrieval Failed for the search criteria. Additional Details: %s', $e->getCode(), $e->getMessage() );
				autoship_log_entry( __( 'Autoship Orders', 'autoship' ), $message );
			}
		}

		if ( is_wp_error( $orders ) ) {
			return $orders;
		}

		return $orders;
	}

	/**
	 * Add section to Utilities settings page.
	 *
	 * Hooked to autoship_after_autoship_admin_utilities.
	 *
	 * @param array  $query_ids The query IDs.
	 * @param string $notice    The notice to display.
	 *
	 * @return void
	 */
	public function wholesale_bulk_admin_utilities_html( $query_ids, $notice ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
		$search_args = apply_filters(
			'autoship_bulk_update_scheduled_orders_wholesale_pricing_args',
			array(
				'pageSize'    => 1,
				'statusNames' => array(
					'Paused',
					'Active',
				),
			),
			array()
		);

		$this->record_scheduled_orders_updated_with_wholesale_price();

		$file = $this->get_wholesale_pricing_updates_export_file();

		if ( ! empty( $file ) ) {
			$download_link = $this->get_export_file_download_link( $file );
		}

		?>

		<div class="autoship-bulk-action" id="autoship-bulk-update-scheduled-order-wholesale-pricing">

			<h2><?php echo esc_html( __( 'Bulk Scheduled Order Wholesale Pricing Update', 'autoship' ) ); ?></h2>
			<p>
				<?php
				// translators: %s is the list of statuses that will be updated.
				echo esc_html( sprintf( __( 'Batch updates all Scheduled Orders ( Statuses Included: %s ) using the Wholesale prices. The Batch Size (default is "10") determines the number of products to update in a single round. Decreasing the Batch Size helps prevent timeout issues for sites with many WooCommerce Products.', 'autoship' ), implode( ', ', $search_args['statusNames'] ) ) );
				?>
			</p>
			<ul>
				<li style="padding-left: 10px;">
					<p><?php echo wp_kses_post( __( '- Use the <strong>"Export Changes Only"</strong> option to export a preview of all Scheduled Orders which will be changed when Wholesale prices are re-applied.  Changes will be exported in a csv file and will NOT be applied to any Scheduled Orders.', 'autoship' ) ); ?></p>
				</li>
				<?php
				if ( apply_filters( 'autoship_bulk_update_scheduled_orders_wholesale_pricing_enable_apply_only', false ) ) {
					?>

					<li style="padding-left: 10px;">
						<p><?php echo wp_kses_post( __( '- Use the <strong>"Apply Changes Only"</strong> option to update all Scheduled Orders in QPilot by re-applying the current Wholesale price rules and do not export all affected Scheduled Orders to a csv file.', 'autoship' ) ); ?></p>
					</li>

				<?php } ?>

				<li style="padding-left: 10px;">
					<p><?php echo wp_kses_post( __( '- Use the <strong>"Apply & Export Changes"</strong> option to update all Scheduled Orders in QPilot by re-applying the current Wholesale price rules and in addition export all affected Scheduled Orders to a csv file so that changes can be reviewed after being applied.', 'autoship' ) ); ?></p>
				</li>

			</ul>

			<?php if ( ! empty( $file ) ) { ?>

				<hr/>

				<p id="wholesale-pricing-bulk-notice"><?php echo wp_kses_post( __( '<b>NOTE:</b> Download the last bulk wholesale pricing changes export file ', 'autoship' ) ); ?>
					<a href="<?php echo esc_url( $download_link ); ?>"
						target="_blank"><?php echo esc_html( __( 'Here', 'autoship' ) ); ?></a>. <?php echo esc_html( __( 'Wholesale pricing export files are automatically saved to a autoship-cloud-exports folder in the WP Uploads directory.' ) ); ?>
				</p>
				<hr/>
			<?php } ?>

			<?php
			$result = $this->search_scheduled_orders( null, 1, $search_args );

			$notice = $result->totalCount ? // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
				// translators: %1$d is the total count of Scheduled Orders, %2$s is the list of statuses that will be updated.
				__( 'A total of a %1$d %2$s Scheduled Orders can be processed.', 'autoship' ) :
				__( 'There are no available Scheduled Orders to process.', 'autoship' );

			$notice = sprintf( $notice, $result->totalCount, implode( ', ', $search_args['statusNames'] ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

			?>

			<h4 class="autoship-bulk-notice"><?php echo esc_html( $notice ); ?></h4>
			<h5 class="autoship-bulk-subnotice"></h5>

			<p class="form-field inline-field ">
				<label for="batch_size">Batch Size:</label>
				<input type="number" class="small-text" name="batch_size" value="10" placeholder="10" step="1" min="0"/>
			</p>

			<p class="form-field inline-field">
				<label for="wsp-yes-batch-preview"><input id="wsp-yes-batch-preview" type="radio" name="export_option"
															value="export" checked="checked"/>
					<?php echo esc_html( __( 'Export Changes Only', 'autoship' ) ); ?></label>
			</p>

			<?php
			if ( apply_filters( 'autoship_bulk_update_scheduled_orders_wholesale_pricing_enable_apply_only', false ) ) {
				?>

				<p class="form-field inline-field">
					<label for="wsp-yes-batch-apply"><input id="wsp-yes-batch-apply" type="radio" name="export_option"
															value="apply"/>
						<?php echo esc_html( __( 'Apply Changes Only', 'autoship' ) ); ?></label>
				</p>

			<?php } ?>

			<p class="form-field inline-field">
				<label for="wsp-yes-batch-all"><input id="wsp-yes-batch-all" type="radio" name="export_option"
														value="applyexport"/>
					<?php echo esc_html( __( 'Apply & Export Changes', 'autoship' ) ); ?></label>
			</p>

			<div style="display:none;" class="autoship-meter">
				<span style="width:10%"></span>
			</div>

			<p>
				<button class="button-primary autoship-action autoship-ajax-button">
					<span><?php echo esc_html( __( 'Update Scheduled Orders', 'autoship' ) ); ?></span></button>
			</p>

			<p>
				<button class="button-secondary autoship-cancel-action autoship-ajax-cancel-button">
					<span><?php echo esc_html( __( 'Cancel Update', 'autoship' ) ); ?></span></button>
			</p>

			<input type="hidden" class="total-toggle-counters" name="total_count" value="<?php echo esc_html( $result->totalCount ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase ?>">
			<input type="hidden" name="current_count" value="0">
			<input type="hidden" name="current_page" value="1">
			<input type="hidden" name="autoship-action" value="autoship_batch_update_products">
			<input type="hidden" name="batch_action" value="autoship_bulk_update_scheduled_orders_wholesale_pricing">
		</div>
		<?php
	}

	/**
	 * Gathers the additional posted values for the batch update.
	 *
	 * Hooked to autoship_batch_update_products_autoship_bulk_update_scheduled_orders_wholesale_pricing_args.
	 *
	 * @param array $args The arguments to update.
	 *
	 * @return array The updated arguments.
	 */
	public function batch_update_scheduled_orders_wholesale_pricing_added_args( $args ): array {
		$args['export_option'] = ! isset( $_POST['export_option'] ) || empty( $_POST['export_option'] ) ? 'export' : $_POST['export_option']; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

		return $args;
	}

	/**
	 * Get the CSV export field headers.
	 *
	 * @return array
	 */
	private function get_export_csv_fields(): array {
		return array(
			'ScheduledOrderId',
			'CustomerId',
			'Email',
			'Status',
			'NextOccurenceDate',
			'FrequencyType',
			'Frequency',
			'FrequencyDisplayName',
			'ShippingFirstName',
			'ShippingLastName',
			'OriginalExternalId',
			'ScheduledOrderItem_id',
			'ProductId',
			'ProductTitle',
			'Quantity',
			'Price',
			'SalePrice',
			'NewPrice',
			'NewSalePrice',
		);
	}

	/**
	 * Checks for empty value (checks empty, not 0 strings and NULL).
	 *
	 * @param mixed $val The value to check.
	 *
	 * @return bool True if empty or not set, else false.
	 */
	private function is_non_zero_empty( $val ): bool {
		return ! isset( $val ) || ( empty( $val ) && 0 !== $val );
	}
}
