<?php
/**
 * Class Noesia Cs Analytics Service.
 *
 * @package Noesia_Commerce_Suite
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class implementation.
 */
class Noesia_CS_Analytics_Service {
	/**
	 * Runtime dependency or state value.
	 *
	 * @var mixed
	 */
	private $warranty_service;
	/**
	 * Runtime dependency or state value.
	 *
	 * @var mixed
	 */
	private $order_repository;

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $warranty_service Warranty service.
	 * @param mixed $order_repository Order repository.
	 */
	public function __construct( Noesia_CS_Warranty_Service $warranty_service = null, Noesia_CS_Order_Repository $order_repository = null ) {
		$this->warranty_service = $warranty_service;
		$this->order_repository = $order_repository ? $order_repository : new Noesia_CS_Order_Repository();
	}

	/**
	 * Handles the related plugin operation.
	 */
	public function get_dashboard_stats() {
		$range_data  = $this->get_requested_range();
		$empty       = $this->empty_stats( $range_data );

		if ( ! $this->order_repository->is_available() ) {
			return $empty;
		}

		$cache_key  = 'noesia_cs_dashboard_stats_v3_' . md5( wp_json_encode( $range_data ) );
		$cached     = get_transient( $cache_key );
		if ( is_array( $cached ) ) {
			return wp_parse_args( $cached, $empty );
		}

		$stats   = $empty;
		$orders  = $this->order_repository->get_orders_between( $range_data['date_after'], $range_data['date_before'], 1000 );

		$product_revenue   = array();
		$product_quantity  = array();
		$category_totals   = array();
		$city_totals       = array();
		$customer_totals   = array();
		$payment_totals    = array();
		$series            = $this->empty_revenue_series( $range_data );

		foreach ( $orders as $order ) {
			if ( ! is_a( $order, 'WC_Order' ) ) {
				continue;
			}

			$order_total  = (float) $order->get_total();
			$status       = (string) $order->get_status();
			$created      = $order->get_date_created();

			if ( ! isset( $stats['orders_by_status'][ $status ] ) ) {
				$stats['orders_by_status'][ $status ] = 0;
			}

			$stats['orders_by_status'][ $status ]++;
			$stats['total_orders']++;
			$stats['total_revenue'] += $order_total;

			if ( $order->get_meta( Noesia_CS_Passport_Service::META_TOKEN_HASH, true ) ) {
				$stats['passports']++;
				$stats['care_links']++;
			}

			if ( $this->warranty_service && $this->warranty_service->order_has_active_warranty( $order ) ) {
				$stats['active_warranties']++;
			}

			if ( $created ) {
				$key = $created->date_i18n( $this->series_date_format( $range_data ) );
				if ( isset( $series[ $key ] ) ) {
					$series[ $key ] += $order_total;
				}
			}

			$payment_key = sanitize_key( $order->get_payment_method() );
			if ( ! $payment_key ) {
				$payment_key = 'unknown';
			}
			$payment_label = sanitize_text_field( $order->get_payment_method_title() );
			if ( ! $payment_label ) {
				$payment_label = 'unknown' === $payment_key ? __( 'Unknown payment method', 'noesia-commerce-suite' ) : $payment_key;
			}
			if ( ! isset( $payment_totals[ $payment_key ] ) ) {
				$payment_totals[ $payment_key ] = array(
					'label'             => $payment_label,
					'orders'            => 0,
					'revenue'           => 0,
					'aov'               => 0,
					'cancelled_failed'  => 0,
					'products'          => array(),
				);
			}
			$payment_totals[ $payment_key ]['orders']++;
			$payment_totals[ $payment_key ]['revenue'] += $order_total;
			if ( in_array( $status, array( 'cancelled', 'failed' ), true ) ) {
				$payment_totals[ $payment_key ]['cancelled_failed']++;
			}

			$area = $this->get_order_area_label( $order );
			if ( $area ) {
				if ( ! isset( $city_totals[ $area ] ) ) {
					$city_totals[ $area ] = array(
						'orders'   => 0,
						'revenue'  => 0,
					);
				}
				$city_totals[ $area ]['orders']++;
				$city_totals[ $area ]['revenue'] += $order_total;
			}

			$customer_key = $this->get_customer_key( $order );
			if ( $customer_key ) {
				if ( ! isset( $customer_totals[ $customer_key['key'] ] ) ) {
					$customer_totals[ $customer_key['key'] ] = array(
						'label'    => $customer_key['label'],
						'orders'   => 0,
						'revenue'  => 0,
					);
				}
				$customer_totals[ $customer_key['key'] ]['orders']++;
				$customer_totals[ $customer_key['key'] ]['revenue'] += $order_total;
			}

			foreach ( $order->get_items() as $item ) {
				$product_name = sanitize_text_field( $item->get_name() );
				if ( ! $product_name ) {
					$product_name = __( 'Product', 'noesia-commerce-suite' );
				}

				if ( ! isset( $product_revenue[ $product_name ] ) ) {
					$product_revenue[ $product_name ] = array(
						'quantity'  => 0,
						'revenue'   => 0,
					);
				}
				if ( ! isset( $product_quantity[ $product_name ] ) ) {
					$product_quantity[ $product_name ] = array(
						'quantity'  => 0,
						'revenue'   => 0,
					);
				}
				if ( ! isset( $payment_totals[ $payment_key ]['products'][ $product_name ] ) ) {
					$payment_totals[ $payment_key ]['products'][ $product_name ] = 0;
				}

				$quantity  = absint( $item->get_quantity() );
				$revenue   = (float) $item->get_total();
				$product_revenue[ $product_name ]['quantity']  += $quantity;
				$product_revenue[ $product_name ]['revenue']   += $revenue;
				$product_quantity[ $product_name ]['quantity'] += $quantity;
				$product_quantity[ $product_name ]['revenue']  += $revenue;
				$payment_totals[ $payment_key ]['products'][ $product_name ] += $quantity;

				$product = method_exists( $item, 'get_product' ) ? $item->get_product() : null;
				if ( $product && method_exists( $product, 'get_category_ids' ) ) {
					foreach ( $product->get_category_ids() as $category_id ) {
						$term = get_term( absint( $category_id ), 'product_cat' );
						if ( ! $term || is_wp_error( $term ) ) {
							continue;
						}
						$name = sanitize_text_field( $term->name );
						if ( ! isset( $category_totals[ $name ] ) ) {
							$category_totals[ $name ] = array(
								'quantity'  => 0,
								'revenue'   => 0,
							);
						}
						$category_totals[ $name ]['quantity'] += $quantity;
						$category_totals[ $name ]['revenue']  += $revenue;
					}
				}
			}
		}

		foreach ( $payment_totals as $key => $payment ) {
			$payment_totals[ $key ]['aov'] = ! empty( $payment['orders'] ) ? (float) $payment['revenue'] / max( 1, absint( $payment['orders'] ) ) : 0;
			arsort( $payment_totals[ $key ]['products'] );
			$payment_totals[ $key ]['top_products'] = array_slice( array_keys( $payment_totals[ $key ]['products'] ), 0, 3 );
			unset( $payment_totals[ $key ]['products'] );
		}

		$stats['orders_checked']               = count( $orders );
		$stats['aov']                          = $stats['total_orders'] > 0 ? $stats['total_revenue'] / $stats['total_orders'] : 0;
		$stats['top_products']                 = $this->sort_limited( $product_quantity, 'quantity', 5 );
		$stats['top_products_by_quantity']     = $this->sort_limited( $product_quantity, 'quantity', 8 );
		$stats['top_products_by_revenue']      = $this->sort_limited( $product_revenue, 'revenue', 8 );
		$stats['top_categories']               = $this->sort_limited( $category_totals, 'revenue', 8 );
		$stats['cities']                       = $this->sort_limited( $city_totals, 'orders', 6 );
		$stats['top_cities_by_orders']         = $this->sort_limited( $city_totals, 'orders', 8 );
		$stats['top_cities_by_revenue']        = $this->sort_limited( $city_totals, 'revenue', 8 );
		$stats['top_customers_by_orders']      = $this->sort_limited( $customer_totals, 'orders', 8 );
		$stats['top_customers_by_spend']       = $this->sort_limited( $customer_totals, 'revenue', 8 );
		$stats['payment_methods_by_revenue']   = $this->sort_limited( $payment_totals, 'revenue', 8 );
		$stats['payment_methods_by_orders']    = $this->sort_limited( $payment_totals, 'orders', 8 );
		$stats['payment_cancelled_or_failed']  = $this->sort_limited( $payment_totals, 'cancelled_failed', 8 );
		$stats['revenue_series']               = $series;

		set_transient( $cache_key, $stats, max( 5, absint( Noesia_CS_Settings::get( 'analytics_cache_minutes', 30 ) ) ) * MINUTE_IN_SECONDS );

		return $stats;
	}


	/**
	 * Handles the related plugin operation.
	 */
	public function get_dashboard_product_range() {
		if ( ! $this->has_valid_dashboard_range_request() || ! isset( $_GET['noesia_cs_product_range'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified by has_valid_dashboard_range_request().
			return 'all';
		}

		$range = sanitize_key( wp_unslash( $_GET['noesia_cs_product_range'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified by has_valid_dashboard_range_request().

		return in_array( $range, array( 'all', 'last_month', 'last_24h' ), true ) ? $range : 'all';
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $range Range.
	 */
	public function get_dashboard_top_products( $range = '' ) {
		$range = $range ? sanitize_key( $range ) : $this->get_dashboard_product_range();
		if ( ! in_array( $range, array( 'all', 'last_month', 'last_24h' ), true ) ) {
			$range = 'all';
		}

		$cache_key  = 'noesia_cs_dashboard_top_products_' . $range;
		$cached     = get_transient( $cache_key );
		if ( is_array( $cached ) ) {
			return $cached;
		}

		$items = $this->get_top_products_from_lookup_tables( $range );
		if ( empty( $items ) ) {
			$items = $this->get_top_products_from_orders( $range );
		}

		set_transient( $cache_key, $items, max( 5, absint( Noesia_CS_Settings::get( 'analytics_cache_minutes', 30 ) ) ) * MINUTE_IN_SECONDS );
		return $items;
	}


	/**
	 * Handles the related plugin operation.
	 */
	public function get_customer_retention_summary() {
		$range_data = $this->get_requested_range();
		$empty      = array(
			'available'                    => false,
			'source'                       => 'unavailable',
			'unique_customers'             => 0,
			'new_customers'                => 0,
			'returning_customers'          => 0,
			'repeat_customers'             => 0,
			'repeat_purchase_rate'         => 0,
			'returning_customer_rate'      => 0,
			'average_orders_per_customer'  => 0,
			'range_orders'                 => 0,
			'reorder_candidates'           => 0,
			'range_label'                  => $range_data['label'],
			'note'                         => __( 'Customer lookup data is not available yet.', 'noesia-commerce-suite' ),
		);

		if ( ! $this->order_repository->is_available() ) {
			return $empty;
		}

		$cache_key  = 'noesia_cs_customer_retention_v1_' . md5( wp_json_encode( $range_data ) );
		$cached     = get_transient( $cache_key );
		if ( is_array( $cached ) ) {
			return wp_parse_args( $cached, $empty );
		}

		$summary = $this->get_customer_retention_from_lookup_tables( $range_data );
		if ( ! is_array( $summary ) ) {
			$summary = $empty;
		}

		set_transient( $cache_key, $summary, max( 5, absint( Noesia_CS_Settings::get( 'analytics_cache_minutes', 30 ) ) ) * MINUTE_IN_SECONDS );
		return $summary;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $limit Limit.
	 */
	public function get_recent_order_preview( $limit = 5 ) {
		if ( ! $this->order_repository->is_available() ) {
			return array();
		}

		$orders  = $this->order_repository->get_recent_orders( max( 1, min( 10, absint( $limit ) ) ) );
		$items   = array();

		foreach ( $orders as $order ) {
			if ( ! is_a( $order, 'WC_Order' ) ) {
				continue;
			}

			$created            = $order->get_date_created();
			$created_timestamp  = $created ? $created->getTimestamp() : 0;
			$items[] = array(
				'order_id'        => absint( $order->get_id() ),
				'order_number'    => sanitize_text_field( $order->get_order_number() ),
				'customer_label'  => $this->get_order_customer_label( $order ),
				'total'           => (float) $order->get_total(),
				'status'          => sanitize_key( $order->get_status() ),
				'status_label'    => function_exists( 'wc_get_order_status_name' ) ? wc_get_order_status_name( $order->get_status() ) : sanitize_text_field( $order->get_status() ),
				'time_ago'        => $created_timestamp ? sprintf( /* translators: %s: dynamic value. */ __( '%s ago', 'noesia-commerce-suite' ), human_time_diff( $created_timestamp, time() ) ) : __( 'Recently', 'noesia-commerce-suite' ),
				'thumbnail'       => $this->get_order_first_product_thumbnail( $order ),
				'url'             => method_exists( $order, 'get_edit_order_url' ) ? esc_url_raw( $order->get_edit_order_url() ) : '',
			);
		}

		return $items;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $range_data Range data.
	 */
	private function get_customer_retention_from_lookup_tables( $range_data ) {
		global $wpdb;

		if ( ! isset( $wpdb ) ) {
			return null;
		}

		$order_stats = $wpdb->prefix . 'wc_order_stats';
		if ( ! $this->table_exists( $order_stats ) ) {
			return null;
		}

		$valid_statuses  = array( 'wc-completed', 'wc-processing', 'wc-on-hold' );
		$date_after      = sanitize_text_field( $range_data['date_after'] );
		$date_before     = sanitize_text_field( $range_data['date_before'] );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only analytics lookup using WooCommerce reporting tables.
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				'SELECT current_customers.customer_id, current_customers.range_orders, history.first_order, history.lifetime_orders
				FROM (
					SELECT customer_id, COUNT(*) AS range_orders
					FROM %i
					WHERE customer_id > 0
					AND status IN (%s,%s,%s)
					AND date_created >= %s
					AND date_created <= %s
					GROUP BY customer_id
				) current_customers
				INNER JOIN (
					SELECT customer_id, MIN(date_created) AS first_order, COUNT(*) AS lifetime_orders
					FROM %i
					WHERE customer_id > 0
					AND status IN (%s,%s,%s)
					GROUP BY customer_id
				) history ON history.customer_id = current_customers.customer_id',
				$order_stats,
				$valid_statuses[0],
				$valid_statuses[1],
				$valid_statuses[2],
				$date_after,
				$date_before,
				$order_stats,
				$valid_statuses[0],
				$valid_statuses[1],
				$valid_statuses[2]
			),
			ARRAY_A
		);
		if ( ! is_array( $rows ) ) {
			return null;
		}

		$unique_customers     = 0;
		$new_customers        = 0;
		$returning_customers  = 0;
		$repeat_customers     = 0;
		$range_orders         = 0;
		$range_start          = sanitize_text_field( $range_data['date_after'] );

		foreach ( $rows as $row ) {
			++$unique_customers;
			$range_order_count  = absint( $row['range_orders'] ?? 0 );
			$lifetime_orders    = absint( $row['lifetime_orders'] ?? 0 );
			$first_order        = sanitize_text_field( $row['first_order'] ?? '' );
			$range_orders     += $range_order_count;

			if ( $first_order && $first_order < $range_start ) {
				++$returning_customers;
			} else {
				++$new_customers;
			}

			if ( $lifetime_orders > 1 ) {
				++$repeat_customers;
			}
		}

		return array(
			'available'                    => true,
			'source'                       => 'woocommerce_order_stats',
			'unique_customers'             => $unique_customers,
			'new_customers'                => $new_customers,
			'returning_customers'          => $returning_customers,
			'repeat_customers'             => $repeat_customers,
			'repeat_purchase_rate'         => $unique_customers ? ( $repeat_customers / $unique_customers ) * 100 : 0,
			'returning_customer_rate'      => $unique_customers ? ( $returning_customers / $unique_customers ) * 100 : 0,
			'average_orders_per_customer'  => $unique_customers ? $range_orders / $unique_customers : 0,
			'range_orders'                 => $range_orders,
			'reorder_candidates'           => 0,
			'range_label'                  => $range_data['label'],
			'note'                         => __( 'Based on WooCommerce customer/order lookup records for valid commerce orders in the selected range.', 'noesia-commerce-suite' ),
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $range Range.
	 */
	private function get_top_products_from_lookup_tables( $range ) {
		global $wpdb;
		if ( ! isset( $wpdb ) || ! function_exists( 'wc_get_product' ) ) {
			return array();
		}

		$order_product_lookup  = $wpdb->prefix . 'wc_order_product_lookup';
		$order_stats           = $wpdb->prefix . 'wc_order_stats';

		if ( ! $this->table_exists( $order_product_lookup ) || ! $this->table_exists( $order_stats ) ) {
			return array();
		}

		$dates           = $this->product_range_dates( $range );
		$valid_statuses  = array( 'wc-completed', 'wc-processing', 'wc-on-hold' );

		if ( ! empty( $dates['after'] ) && ! empty( $dates['before'] ) ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only top-products lookup using WooCommerce reporting tables.
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					'SELECT COALESCE(NULLIF(opl.variation_id, 0), opl.product_id) AS product_id, SUM(opl.product_qty) AS quantity, SUM(opl.product_net_revenue) AS revenue FROM %i opl INNER JOIN %i os ON os.order_id = opl.order_id WHERE os.status IN (%s,%s,%s) AND opl.product_id > 0 AND os.date_created >= %s AND os.date_created <= %s GROUP BY product_id ORDER BY quantity DESC, revenue DESC LIMIT %d',
					$order_product_lookup,
					$order_stats,
					$valid_statuses[0],
					$valid_statuses[1],
					$valid_statuses[2],
					sanitize_text_field( $dates['after'] ),
					sanitize_text_field( $dates['before'] ),
					10
				),
				ARRAY_A
			);
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Read-only top-products lookup using WooCommerce reporting tables.
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					'SELECT COALESCE(NULLIF(opl.variation_id, 0), opl.product_id) AS product_id, SUM(opl.product_qty) AS quantity, SUM(opl.product_net_revenue) AS revenue FROM %i opl INNER JOIN %i os ON os.order_id = opl.order_id WHERE os.status IN (%s,%s,%s) AND opl.product_id > 0 GROUP BY product_id ORDER BY quantity DESC, revenue DESC LIMIT %d',
					$order_product_lookup,
					$order_stats,
					$valid_statuses[0],
					$valid_statuses[1],
					$valid_statuses[2],
					10
				),
				ARRAY_A
			);
		}

		if ( empty( $rows ) || ! is_array( $rows ) ) {
			return array();
		}

		$items = array();
		foreach ( $rows as $row ) {
			$product_id  = absint( $row['product_id'] ?? 0 );
			$product     = $product_id ? wc_get_product( $product_id ) : null;
			if ( ! $product ) {
				continue;
			}
			$items[] = $this->format_product_preview_item(
				$product,
				absint( $row['quantity'] ?? 0 ),
				(float) ( $row['revenue'] ?? 0 )
			);
		}

		return $items;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $range Range.
	 */
	private function get_top_products_from_orders( $range ) {
		if ( ! $this->order_repository->is_available() ) {
			return array();
		}

		$dates   = $this->product_range_dates( $range );
		$orders  = $this->order_repository->get_orders_between( $dates['after'] ?? '', $dates['before'] ?? '', 1000 );
		$items   = array();

		foreach ( $orders as $order ) {
			if ( ! is_a( $order, 'WC_Order' ) || in_array( $order->get_status(), array( 'cancelled', 'failed', 'refunded', 'trash' ), true ) ) {
				continue;
			}
			foreach ( $order->get_items() as $order_item ) {
				$product     = method_exists( $order_item, 'get_product' ) ? $order_item->get_product() : null;
				$product_id  = $product ? absint( $product->get_id() ) : absint( $order_item->get_product_id() );
				if ( ! $product_id ) {
					continue;
				}
				if ( ! isset( $items[ $product_id ] ) ) {
					$items[ $product_id ] = array(
						'product'   => $product,
						'quantity'  => 0,
						'revenue'   => 0,
					);
				}
				$items[ $product_id ]['quantity'] += absint( $order_item->get_quantity() );
				$items[ $product_id ]['revenue']  += (float) $order_item->get_total();
			}
		}

		uasort(
			$items,
			function ( $a, $b ) {
				$quantity_compare = ( $b['quantity'] ?? 0 ) <=> ( $a['quantity'] ?? 0 );
				return 0 !== $quantity_compare ? $quantity_compare : ( ( $b['revenue'] ?? 0 ) <=> ( $a['revenue'] ?? 0 ) );
			}
		);

		$preview = array();
		foreach ( array_slice( $items, 0, 10, true ) as $product_id => $data ) {
			$product = $data['product'];
			if ( ! $product && function_exists( 'wc_get_product' ) ) {
				$product = wc_get_product( absint( $product_id ) );
			}
			if ( ! $product ) {
				continue;
			}
			$preview[] = $this->format_product_preview_item( $product, absint( $data['quantity'] ?? 0 ), (float) ( $data['revenue'] ?? 0 ) );
		}

		return $preview;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $product Product.
	 * @param mixed $quantity Quantity.
	 * @param mixed $revenue Revenue.
	 */
	private function format_product_preview_item( $product, $quantity, $revenue ) {
		$image_id = $product && method_exists( $product, 'get_image_id' ) ? absint( $product->get_image_id() ) : 0;
		if ( ! $image_id && $product && method_exists( $product, 'get_parent_id' ) && $product->get_parent_id() ) {
			$parent    = wc_get_product( absint( $product->get_parent_id() ) );
			$image_id  = $parent && method_exists( $parent, 'get_image_id' ) ? absint( $parent->get_image_id() ) : 0;
		}

		$product_id  = $product && method_exists( $product, 'get_id' ) ? absint( $product->get_id() ) : 0;
		$edit_url    = $product_id ? get_edit_post_link( $product_id, '' ) : '';

		return array(
			'product_id'  => $product_id,
			'label'       => $product && method_exists( $product, 'get_name' ) ? sanitize_text_field( $product->get_name() ) : __( 'Product', 'noesia-commerce-suite' ),
			'quantity'    => absint( $quantity ),
			'revenue'     => (float) $revenue,
			'thumbnail'   => $image_id ? esc_url_raw( wp_get_attachment_image_url( $image_id, 'thumbnail' ) ) : '',
			'url'         => $edit_url ? esc_url_raw( $edit_url ) : '',
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $order Order.
	 */
	private function get_order_customer_label( $order ) {
		$name = trim( (string) $order->get_formatted_billing_full_name() );
		if ( $name ) {
			return sanitize_text_field( $name );
		}
		$customer_id = absint( $order->get_customer_id() );
		return $customer_id ? sprintf( /* translators: %d: numeric value. */ __( 'Customer #%d', 'noesia-commerce-suite' ), $customer_id ) : __( 'Guest customer', 'noesia-commerce-suite' );
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $order Order.
	 */
	private function get_order_first_product_thumbnail( $order ) {
		foreach ( $order->get_items() as $order_item ) {
			$product = method_exists( $order_item, 'get_product' ) ? $order_item->get_product() : null;
			if ( ! $product ) {
				continue;
			}
			$image_id = method_exists( $product, 'get_image_id' ) ? absint( $product->get_image_id() ) : 0;
			if ( ! $image_id && method_exists( $product, 'get_parent_id' ) && $product->get_parent_id() ) {
				$parent    = wc_get_product( absint( $product->get_parent_id() ) );
				$image_id  = $parent && method_exists( $parent, 'get_image_id' ) ? absint( $parent->get_image_id() ) : 0;
			}
			if ( $image_id ) {
				return esc_url_raw( wp_get_attachment_image_url( $image_id, 'thumbnail' ) );
			}
		}
		return '';
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $range Range.
	 */
	private function product_range_dates( $range ) {
		$range  = sanitize_key( $range );
		$now    = time();
		if ( 'last_24h' === $range ) {
			return array(
				'after'   => gmdate( 'Y-m-d H:i:s', $now - DAY_IN_SECONDS ),
				'before'  => gmdate( 'Y-m-d H:i:s', $now ),
			);
		}
		if ( 'last_month' === $range ) {
			return array(
				'after'   => gmdate( 'Y-m-d H:i:s', $now - ( 30 * DAY_IN_SECONDS ) ),
				'before'  => gmdate( 'Y-m-d H:i:s', $now ),
			);
		}
		return array(
			'after'   => '',
			'before'  => '',
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $table_name Table name.
	 */
	private function table_exists( $table_name ) {
		global $wpdb;
		$table_name = sanitize_text_field( $table_name );
		return isset( $wpdb ) && $table_name && $table_name === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * Handles the related plugin operation.
	 */
	public function flush_cache() {
		global $wpdb;
		foreach ( array( 7, 30, 90, 365 ) as $range ) {
			delete_transient( 'noesia_cs_dashboard_stats_v2_' . $range );
		}
		foreach ( array( 'all', 'last_month', 'last_24h' ) as $range ) {
			delete_transient( 'noesia_cs_dashboard_top_products_' . $range );
		}
		delete_transient( 'noesia_cs_dashboard_stats_v1' );
		if ( isset( $wpdb ) ) {
			$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_noesia_cs_dashboard_stats_v3_%' OR option_name LIKE '_transient_timeout_noesia_cs_dashboard_stats_v3_%' OR option_name LIKE '_transient_noesia_cs_customer_retention_v1_%' OR option_name LIKE '_transient_timeout_noesia_cs_customer_retention_v1_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		}
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $range_data Range data.
	 */
	private function empty_stats( $range_data = null ) {
		if ( ! is_array( $range_data ) ) {
			$range_data = $this->get_requested_range();
		}

		return array(
			'total_revenue'                => 0,
			'total_orders'                 => 0,
			'aov'                          => 0,
			'passports'                    => 0,
			'active_warranties'            => 0,
			'care_links'                   => 0,
			'orders_checked'               => 0,
			'date_range_days'              => $range_data['days'],
			'range_mode'                   => $range_data['mode'],
			'range_start'                  => $range_data['start_date'],
			'range_end'                    => $range_data['end_date'],
			'range_start_ts'               => $range_data['start_ts'],
			'range_end_ts'                 => $range_data['end_ts'],
			'range_label'                  => $range_data['label'],
			'top_products'                 => array(),
			'top_products_by_quantity'     => array(),
			'top_products_by_revenue'      => array(),
			'top_categories'               => array(),
			'top_customers_by_orders'      => array(),
			'top_customers_by_spend'       => array(),
			'orders_by_status'             => array(),
			'cities'                       => array(),
			'top_cities_by_orders'         => array(),
			'top_cities_by_revenue'        => array(),
			'payment_methods_by_revenue'   => array(),
			'payment_methods_by_orders'    => array(),
			'payment_cancelled_or_failed'  => array(),
			'revenue_series'               => array(),
		);
	}

	/**
	 * Checks whether dashboard range query arguments came from a valid admin request.
	 */
	private function has_valid_dashboard_range_request() {
		if ( ! current_user_can( NOESIA_CS_CAPABILITY ) ) {
			return false;
		}

		if ( empty( $_GET['noesia_cs_dashboard_nonce'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The nonce is verified below when present.
			return false;
		}

		$nonce = sanitize_text_field( wp_unslash( $_GET['noesia_cs_dashboard_nonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The nonce is verified on the next line.

		return (bool) wp_verify_nonce( $nonce, 'noesia_cs_dashboard_range' );
	}

	/**
	 * Handles the related plugin operation.
	 */
	private function get_requested_range() {
		$default_days  = $this->get_range_days();
		$mode          = 'preset';
		$start_ts      = strtotime( gmdate( 'Y-m-d 00:00:00', time() - ( ( $default_days - 1 ) * DAY_IN_SECONDS ) ) );
		$end_ts        = strtotime( gmdate( 'Y-m-d 23:59:59' ) );

		if ( $this->has_valid_dashboard_range_request() && isset( $_GET['noesia_cs_date_mode'] ) && 'custom' === sanitize_key( wp_unslash( $_GET['noesia_cs_date_mode'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified by has_valid_dashboard_range_request().
			$raw_from = isset( $_GET['noesia_cs_date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['noesia_cs_date_from'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified by has_valid_dashboard_range_request().
			$raw_to   = isset( $_GET['noesia_cs_date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['noesia_cs_date_to'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified by has_valid_dashboard_range_request().
			$from  = $this->valid_date( $raw_from );
			$to    = $this->valid_date( $raw_to );

			if ( $from && $to ) {
				$custom_start  = strtotime( $from . ' 00:00:00' );
				$custom_end    = strtotime( $to . ' 23:59:59' );
				if ( $custom_start && $custom_end && $custom_start <= $custom_end ) {
					$mode      = 'custom';
					$start_ts  = $custom_start;
					$end_ts    = $custom_end;
				}
			}
		}

		$days = max( 1, (int) ceil( ( $end_ts - $start_ts ) / DAY_IN_SECONDS ) );

		return array(
			'mode'         => $mode,
			'days'         => $days,
			'start_ts'     => $start_ts,
			'end_ts'       => $end_ts,
			'start_date'   => gmdate( 'Y-m-d', $start_ts ),
			'end_date'     => gmdate( 'Y-m-d', $end_ts ),
			'date_after'   => gmdate( 'Y-m-d H:i:s', $start_ts ),
			'date_before'  => gmdate( 'Y-m-d H:i:s', $end_ts ),
			'label'        => date_i18n( get_option( 'date_format' ), $start_ts ) . ' — ' . date_i18n( get_option( 'date_format' ), $end_ts ),
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $date Date.
	 */
	private function valid_date( $date ) {
		if ( ! is_string( $date ) || ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) {
			return '';
		}

		$parts = array_map( 'absint', explode( '-', $date ) );
		return checkdate( $parts[1], $parts[2], $parts[0] ) ? $date : '';
	}

	/**
	 * Handles the related plugin operation.
	 */
	private function get_range_days() {
		$range = (string) Noesia_CS_Settings::get( 'analytics_default_range', '30' );
		return in_array( $range, array( '7', '30', '90', '365' ), true ) ? absint( $range ) : 30;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $range_data Range data.
	 */
	private function empty_revenue_series( $range_data ) {
		$range   = max( 1, absint( $range_data['days'] ) );
		$series  = array();
		$step    = $range > 90 ? 30 : ( $range > 30 ? 7 : 1 );
		$format  = $this->series_date_format( $range_data );
		for ( $time = $range_data['start_ts']; $time <= $range_data['end_ts']; $time += ( $step * DAY_IN_SECONDS ) ) {
			$series[ date_i18n( $format, $time ) ] = 0;
		}
		if ( empty( $series ) ) {
			$series[ date_i18n( $format, $range_data['end_ts'] ) ] = 0;
		}
		return $series;
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $range_data Range data.
	 */
	private function series_date_format( $range_data ) {
		$range = is_array( $range_data ) ? absint( $range_data['days'] ) : absint( $range_data );
		return $range > 90 ? 'M Y' : 'M j';
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $order Order.
	 */
	private function get_order_area_label( $order ) {
		$city     = trim( (string) ( $order->get_shipping_city() ? $order->get_shipping_city() : $order->get_billing_city() ) );
		$region   = trim( (string) ( $order->get_shipping_state() ? $order->get_shipping_state() : $order->get_billing_state() ) );
		$country  = trim( (string) ( $order->get_shipping_country() ? $order->get_shipping_country() : $order->get_billing_country() ) );
		$parts    = array_filter( array( $city, $region, $country ) );
		return $parts ? sanitize_text_field( implode( ', ', array_slice( $parts, 0, 2 ) ) ) : '';
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $order Order.
	 */
	private function get_customer_key( $order ) {
		$customer_id = absint( $order->get_customer_id() );
		if ( $customer_id ) {
			return array(
				'key'    => 'customer:' . $customer_id,
				'label'  => sprintf( /* translators: %d: customer ID. */ __( 'Customer #%d', 'noesia-commerce-suite' ), $customer_id ),
			);
		}

		$email = sanitize_email( $order->get_billing_email() );
		if ( ! $email ) {
			return array(
				'key'    => 'guest:unknown',
				'label'  => __( 'Guest customer', 'noesia-commerce-suite' ),
			);
		}

		$hash = substr( hash_hmac( 'sha256', strtolower( $email ), wp_salt( 'auth' ) ), 0, 6 );
		return array(
			'key'    => 'guest:' . $hash,
			'label'  => sprintf( /* translators: %s: anonymous customer code. */ __( 'Guest customer %s', 'noesia-commerce-suite' ), strtoupper( $hash ) ),
		);
	}

	/**
	 * Handles the related plugin operation.
	 *
	 * @param mixed $items Items.
	 * @param mixed $key Key.
	 * @param mixed $limit Limit.
	 */
	private function sort_limited( $items, $key, $limit ) {
		uasort(
			$items,
			function ( $a, $b ) use ( $key ) {
				return ( $b[ $key ] ?? 0 ) <=> ( $a[ $key ] ?? 0 );
			}
		);
		return array_slice( $items, 0, $limit, true );
	}
}
