<?php
/**
 * WP-CLI commands for Cart Recovery.
 *
 * @package     EDD\Pro\CartRecovery\CLI
 * @copyright   Copyright (c) 2026, Sandhills Development, LLC
 * @license     https://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       3.6.5
 */

namespace EDD\Pro\CartRecovery\CLI;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore

use EDD\EventManagement\SubscriberInterface;
use EDD\Pro\CartRecovery\Carts\Repository;
use EDD\CLI\Traits\TestData;

/**
 * Cart Recovery WP-CLI commands.
 *
 * @since 3.6.5
 */
class CartRecovery implements SubscriberInterface {
	use TestData;

	/**
	 * Returns an array of events that this subscriber wants to listen to.
	 *
	 * @since 3.6.5
	 * @return array
	 */
	public static function get_subscribed_events() {
		return array(
			'init' => array( 'register_cli_command', 99 ),
		);
	}

	/**
	 * Register the WP-CLI command.
	 *
	 * @since 3.6.5
	 * @return void
	 */
	public function register_cli_command() {
		if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
			return;
		}

		\WP_CLI::add_command( 'edd cart-recovery', $this );
	}

	/**
	 * Generate test abandoned carts for development purposes.
	 *
	 * ## OPTIONS
	 *
	 * [--number=<number>]
	 * : The number of abandoned carts to generate. Default: 10.
	 *
	 * [--status=<status>]
	 * : The status of the carts to generate. Valid values: tracking, abandoned, recovered, completed, expired, unsubscribed. Default: abandoned.
	 *
	 * [--with-customer]
	 * : Generate carts with existing customer IDs.
	 *
	 * [--with-email]
	 * : Generate carts with email addresses (default: true).
	 *
	 * [--min-items=<number>]
	 * : Minimum number of items per cart. Default: 1.
	 *
	 * [--max-items=<number>]
	 * : Maximum number of items per cart. Default: 3.
	 *
	 * ## EXAMPLES
	 *
	 *     # Generate 10 abandoned carts
	 *     wp edd cart-recovery generate --number=10
	 *
	 *     # Generate 50 tracking carts with customer IDs
	 *     wp edd cart-recovery generate --number=50 --status=tracking --with-customer
	 *
	 *     # Generate 25 abandoned carts with 1-5 items each
	 *     wp edd cart-recovery generate --number=25 --min-items=1 --max-items=5
	 *
	 * @since 3.6.5
	 *
	 * @param array $args       Positional arguments.
	 * @param array $assoc_args Associative arguments.
	 */
	public function generate( $args, $assoc_args ) {
		global $wpdb;

		// Parse arguments with defaults.
		$number        = isset( $assoc_args['number'] ) ? absint( $assoc_args['number'] ) : 10;
		$status        = isset( $assoc_args['status'] ) ? sanitize_text_field( $assoc_args['status'] ) : 'abandoned';
		$min_items     = isset( $assoc_args['min-items'] ) ? max( 1, absint( $assoc_args['min-items'] ) ) : 1;
		$max_items     = isset( $assoc_args['max-items'] ) ? max( 1, absint( $assoc_args['max-items'] ) ) : 3;
		$with_customer = isset( $assoc_args['with-customer'] );
		$with_email    = ! isset( $assoc_args['with-email'] ) || $assoc_args['with-email'] !== 'false';

		// Validate parameters.
		if ( $number <= 0 ) {
			\WP_CLI::error( 'The --number parameter must be a positive integer.' );
		}

		if ( $max_items < $min_items ) {
			\WP_CLI::error( 'The --max-items parameter must be greater than or equal to --min-items.' );
		}

		$valid_statuses = array( 'tracking', 'abandoned', 'recovered', 'completed', 'expired', 'unsubscribed' );
		if ( ! in_array( $status, $valid_statuses, true ) ) {
			\WP_CLI::error( sprintf( 'Invalid status. Valid values: %s', implode( ', ', $valid_statuses ) ) );
		}

		// Get available downloads.
		$downloads = get_posts(
			array(
				'post_type'      => 'download',
				'post_status'    => 'publish',
				'posts_per_page' => 100,
				'fields'         => 'ids',
			)
		);

		if ( empty( $downloads ) ) {
			\WP_CLI::error( 'No published downloads found. Please create some downloads first.' );
		}

		// Get customer IDs if requested.
		$customer_ids = array();
		if ( $with_customer ) {
			$customer_ids = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT id FROM {$wpdb->prefix}edd_customers WHERE status = %s ORDER BY RAND() LIMIT %d",
					'active',
					$number
				)
			);

			if ( empty( $customer_ids ) ) {
				\WP_CLI::warning( 'No active customers found. Generating carts without customer IDs.' );
				$with_customer = false;
			}
		}

		\WP_CLI::log( sprintf( 'Generating %d abandoned carts with status "%s"...', $number, $status ) );

		$progress   = \WP_CLI\Utils\make_progress_bar( 'Generating carts', $number );
		$repository = new Repository();
		$generated  = 0;
		$errors     = 0;

		for ( $i = 0; $i < $number; $i++ ) {
			$cart_data = $this->generate_cart_data(
				$downloads,
				$customer_ids,
				$status,
				$with_customer,
				$with_email,
				$min_items,
				$max_items
			);

			$cart_id = $repository->upsert( $cart_data );

			if ( $cart_id ) {
				++$generated;
			} else {
				++$errors;
			}

			$progress->tick();
		}

		$progress->finish();

		if ( $generated > 0 ) {
			\WP_CLI::success( sprintf( 'Successfully generated %d abandoned cart(s).', $generated ) );
		}

		if ( $errors > 0 ) {
			\WP_CLI::warning( sprintf( '%d cart(s) failed to generate.', $errors ) );
		}

		// Show summary statistics.
		$this->show_cart_summary( $status );
	}

	/**
	 * Generate cart data for a single test cart.
	 *
	 * @since 3.6.5
	 *
	 * @param array  $downloads      Available download IDs.
	 * @param array  $customer_ids   Available customer IDs.
	 * @param string $status         Cart status.
	 * @param bool   $with_customer  Whether to include customer ID.
	 * @param bool   $with_email     Whether to include email.
	 * @param int    $min_items      Minimum items per cart.
	 * @param int    $max_items      Maximum items per cart.
	 * @return array Cart data attributes.
	 */
	private function generate_cart_data( $downloads, $customer_ids, $status, $with_customer, $with_email, $min_items, $max_items ) {
		$num_items = wp_rand( $min_items, $max_items );
		$items     = array();
		$total     = 0.0;

		// Generate cart items.
		for ( $i = 0; $i < $num_items; $i++ ) {
			$download_id = $downloads[ array_rand( $downloads ) ];
			$download    = edd_get_download( $download_id );

			if ( ! $download ) {
				continue;
			}

			$quantity = wp_rand( 1, 3 );
			$price    = (float) edd_get_download_price( $download_id );
			$price_id = null;

			if ( edd_has_variable_prices( $download_id ) ) {
				$prices   = edd_get_variable_prices( $download_id );
				$price_id = array_rand( $prices );
				$price    = (float) $prices[ $price_id ]['amount'];
			}

		$item_total = $price * $quantity;
		$total     += $item_total;

		$items[] = array(
			'id'       => $download_id,
			'name'     => edd_get_download_name( $download_id, $price_id ),
			'price_id' => $price_id,
			'quantity' => $quantity,
			'price'    => $price,
		);
		}

		// Build cart contents JSON.
		$cart_contents = wp_json_encode(
			array(
				'items' => $items,
			)
		);

		// Generate session ID.
		$session_id = 'test_' . wp_generate_password( 32, false );

		// Build cart data.
		$cart_data = array(
			'session_id'    => $session_id,
			'cart_contents' => $cart_contents,
			'cart_total'    => $total,
			'currency'      => edd_get_currency(),
			'status'        => $status,
			'cart_version'  => 1,
			'ip_address'    => $this->generate_random_ip(),
			'user_agent'    => $this->generate_random_user_agent(),
			'referrer_url'  => $this->generate_random_referrer(),
			'timezone'      => wp_timezone_string(),
		);

		// Add customer ID if requested.
		if ( $with_customer && ! empty( $customer_ids ) ) {
			$customer_id = $customer_ids[ array_rand( $customer_ids ) ];
			$customer    = edd_get_customer( $customer_id );

			if ( $customer ) {
				$cart_data['customer_id'] = $customer_id;
				$cart_data['email']       = $customer->email;
			}
		}

		// Add email if requested and not already set from customer.
		if ( $with_email && empty( $cart_data['email'] ) ) {
			$cart_data['email'] = $this->generate_random_email();
		}

		// Set date_abandoned for abandoned status.
		if ( 'abandoned' === $status ) {
			$days_ago                    = wp_rand( 1, 30 );
			$cart_data['date_abandoned'] = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days_ago} days" ) );
		}

		// Set date_recovered for recovered status (was abandoned, then recovered).
		if ( 'recovered' === $status ) {
			$days_ago                    = wp_rand( 1, 30 );
			$recovered_days_ago          = wp_rand( 0, $days_ago );
			$cart_data['date_abandoned'] = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days_ago} days" ) );
			$cart_data['date_recovered'] = gmdate( 'Y-m-d H:i:s', strtotime( "-{$recovered_days_ago} days" ) );
		}

		// Set date_recovered for converted status (never abandoned, direct conversion).
		if ( 'converted' === $status ) {
			$days_ago                    = wp_rand( 0, 7 );
			$cart_data['date_recovered'] = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days_ago} days" ) );
		}

		// Add discount code occasionally.
		if ( wp_rand( 1, 100 ) <= 20 ) {
			$discounts = edd_get_discounts(
				array(
					'number' => 10,
					'status' => 'active',
				)
			);

			if ( ! empty( $discounts ) ) {
				$discount                      = $discounts[ array_rand( $discounts ) ];
				$cart_data['discount_applied'] = $discount->code;
			}
		}

		return $cart_data;
	}

	/**
	 * Show summary statistics for abandoned carts.
	 *
	 * @since 3.6.5
	 * @param string $status Optional. Filter by status.
	 * @return void
	 */
	private function show_cart_summary( $status = '' ) {
		global $wpdb;

		$where  = '';
		$params = array();

		if ( ! empty( $status ) ) {
			$where    = ' WHERE status = %s';
			$params[] = $status;
		}

		$table_name = $wpdb->prefix . 'edd_acr_carts';

		if ( ! empty( $params ) ) {
			$total = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(*) FROM {$table_name}{$where}",
					...$params
				)
			);
		} else {
			$total = $wpdb->get_var( "SELECT COUNT(*) FROM {$table_name}{$where}" );
		}

		\WP_CLI::log( sprintf( "\nTotal carts in database with status '%s': %d", $status, $total ) );
	}
}
