<?php
/**
 * GGM Commerce Virtual Products Class
 *
 * Helper class for handling virtual and downloadable product logic,
 * including shipping requirements and order processing.
 *
 * @package GGM_Commerce
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class GGM_Commerce_Virtual_Products
 */
class GGM_Commerce_Virtual_Products {

	/**
	 * Singleton instance.
	 *
	 * @var GGM_Commerce_Virtual_Products|null
	 */
	private static $instance = null;

	/**
	 * Get singleton instance.
	 *
	 * @return GGM_Commerce_Virtual_Products
	 */
	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor.
	 */
	private function __construct() {
		// Hook into order completion to grant download permissions.
		add_action( 'ggmc_order_status_completed', array( $this, 'on_order_completed' ) );
		add_action( 'ggmc_order_status_processing', array( $this, 'on_order_processing' ) );
	}

	/**
	 * Check if a product is virtual.
	 *
	 * @param int $product_id Product ID.
	 * @return bool Whether product is virtual.
	 */
	public function is_virtual( $product_id ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$is_virtual = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT is_virtual FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
				$product_id
			)
		);

		return (bool) $is_virtual;
	}

	/**
	 * Check if a product is downloadable.
	 *
	 * @param int $product_id Product ID.
	 * @return bool Whether product is downloadable.
	 */
	public function is_downloadable( $product_id ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$is_downloadable = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT is_downloadable FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
				$product_id
			)
		);

		return (bool) $is_downloadable;
	}

	/**
	 * Check if a product requires shipping.
	 *
	 * @param int $product_id Product ID.
	 * @return bool Whether product requires shipping.
	 */
	public function requires_shipping( $product_id ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$requires_shipping = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT requires_shipping FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
				$product_id
			)
		);

		// Default to true if column doesn't exist yet.
		if ( null === $requires_shipping ) {
			return true;
		}

		return (bool) $requires_shipping;
	}

	/**
	 * Get product shipping/virtual settings.
	 *
	 * @param int $product_id Product ID.
	 * @return array Product settings.
	 */
	public function get_product_settings( $product_id ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$product = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT requires_shipping, is_virtual, is_downloadable, download_access_mode
				FROM {$wpdb->prefix}ggmc_products WHERE id = %d",
				$product_id
			),
			ARRAY_A
		);

		if ( ! $product ) {
			return array(
				'requires_shipping'    => true,
				'is_virtual'           => false,
				'is_downloadable'      => false,
				'download_access_mode' => 'unlimited',
			);
		}

		return array(
			'requires_shipping'    => (bool) $product['requires_shipping'],
			'is_virtual'           => (bool) $product['is_virtual'],
			'is_downloadable'      => (bool) $product['is_downloadable'],
			'download_access_mode' => $product['download_access_mode'] ?: 'unlimited',
		);
	}

	/**
	 * Set product virtual/downloadable settings.
	 *
	 * @param int   $product_id Product ID.
	 * @param array $settings   Settings to update.
	 * @return bool Success or failure.
	 */
	public function set_product_settings( $product_id, $settings ) {
		global $wpdb;

		$update_data = array();
		$format = array();

		if ( isset( $settings['requires_shipping'] ) ) {
			$update_data['requires_shipping'] = $settings['requires_shipping'] ? 1 : 0;
			$format[] = '%d';
		}

		if ( isset( $settings['is_virtual'] ) ) {
			$update_data['is_virtual'] = $settings['is_virtual'] ? 1 : 0;
			$format[] = '%d';

			// If virtual, doesn't require shipping.
			if ( $settings['is_virtual'] && ! isset( $settings['requires_shipping'] ) ) {
				$update_data['requires_shipping'] = 0;
				$format[] = '%d';
			}
		}

		if ( isset( $settings['is_downloadable'] ) ) {
			$update_data['is_downloadable'] = $settings['is_downloadable'] ? 1 : 0;
			$format[] = '%d';
		}

		if ( isset( $settings['download_access_mode'] ) ) {
			$valid_modes = array( 'unlimited', 'limited', 'license' );
			$mode = in_array( $settings['download_access_mode'], $valid_modes, true )
				? $settings['download_access_mode']
				: 'unlimited';
			$update_data['download_access_mode'] = $mode;
			$format[] = '%s';
		}

		if ( empty( $update_data ) ) {
			return false;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $wpdb->update(
			"{$wpdb->prefix}ggmc_products",
			$update_data,
			array( 'id' => $product_id ),
			$format,
			array( '%d' )
		);

		return false !== $result;
	}

	/**
	 * Check if cart requires shipping.
	 *
	 * @param array $cart_items Cart items.
	 * @return bool Whether cart requires shipping.
	 */
	public function cart_requires_shipping( $cart_items ) {
		foreach ( $cart_items as $item ) {
			$product_id = isset( $item['product_id'] ) ? $item['product_id'] : 0;

			if ( $product_id && $this->requires_shipping( $product_id ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Get shippable items from cart.
	 *
	 * @param array $cart_items Cart items.
	 * @return array Shippable items only.
	 */
	public function get_shippable_items( $cart_items ) {
		$shippable = array();

		foreach ( $cart_items as $key => $item ) {
			$product_id = isset( $item['product_id'] ) ? $item['product_id'] : 0;

			if ( $product_id && $this->requires_shipping( $product_id ) ) {
				$shippable[ $key ] = $item;
			}
		}

		return $shippable;
	}

	/**
	 * Get non-shippable items from cart.
	 *
	 * @param array $cart_items Cart items.
	 * @return array Non-shippable items only.
	 */
	public function get_non_shippable_items( $cart_items ) {
		$non_shippable = array();

		foreach ( $cart_items as $key => $item ) {
			$product_id = isset( $item['product_id'] ) ? $item['product_id'] : 0;

			if ( $product_id && ! $this->requires_shipping( $product_id ) ) {
				$non_shippable[ $key ] = $item;
			}
		}

		return $non_shippable;
	}

	/**
	 * Get downloadable items from cart.
	 *
	 * @param array $cart_items Cart items.
	 * @return array Downloadable items only.
	 */
	public function get_downloadable_items( $cart_items ) {
		$downloadable = array();

		foreach ( $cart_items as $key => $item ) {
			$product_id = isset( $item['product_id'] ) ? $item['product_id'] : 0;

			if ( $product_id && $this->is_downloadable( $product_id ) ) {
				$downloadable[ $key ] = $item;
			}
		}

		return $downloadable;
	}

	/**
	 * Check if order contains only digital items.
	 *
	 * An order is considered "all digital" if every item is either:
	 * - A virtual product (requires_shipping = 0), OR
	 * - A downloadable product (is_downloadable = 1)
	 *
	 * This allows downloadable products to auto-complete even without
	 * the "Virtual product" checkbox being checked.
	 *
	 * @param int $order_id Order ID.
	 * @return bool Whether order is all digital.
	 */
	public function is_all_digital_order( $order_id ) {
		global $wpdb;

		// Check if any item requires physical shipping (requires_shipping=1 AND not downloadable).
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$physical_items = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_order_items
				WHERE order_id = %d AND requires_shipping = 1 AND is_downloadable = 0",
				$order_id
			)
		);

		// Order is all-digital if there are no physical (non-downloadable) items requiring shipping.
		return (int) $physical_items === 0;
	}

	/**
	 * Check if order contains any downloadable items.
	 *
	 * @param int $order_id Order ID.
	 * @return bool Whether order has downloadable items.
	 */
	public function has_downloadable_items( $order_id ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$has_downloadable = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_order_items
				WHERE order_id = %d AND is_downloadable = 1",
				$order_id
			)
		);

		return (int) $has_downloadable > 0;
	}

	/**
	 * Auto-complete digital orders.
	 *
	 * @param int $order_id Order ID.
	 * @return bool Whether order was auto-completed.
	 */
	public function auto_complete_digital_order( $order_id ) {
		// Only auto-complete if order is all digital.
		if ( ! $this->is_all_digital_order( $order_id ) ) {
			return false;
		}

		global $wpdb;

		// Get current order status.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$status = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT status FROM {$wpdb->prefix}ggmc_orders WHERE id = %d",
				$order_id
			)
		);

		// Only auto-complete if payment is received.
		if ( ! in_array( $status, array( 'processing', 'paid' ), true ) ) {
			return false;
		}

		// Update order status to completed.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->update(
			"{$wpdb->prefix}ggmc_orders",
			array( 'status' => 'completed' ),
			array( 'id' => $order_id ),
			array( '%s' ),
			array( '%d' )
		);

		/**
		 * Fires when an order is auto-completed.
		 *
		 * @param int $order_id Order ID.
		 */
		do_action( 'ggmc_order_auto_completed', $order_id );

		return true;
	}

	/**
	 * Handle order status change to completed.
	 *
	 * @param int $order_id Order ID.
	 */
	public function on_order_completed( $order_id ) {
		$this->grant_download_access( $order_id );
	}

	/**
	 * Handle order status change to processing.
	 *
	 * @param int $order_id Order ID.
	 */
	public function on_order_processing( $order_id ) {
		// Grant download access on processing for digital-only orders.
		if ( $this->is_all_digital_order( $order_id ) ) {
			$this->grant_download_access( $order_id );
			$this->auto_complete_digital_order( $order_id );
		}
	}

	/**
	 * Grant download access for an order.
	 *
	 * @param int $order_id Order ID.
	 * @return bool Whether access was granted.
	 */
	public function grant_download_access( $order_id ) {
		if ( ! $this->has_downloadable_items( $order_id ) ) {
			return false;
		}

		global $wpdb;

		// Get order details.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$order = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT customer_id, customer_email FROM {$wpdb->prefix}ggmc_orders WHERE id = %d",
				$order_id
			),
			ARRAY_A
		);

		if ( ! $order || empty( $order['customer_email'] ) ) {
			return false;
		}

		// Get customer email - prefer from customer record if available.
		$email = $order['customer_email'];
		if ( $order['customer_id'] ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$customer_email = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT email FROM {$wpdb->prefix}ggmc_customers WHERE id = %d",
					$order['customer_id']
				)
			);
			if ( $customer_email ) {
				$email = $customer_email;
			}
		}

		// Use downloads class to grant permissions.
		if ( class_exists( 'GGM_Commerce_Downloads' ) ) {
			$downloads = GGM_Commerce_Downloads::instance();
			$permissions = $downloads->grant_permissions( $order_id, $email, $order['customer_id'] );

			// Also assign license keys if needed.
			$this->assign_license_keys( $order_id, $email );

			return ! empty( $permissions );
		}

		return false;
	}

	/**
	 * Assign license keys for an order.
	 *
	 * @param int    $order_id Order ID.
	 * @param string $email    Customer email.
	 */
	private function assign_license_keys( $order_id, $email ) {
		global $wpdb;

		// Get downloadable order items that need license keys.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$items = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT oi.id, oi.product_id
				FROM {$wpdb->prefix}ggmc_order_items oi
				INNER JOIN {$wpdb->prefix}ggmc_products p ON oi.product_id = p.id
				WHERE oi.order_id = %d
				AND oi.is_downloadable = 1
				AND p.download_access_mode = 'license'",
				$order_id
			),
			ARRAY_A
		);

		if ( empty( $items ) ) {
			return;
		}

		if ( ! class_exists( 'GGM_Commerce_Downloads' ) ) {
			return;
		}

		$downloads = GGM_Commerce_Downloads::instance();

		foreach ( $items as $item ) {
			$downloads->assign_license( $order_id, $item['id'], $email, $item['product_id'] );
		}
	}

	/**
	 * Get order shipping/fulfillment summary.
	 *
	 * @param int $order_id Order ID.
	 * @return array Summary with shipping and digital item counts.
	 */
	public function get_order_fulfillment_summary( $order_id ) {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$summary = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT
					SUM(CASE WHEN requires_shipping = 1 THEN quantity ELSE 0 END) as shippable_qty,
					SUM(CASE WHEN requires_shipping = 0 THEN quantity ELSE 0 END) as non_shippable_qty,
					SUM(CASE WHEN is_downloadable = 1 THEN quantity ELSE 0 END) as downloadable_qty
				FROM {$wpdb->prefix}ggmc_order_items
				WHERE order_id = %d",
				$order_id
			),
			ARRAY_A
		);

		return array(
			'shippable_qty'     => (int) ( $summary['shippable_qty'] ?? 0 ),
			'non_shippable_qty' => (int) ( $summary['non_shippable_qty'] ?? 0 ),
			'downloadable_qty'  => (int) ( $summary['downloadable_qty'] ?? 0 ),
			'requires_shipping' => (int) ( $summary['shippable_qty'] ?? 0 ) > 0,
			'has_digital'       => (int) ( $summary['non_shippable_qty'] ?? 0 ) > 0,
		);
	}

	/**
	 * Mark order item shipping/download properties when saving.
	 *
	 * @param int $order_item_id Order item ID.
	 * @param int $product_id    Product ID.
	 */
	public function mark_order_item_properties( $order_item_id, $product_id ) {
		global $wpdb;

		$settings = $this->get_product_settings( $product_id );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->update(
			"{$wpdb->prefix}ggmc_order_items",
			array(
				'requires_shipping' => $settings['requires_shipping'] ? 1 : 0,
				'is_downloadable'   => $settings['is_downloadable'] ? 1 : 0,
			),
			array( 'id' => $order_item_id ),
			array( '%d', '%d' ),
			array( '%d' )
		);
	}
}
