<?php
/**
 * Warranty data access.
 *
 * @package WarrantyCart
 */

namespace WarrantyCart\Core;

use Automattic\WooCommerce\Enums\OrderInternalStatus;
use WC_Order;
use WC_Order_Item_Product;
use WC_Product;

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

/**
 * Every query this plugin makes about warranties, warranty products and the
 * orders carrying them.
 *
 * Was `WarrantyCart\Admin\Reports`, which was wrong twice over. It is not
 * reports — reporting is four of its eighteen methods, and the rest are the
 * product and order lookups the whole plugin is built on. And it is not admin:
 * the storefront shortcode calls it on every page view, as do the cart and
 * REST layers, so a class the frontend depends on was sitting in the admin
 * namespace.
 *
 * The CSV export methods at the end genuinely are reporting and belong
 * elsewhere; moving them is a separate change from moving the class.
 */
class Repository {

	/**
	 * Constructor
	 */
	public function __construct() {
		// Hook into WooCommerce product query to handle warranty meta queries
		add_filter( 'woocommerce_product_data_store_cpt_get_products_query', array( $this, 'modify_warranty_product_query' ), 10, 2 );
	}

	/**
	 * Modify warranty product query
	 *
	 * @param array $query      WP_Query arguments.
	 * @param array $query_vars Query variables.
	 *
	 * @return array Modified query arguments.
	 */
	public function modify_warranty_product_query( $query, $query_vars ) {
		// Check if this is a warranty query
		if ( isset( $query_vars['_warranty_query'] ) && $query_vars['_warranty_query'] ) {
			/*
			 * A product counts as a warranty under either spelling.
			 *
			 * This required `_warranty_product_type` — the key only the CSV
			 * importer ever wrote. A warranty created through this plugin's own
			 * admin gets `_warranty_cart_product_type` and nothing else, so it
			 * matched nothing here and was invisible to the storefront
			 * selector, which is the one place it exists to appear.
			 */
			$meta_query = array(
				'relation' => 'AND',
				array(
					'relation' => 'OR',
					array(
						'key'     => '_warranty_cart_product_type',
						'compare' => 'EXISTS',
					),
					array(
						'key'     => '_warranty_product_type',
						'compare' => 'EXISTS',
					),
				),
			);

			/*
			 * Every condition the caller asked for, not just one of them.
			 *
			 * This forwarded conditions whose key was exactly
			 * `_warranty_product_type` and dropped the rest without a word.
			 * The storefront selector asks for `_warranty_cart_product_status`
			 * = enabled and `_warranty_cart_product_type` IN (…); both were
			 * discarded, so switching a warranty to Disabled in the admin left
			 * it on sale.
			 */
			if ( isset( $query_vars['_warranty_meta_query'] ) && \is_array( $query_vars['_warranty_meta_query'] ) ) {
				foreach ( $query_vars['_warranty_meta_query'] as $meta_condition ) {
					if ( ! isset( $meta_condition['key'] ) || ! isset( $meta_condition['value'] ) ) {
						continue;
					}

					$meta_query[] = $this->normalize_meta_condition( $meta_condition );
				}
			}

			$query['meta_query'] = $meta_query;
		}

		return $query;
	}

	/**
	 * Make one caller condition match products written by either code path.
	 *
	 * @param array<string, mixed> $condition A meta_query condition.
	 *
	 * @return array<string, mixed> The condition, widened where needed.
	 */
	private function normalize_meta_condition( array $condition ): array {
		$key = $condition['key'];

		// The type key has a legacy spelling; match a product carrying either.
		if ( '_warranty_cart_product_type' === $key ) {
			$legacy        = $condition;
			$legacy['key'] = '_warranty_product_type';

			return array(
				'relation' => 'OR',
				$condition,
				$legacy,
			);
		}

		/*
		 * Products imported before the status key was written have no status
		 * at all. Requiring it outright would drop them from the storefront on
		 * upgrade — an unset status means "not disabled", not "not a warranty".
		 */
		if ( '_warranty_cart_product_status' === $key ) {
			return array(
				'relation' => 'OR',
				$condition,
				array(
					'key'     => '_warranty_cart_product_status',
					'compare' => 'NOT EXISTS',
				),
			);
		}

		return $condition;
	}

	/**
	 * The avatar for whoever placed an order.
	 *
	 * Resolved from the customer's user account where there is one, falling
	 * back to the billing email — a guest order has no user, but Gravatar (and
	 * any avatar plugin filtering `get_avatar_url`) keys on the address just
	 * the same.
	 *
	 * Returns an empty string when the site has avatars switched off, so the
	 * admin falls back to tinted initials instead of a broken image.
	 *
	 * @param \WC_Order $order The order.
	 *
	 * @return string A URL, or ''.
	 */
	public static function avatar_url_for_order( \WC_Order $order ): string {
		$customer_id = $order->get_customer_id();
		$identifier  = $customer_id > 0 ? $customer_id : $order->get_billing_email();

		if ( ! $identifier ) {
			return '';
		}

		$url = \get_avatar_url( $identifier, array( 'size' => 64 ) );

		return is_string( $url ) ? $url : '';
	}

	/**
	 * Get warranty product IDs
	 *
	 * Retrieves warranty product IDs with optional filtering and arguments.
	 *
	 * @param array<string, mixed> $args Additional arguments to merge with default query.
	 *
	 * @return array<int, int> Array of warranty product IDs.
	 */
	public function get_warranty_product_ids( array $args = array() ): array {
		$default_args = array(
			'limit'      => - 1,
			'return'     => 'ids',
			'meta_query' => array(
				array(
					'key'     => '_warranty_product_type',
					'compare' => 'EXISTS',
				),
			),
		);

		// Merge arguments properly - args override defaults for scalar values
		$query_args          = $default_args;
		$warranty_meta_query = array();

		foreach ( $args as $key => $value ) {
			if ( 'meta_query' === $key ) {
				// Store meta_query for the hook to process
				$warranty_meta_query = $value;
			} else {
				// For other keys, use the provided value
				$query_args[ $key ] = $value;
			}
		}

		// Store warranty query context and meta query for the hook
		$query_args['_warranty_query']      = true;
		$query_args['_warranty_meta_query'] = $warranty_meta_query;

		/**
		 * Filter warranty product IDs query arguments
		 *
		 * @param array $query_args Query arguments for wc_get_products.
		 * @param array $args       Additional arguments passed to the method.
		 */
		$query_args = \apply_filters( 'warranty_cart_warranty_product_ids_query_args', $query_args, $args );

		$products = \wc_get_products( $query_args );

		return is_array( $products ) ? $products : array();
	}

	/**
	 * Get total warranty orders count
	 *
	 * Retrieves the total count of orders containing warranty products.
	 *
	 * @return int Total number of warranty orders.
	 */
	public function get_total_warranty_orders(): int {
		global $wpdb;

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

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$result = $wpdb->get_row( "SELECT COUNT(DISTINCT order_id) as total FROM {$table_name}" );

		return (int) ( $result->total ?? 0 );
	}

	/**
	 * Get warranty products
	 *
	 * Retrieves warranty products as objects with optional filtering and arguments.
	 *
	 * @param array<string, mixed> $args Additional arguments to merge with default query.
	 *
	 * @return array<int, WC_Product> Array of warranty product objects.
	 */
	public function get_warranty_products( array $args = array() ): array {
		$default_args = array(
			'limit'      => - 1,
			'return'     => 'objects',
			'meta_query' => array(
				array(
					'key'     => '_warranty_product_type',
					'compare' => 'EXISTS',
				),
			),
		);

		// Merge arguments properly - args override defaults for scalar values
		$query_args          = $default_args;
		$warranty_meta_query = array();
		foreach ( $args as $key => $value ) {
			if ( isset( $default_args['meta_query'] ) && 'meta_query' === $key ) {
				// Store meta_query for the hook to process
				$warranty_meta_query = $value;
			} else {
				// For other keys, use the provided value
				$query_args[ $key ] = $value;
			}
		}

		// Store warranty query context and meta query for the hook
		$query_args['_warranty_query']      = true;
		$query_args['_warranty_meta_query'] = $warranty_meta_query;

		/**
		 * Filter warranty products query arguments
		 *
		 * @param array $query_args Query arguments for wc_get_products.
		 * @param array $args       Additional arguments passed to the method.
		 */
		$query_args = \apply_filters( 'warranty_cart_warranty_products_query_args', $query_args, $args );

		$products = \wc_get_products( $query_args );

		return is_array( $products ) ? $products : array();
	}

	/**
	 * Get sales data overview
	 *
	 * Retrieves basic warranty sales overview data with optional date range filtering.
	 *
	 * @param int $date_range Date range in days (0 for all time, or positive number for days).
	 *
	 * @return array<string, mixed> Sales data including total_sales, total_orders, average_order, total_warranties, and active_warranties.
	 */
	public function get_sales_data( int $date_range = 0 ): array {
		global $wpdb;

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

		// Build date filter
		$date_filter = '';
		$date_params = array();
		if ( $date_range > 0 ) {
			$start_date  = \gmdate( 'Y-m-d H:i:s', \strtotime( "-{$date_range} days" ) );
			$date_filter = 'AND purchase_date >= %s';
			$date_params = array( $start_date );
		}

		/**
		 * Filter sales data query arguments
		 *
		 * @param string $date_filter Date filter SQL.
		 * @param array  $date_params Date filter parameters.
		 * @param int    $date_range  Date range in days.
		 */
		$date_filter = \apply_filters( 'warranty_cart_sales_data_date_filter', $date_filter, $date_params, $date_range );

		/*
		 * COUNT(DISTINCT order_id) is not decoration.
		 *
		 * The assignment below reads $sales_data->total_orders, which this
		 * SELECT did not return — so it was always null, "Total orders" always
		 * read 0, and average_order divided by it and always read 0.00. Two of
		 * the four figures on the dashboard were structurally incapable of
		 * showing anything else.
		 *
		 * DISTINCT because one order can carry several warranty lines: an order
		 * with a warranty and a service is one order, not two.
		 */

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare

		/*
		 * prepare() only when there is something to prepare.
		 *
		 * With no date range $date_filter is empty and $date_params has no
		 * entries, so this called prepare() on a query with no placeholders.
		 * WordPress answers that with a _doing_it_wrong notice — "The query
		 * argument of wpdb::prepare() must have a placeholder" — on every
		 * dashboard load that did not pass a range, which is the default.
		 * Invisible in production, and it fails the test suite outright.
		 */
		$sales_sql = "SELECT
				SUM(purchase_price) as total_sales,
				COUNT(DISTINCT order_id) as total_orders,
				COUNT(*) as total_warranties,
				SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_warranties
			FROM {$table_name}
			WHERE 1=1 " . $date_filter;

		$sales_data = $wpdb->get_row(
			$date_params ? $wpdb->prepare( $sales_sql, $date_params ) : $sales_sql
		);

		$total_sales       = (float) ( $sales_data->total_sales ?? 0 );
		$total_orders      = (int) ( $sales_data->total_orders ?? 0 );
		$total_warranties  = (int) ( $sales_data->total_warranties ?? 0 );
		$active_warranties = (int) ( $sales_data->active_warranties ?? 0 );

		$average_order = number_format( $total_orders > 0 ? $total_sales / $total_orders : 0, 2 );

		// Get order totals for detailed breakdown
		$totals_sql = "SELECT order_id, SUM(purchase_price) as total
			FROM {$table_name}
			WHERE 1=1 " . $date_filter . '
			GROUP BY order_id';

		$order_totals = $wpdb->get_results(
			$date_params ? $wpdb->prepare( $totals_sql, $date_params ) : $totals_sql,
			OBJECT_K
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared

		$order_totals_array = array();
		foreach ( $order_totals as $order_id => $data ) {
			$order_totals_array[ $order_id ] = (float) $data->total;
		}

		$sales_data = array(
			'total_sales'       => $total_sales,
			'total_orders'      => $total_orders,
			'average_order'     => $average_order,
			'order_totals'      => $order_totals_array,
			'total_warranties'  => $total_warranties,
			'active_warranties' => $active_warranties,
		);

		/**
		 * Filter sales data results
		 *
		 * @param array $sales_data Sales data array.
		 * @param int   $date_range Date range in days.
		 */
		return \apply_filters( 'warranty_cart_sales_data', $sales_data, $date_range );
	}

	/**
	 * Get popular warranties
	 *
	 * Retrieves the most popular warranty products based on sales data.
	 *
	 * @param int $limit Number of products to return.
	 *
	 * @return array<int, array<string, mixed>> Popular products data with sales counts.
	 */
	public function get_popular_warranties( int $limit = 20 ): array {
		global $wpdb;

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

		/*
		 * Grouped by product_id alone.
		 *
		 * This selected and grouped by `product_name`, which is not a column on
		 * this table and never has been — Schema::create_tables()
		 * defines product_id and nothing else identifying. MySQL rejected the
		 * whole statement, $wpdb->get_results() returned an empty array with
		 * the error suppressed, and the dashboard's "Popular Warranties" panel
		 * has therefore been permanently empty rather than visibly broken.
		 *
		 * The name comes from the product post instead, which is also the only
		 * place it can be correct after a product is renamed.
		 */
		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$query              = $wpdb->prepare(
			"SELECT
				product_id,
				COUNT(*) as sales,
				SUM(purchase_price) as revenue
			FROM {$table_name}
			GROUP BY product_id
			ORDER BY sales DESC
			LIMIT %d",
			$limit
		);
		$popular_warranties = $wpdb->get_results( $query );
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared

		$result = array();
		foreach ( $popular_warranties as $warranty ) {
			$product_id = (int) $warranty->product_id;
			$name       = \get_the_title( $product_id );

			$result[ $product_id ] = array(
				// A deleted product still has warranty rows pointing at it, and
				// an untitled row in the panel says less than the id does.
				'name'    => '' !== $name ? $name : sprintf( '#%d', $product_id ),
				'sales'   => (int) $warranty->sales,
				'revenue' => (float) $warranty->revenue,
			);
		}

		return $result;
	}

	/**
	 * Get monthly sales data
	 *
	 * Retrieves warranty sales data grouped by month for the last 12 months.
	 *
	 * @return array<string, float> Monthly sales data with totals per month.
	 */
	public function get_monthly_sales_data(): array {
		global $wpdb;

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

		// Get monthly sales data from warranty table
		$date_format = '%M, %Y';
		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$query        = $wpdb->prepare(
			"SELECT
				DATE_FORMAT(purchase_date, %s) as month,
				SUM(purchase_price) as sales
			FROM {$table_name}
			WHERE purchase_date >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
			GROUP BY DATE_FORMAT(purchase_date, %s), YEAR(purchase_date), MONTH(purchase_date)
			ORDER BY YEAR(purchase_date), MONTH(purchase_date)",
			$date_format,
			$date_format
		);
		$monthly_data = $wpdb->get_results( $query );
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared

		$result = array();
		foreach ( $monthly_data as $data ) {
			$result[ $data->month ] = (float) $data->sales;
		}

		return $result;
	}

	/**
	 * Get formatted warranty orders
	 *
	 * Retrieves orders that contain warranty products with optional filtering and formatting.
	 *
	 * @param array<string, mixed> $query_args Additional query arguments for wc_get_orders.
	 * @param bool                 $formatted  Whether to return formatted data or raw WC_Order objects.
	 *
	 * @return array<mixed> Array of formatted order data or WC_Order objects containing warranty products.
	 */
	public function get_formatted_warranty_orders( array $query_args = array(), bool $formatted = false ): array {
		$page     = (int) ( $query_args['page'] ?? 1 );
		$per_page = (int) ( $query_args['limit'] ?? -1 );

		/*
		 * Page over warranty orders, not over every order in the shop.
		 *
		 * This asked WooCommerce for a page of orders and then threw away the
		 * ones with no warranty on them. On a store where warranties are a
		 * minority of sales — which is every store selling them — a page of ten
		 * orders could contain none at all, so the table rendered empty while
		 * the pager underneath said "Showing 1 to 10 of 22". Page two was no
		 * more related to page one than to any other slice, and the total came
		 * from a second, unlimited query that loaded every order in the shop to
		 * count them, which is how the two managed to disagree.
		 *
		 * Narrowing to the warranty orders first fixes both. The set comes from
		 * the order-items tables rather than the warranties table because a
		 * warranty record is only written once an order completes, and this
		 * list shows processing and on-hold orders too — a warranty sale that
		 * has been paid for but not yet fulfilled still belongs here.
		 */
		$warranty_order_ids = $this->order_ids_containing_warranties();

		if ( empty( $warranty_order_ids ) ) {
			return $formatted ? array(
				'orders'      => array(),
				'total'       => 0,
				'page'        => $page,
				'per_page'    => $per_page,
				'total_pages' => 0,
			) : array();
		}

		$args = $this->build_order_query_args(
			$query_args,
			array(
				'orderby' => 'date',
				'order'   => 'DESC',
			)
		);

		/*
		 * `post__in`, not `include`. Neither order data store recognises
		 * `include` — HPOS maps `post__in` to the orders table's `id` column and
		 * the legacy CPT store passes it straight to WP_Query — so `include`
		 * was silently dropped and the query ran unrestricted.
		 */
		$args['post__in'] = $warranty_order_ids;

		/*
		 * `paginate` gives the matching total back with the page, so the count
		 * comes from the same query as the rows.
		 */
		if ( $formatted ) {
			$args['paginate'] = true;
			$args['limit']    = $per_page;
			$args['page']     = $page;
		}

		$result = \wc_get_orders( $args );

		$orders_query = $formatted ? ( $result->orders ?? array() ) : $result;
		$total_orders = $formatted ? (int) ( $result->total ?? 0 ) : 0;

		if ( ! is_array( $orders_query ) ) {
			$orders_query = array();
		}

		$orders = array();

		foreach ( $orders_query as $order ) {
			if ( ! $order instanceof WC_Order ) {
				continue;
			}

			$warranty_data = $this->get_order_warranty_items( $order );

			/*
			 * Still checked. An order reaches this list because the warranties
			 * table names it, and a record can outlive the product it was sold
			 * as — so the line items are the truth about what is on the order
			 * today.
			 */
			if ( 0 === $warranty_data['count'] ) {
				continue;
			}

			$warranty_items = array_map(
				static function ( $item ) {
					return array(
						'product_id'   => $item['product_id'],
						'product_name' => $item['product_name'],
						'quantity'     => $item['quantity'],
						'price'        => $item['total'],
					);
				},
				$warranty_data['items']
			);

			if ( ! $formatted ) {
				$orders[] = $order;
				continue;
			}

			$date_created = $order->get_date_created();

			$orders[] = array(
				'id'            => $order->get_id(),
				'customer_name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
				'email'         => $order->get_billing_email(),
				'avatar'        => self::avatar_url_for_order( $order ),
				'warranty_name' => implode( ', ', array_column( $warranty_items, 'product_name' ) ),
				'order_date'    => $date_created ? $date_created->date( 'Y-m-d' ) : '',
				'status'        => $order->get_status(),
				'amount'        => $order->get_total(),
				'items'         => $warranty_items,
			);
		}

		if ( ! $formatted ) {
			return $orders;
		}

		$total_pages = $per_page > 0 ? (int) ceil( $total_orders / $per_page ) : 1;

		return array(
			'orders'      => $orders,
			'total'       => $total_orders,
			'page'        => $page,
			'per_page'    => $per_page,
			'total_pages' => $total_pages,
		);
	}

	/**
	 * Every order carrying a warranty product on one of its line items.
	 *
	 * Asked of the order-items tables directly. The alternative is to load each
	 * order and walk `get_items()`, which is what the count used to do — that
	 * means instantiating every order in the shop to answer a question the
	 * index can answer, and it got slower with every sale the store made.
	 *
	 * Both tables are unchanged under HPOS, so this is safe either way.
	 *
	 * @return array<int, int> Order IDs.
	 */
	private function order_ids_containing_warranties(): array {
		global $wpdb;

		$product_ids = $this->get_warranty_product_ids();

		if ( empty( $product_ids ) ) {
			return array();
		}

		$placeholders = implode( ', ', array_fill( 0, count( $product_ids ), '%d' ) );

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT DISTINCT items.order_id
				 FROM {$wpdb->prefix}woocommerce_order_items AS items
				 INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS meta
				     ON items.order_item_id = meta.order_item_id
				 WHERE items.order_item_type = 'line_item'
				   AND meta.meta_key IN ( '_product_id', '_variation_id' )
				   AND meta.meta_value IN ( {$placeholders} )",
				$product_ids
			)
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared

		return is_array( $ids ) ? array_map( 'absint', $ids ) : array();
	}

	/**
	 * Get comprehensive reports data
	 *
	 * Retrieves warranty sales analytics and reporting data with date range filtering.
	 *
	 * @param int $date_range Date range in days (7, 30, 90, 365).
	 *
	 * @return array<string, mixed> Comprehensive reports data including sales, orders, products, and monthly data.
	 */
	public function get_comprehensive_reports( int $date_range = 30 ): array {
		// Validate date range
		if ( ! in_array( $date_range, array( 7, 30, 90, 365 ), true ) ) {
			$date_range = 30; // Default to 30 days
		}

		$cache_key   = 'warranty_comprehensive_reports_' . $date_range;
		$cached_data = \get_transient( $cache_key );

		if ( false !== $cached_data ) {
			return $cached_data;
		}

		$query_args = $this->build_order_query_with_date_range( $date_range );

		/**
		 * Filter reports query arguments
		 *
		 * @param array $query_args Query arguments.
		 * @param int   $date_range Date range in days.
		 */
		$query_args = \apply_filters( 'warranty_cart_rest_reports_query_args', $query_args, $date_range );

		$orders          = $this->get_warranty_orders( array_merge( $query_args, array( 'return' => 'objects' ) ) );
		$warranty_orders = $orders; // Already filtered to warranty orders
		$product_sales   = array();
		$monthly_data    = array();

		// Initialize monthly data
		for ( $i = $date_range / 30; $i >= 0; $i-- ) {
			$timestamp = strtotime( "-{$i} months" );
			$month_key = $timestamp ? \wp_date( 'M', $timestamp ) : '';
			if ( $month_key ) {
				$monthly_data[ $month_key ] = array(
					'sales'  => 0,
					'orders' => 0,
				);
			}
		}

		foreach ( $orders as $order ) {
			if ( ! $order instanceof WC_Order ) {
				continue;
			}

			$warranty_data = $this->get_order_warranty_items( $order );

			if ( $warranty_data['count'] > 0 ) {
				// Track product sales data
				foreach ( $warranty_data['items'] as $item ) {
					$product_id = $item['product_id'];
					if ( ! isset( $product_sales[ $product_id ] ) ) {
						$product_sales[ $product_id ] = array(
							'name'    => $item['product_name'],
							'sales'   => 0,
							'revenue' => 0,
						);
					}
					$product_sales[ $product_id ]['sales']   += $item['quantity'];
					$product_sales[ $product_id ]['revenue'] += $item['total'];
				}

				// Add to monthly data
				$date_created = $order->get_date_created();
				if ( $date_created ) {
					$order_month = $date_created->date( 'M' );
					if ( isset( $monthly_data[ $order_month ] ) ) {
						$monthly_data[ $order_month ]['sales'] += $warranty_data['total'];
						++$monthly_data[ $order_month ]['orders'];
					}
				}
			}
		}

		/*
		 * `array_column()` cannot read a WC_Order.
		 *
		 * It reaches public properties, and WC_Order keeps everything in a
		 * protected `$data` array behind getters — so this returned an empty
		 * array and `array_sum()` of it was 0. Reports has shown "Total Sales
		 * $0" on every installation since, next to a non-zero order count,
		 * which is the giveaway: one order cannot total nothing.
		 */
		$total_sales = 0.0;

		foreach ( $warranty_orders as $warranty_order ) {
			if ( $warranty_order instanceof WC_Order ) {
				$total_sales += (float) $warranty_order->get_total();
			}
		}

		$total_orders        = count( $warranty_orders );
		$average_order_value = $total_orders > 0 ? $total_sales / $total_orders : 0;

		// Sort products by revenue
		usort(
			$product_sales,
			static function ( $a, $b ) {
				return $b['revenue'] <=> $a['revenue'];
			}
		);
		$top_products = array_slice( (array) $product_sales, 0, 5 );

		// Format monthly data
		$formatted_monthly = array();
		foreach ( $monthly_data as $month => $data ) {
			$formatted_monthly[] = array(
				'month'  => $month,
				'sales'  => $data['sales'],
				'orders' => $data['orders'],
			);
		}

		/*
		 * The same array_column() mistake, counting customers.
		 *
		 * It read nothing off the WC_Order objects, so this counted the unique
		 * values of an empty array. A guest checkout has customer_id 0, which is
		 * still one distinct buyer per email — so both are counted, and a shop
		 * selling only to guests no longer reports no customers at all.
		 */
		$customer_keys = array();

		foreach ( $warranty_orders as $warranty_order ) {
			if ( ! $warranty_order instanceof WC_Order ) {
				continue;
			}

			$customer_id = $warranty_order->get_customer_id();

			$customer_keys[] = $customer_id > 0
				? 'user:' . $customer_id
				: 'guest:' . strtolower( $warranty_order->get_billing_email() );
		}

		$total_customers       = count( array_unique( array_filter( $customer_keys, static fn( $key ) => 'guest:' !== $key ) ) );
		$conversion_rate       = 0; // Would need additional data to calculate
		$top_customer_segments = $this->get_top_customer_segments( $orders );

		// Calculate growth metrics (compare with previous period)
		$previous_period_sales = $this->get_previous_period_sales( $date_range );
		$sales_growth          = $previous_period_sales > 0 ? ( ( $total_sales - $previous_period_sales ) / $previous_period_sales ) * 100 : 0;

		// Calculate warranty type distribution
		$warranty_type_distribution = $this->get_warranty_type_distribution( $orders );

		// Calculate average warranty duration
		$average_warranty_duration = $this->get_average_warranty_duration( $orders );

		$reports = array(
			'total_sales'                => $total_sales,
			'total_orders'               => $total_orders,
			'total_customers'            => $total_customers,
			'average_order_value'        => $average_order_value,
			'sales_growth'               => $sales_growth,
			'conversion_rate'            => $conversion_rate,
			'top_products'               => $top_products,
			'top_customer_segments'      => $top_customer_segments,
			'warranty_type_distribution' => $warranty_type_distribution,
			'average_warranty_duration'  => $average_warranty_duration,
			'monthly_data'               => $formatted_monthly,
			'date_range'                 => $date_range,
			'generated_at'               => time(),
		);

		/**
		 * Filter reports response data
		 *
		 * @param array $reports    Reports data.
		 * @param int   $date_range Date range in days.
		 */
		$filtered_reports = \apply_filters( 'warranty_cart_rest_reports_response', $reports, $date_range );

		// Cache for 15 minutes
		\set_transient( $cache_key, $filtered_reports, 15 * MINUTE_IN_SECONDS );

		return $filtered_reports;
	}

	/**
	 * Get single warranty order
	 *
	 * Retrieves detailed information for a specific warranty order.
	 *
	 * @param int $order_id Order ID to retrieve.
	 *
	 * @return array<string, mixed>|null Order data if it's a warranty order, null otherwise.
	 */
	public function get_single_warranty_order( int $order_id ): ?array {
		$order = \wc_get_order( $order_id );
		if ( ! $order instanceof WC_Order ) {
			return null;
		}

		$warranty_data = $this->get_order_warranty_items( $order );

		// Only return data if order contains warranty products
		if ( 0 === $warranty_data['count'] ) {
			return null;
		}

		$warranty_items = array_map(
			static function ( $item ) {
				return array(
					'product_id'   => $item['product_id'],
					'product_name' => $item['product_name'],
					'quantity'     => $item['quantity'],
					'price'        => $item['total'],
				);
			},
			$warranty_data['items']
		);

		$date_created = $order->get_date_created();

		return array(
			'id'                   => $order->get_id(),
			'customer_name'        => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
			'email'                => $order->get_billing_email(),
			'avatar'               => self::avatar_url_for_order( $order ),
			'warranty_name'        => implode( ', ', array_column( $warranty_items, 'product_name' ) ),
			'order_date'           => $date_created ? $date_created->date( 'Y-m-d' ) : '',
			'status'               => $order->get_status(),
			'amount'               => $order->get_total(),
			'items'                => $warranty_items,
			'payment_method'       => $order->get_payment_method(),
			'payment_method_title' => $order->get_payment_method_title(),
			'customer_note'        => $order->get_customer_note(),

			/*
			 * The currency this order was taken in, not the store's current one.
			 *
			 * `OrderDetail` in src/types.ts has declared these two fields since
			 * it was written and nothing sent them, so every amount on the order
			 * screen was formatted against `undefined`. An order also keeps the
			 * currency it was placed in — a store that has since switched would
			 * otherwise have its history silently relabelled.
			 */
			'currency'             => get_woocommerce_currency_symbol( $order->get_currency() ),
			'currency_code'        => $order->get_currency(),
			'billing'              => array(
				'first_name' => $order->get_billing_first_name(),
				'last_name'  => $order->get_billing_last_name(),
				'email'      => $order->get_billing_email(),
				'phone'      => $order->get_billing_phone(),
				'address_1'  => $order->get_billing_address_1(),
				'address_2'  => $order->get_billing_address_2(),
				'city'       => $order->get_billing_city(),
				'state'      => $order->get_billing_state(),
				'postcode'   => $order->get_billing_postcode(),
				'country'    => $order->get_billing_country(),
			),
		);
	}

	/**
	 * Get recent orders
	 *
	 * Retrieves the most recent orders containing warranty products.
	 *
	 * @param int $limit Number of orders to retrieve.
	 *
	 * @return array<int, array<string, mixed>> Recent orders data with customer and product information.
	 */
	public function get_recent_orders( int $limit = 10 ): array {
		// Get all warranty products
		$warranty_products = $this->get_warranty_product_ids();

		if ( empty( $warranty_products ) ) {
			return array();
		}

		// Get recent completed orders containing warranty products
		$orders = $this->get_warranty_orders(
			array(
				'limit'   => $limit * 5, // Get more to account for filtering
				'orderby' => 'date',
				'order'   => 'DESC',
			)
		);

		$recent_orders = array();

		foreach ( $orders as $order ) {
			if ( ! $order instanceof WC_Order ) {
				continue;
			}

			$warranty_data = $this->get_order_warranty_items( $order );

			if ( $warranty_data['count'] > 0 ) {
				$products     = array_column( $warranty_data['items'], 'product_name' );
				$date_created = $order->get_date_created();

				$recent_orders[] = array(
					'id'       => $order->get_id(),
					'customer' => trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ),
					'email'    => $order->get_billing_email(),
					'avatar'   => self::avatar_url_for_order( $order ),
					'products' => $products,
					'total'    => $order->get_total(),
					'date'     => $date_created ? \date_i18n( \get_option( 'date_format' ), $date_created->getTimestamp() ) : '',
					'edit_url' => \admin_url( 'post.php?post=' . $order->get_id() . '&action=edit' ),
				);
			}
		}

		/*
		 * Trimmed back to what was asked for.
		 *
		 * The query above deliberately over-fetches — five times the limit — so
		 * that orders dropped by the warranty-items check below still leave
		 * enough rows. Nothing then cut the result back down, so
		 * get_recent_orders( 5 ) returned up to twenty-five and the dashboard's
		 * "Recent Orders" panel ran five times longer than the card it sits in
		 * was designed for.
		 */
		return $limit > 0 ? array_slice( $recent_orders, 0, $limit ) : $recent_orders;
	}

	/**
	 * Get warranty orders
	 *
	 * Retrieves orders containing warranty products with optional filtering.
	 *
	 * @param array<string, mixed> $args Additional arguments to merge with default query.
	 *
	 * @return array<int, WC_Order> Array of WC_Order objects containing warranty products.
	 */
	public function get_warranty_orders( array $args = array() ): array {
		global $wpdb;

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

		/*
		 * `absint()` turned the "no limit" sentinel into a limit of one.
		 *
		 * Callers say `limit => -1` for "everything", which is WooCommerce's
		 * convention and what `build_order_query_args()` passes by default.
		 * `absint( -1 )` is 1, so `$limit > 0` held and the query ran with
		 * `LIMIT 1` — the entire Reports screen was computed from a single
		 * order. It read "Total Orders 1" on a shop with twenty-two of them,
		 * whatever date range was chosen.
		 */
		$limit  = isset( $args['limit'] ) ? (int) $args['limit'] : -1;
		$offset = isset( $args['offset'] ) ? absint( $args['offset'] ) : 0;

		/*
		 * Honour the ordering the caller asked for.
		 *
		 * This always sorted by `order_id DESC` and silently discarded the
		 * `orderby` and `order` it was handed. `get_recent_orders()` asks for
		 * `date`, so the dashboard's "Recent Orders" panel was really showing
		 * "highest order IDs" — which is the same thing only while orders are
		 * created in the order they were placed. An imported order, a backdated
		 * one, or anything a migration wrote gets a high ID and an old date, and
		 * takes the top of the panel from the orders that genuinely are recent.
		 *
		 * `purchase_date` rather than a join to the orders table: it is anchored
		 * to the order's own creation date when the record is written, so it
		 * answers the same question without HPOS-versus-posts branching.
		 *
		 * Whitelisted rather than interpolated. Both halves land in SQL, and a
		 * caller's string reaching an ORDER BY clause is how that goes wrong.
		 */
		$order_by = 'date' === ( $args['orderby'] ?? '' ) ? 'MAX(purchase_date)' : 'order_id';
		$sort     = 'ASC' === strtoupper( (string) ( $args['order'] ?? 'DESC' ) ) ? 'ASC' : 'DESC';

		/*
		 * The date range reaches the query.
		 *
		 * `date_after` and `date_before` are wc_get_orders() arguments, and this
		 * method does not call wc_get_orders() — it reads the warranties table
		 * directly, and simply ignored them. Every range on the Reports screen
		 * therefore produced identical figures: "Last 7 days" and "Last 365
		 * days" counted the same orders, because neither was ever applied.
		 */
		$where  = array( '1=1' );
		$params = array();

		if ( ! empty( $args['date_after'] ) ) {
			$where[]  = 'purchase_date >= %s';
			$params[] = $args['date_after'];
		}

		if ( ! empty( $args['date_before'] ) ) {
			$where[]  = 'purchase_date <= %s';
			$params[] = $args['date_before'];
		}

		$clause = implode( ' AND ', $where );

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$select = "SELECT order_id FROM {$table_name} WHERE {$clause} GROUP BY order_id ORDER BY {$order_by} {$sort}";

		/*
		 * prepare() only when there is something to prepare.
		 *
		 * Calling it with no placeholders raises _doing_it_wrong in WordPress
		 * 6.2 and up, so the unlimited branch used to emit a notice on every
		 * dashboard load.
		 */
		if ( $limit > 0 ) {
			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Replacements are passed as one array; the sniff counts arguments, not elements.
			$prepared = $wpdb->prepare( $select . ' LIMIT %d OFFSET %d', array_merge( $params, array( $limit, $offset ) ) );

			$order_ids = $wpdb->get_col( $prepared );
		} else {
			$order_ids = $params
				? $wpdb->get_col( $wpdb->prepare( $select, $params ) )
				: $wpdb->get_col( $select );
		}
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared

		if ( empty( $order_ids ) ) {
			return array();
		}

		/*
		 * And so does the status filter, which was dropped the same way. A
		 * cancelled or refunded order kept contributing to the sales figures.
		 */
		$statuses = isset( $args['status'] ) ? (array) $args['status'] : array();
		// A prefix, not a character list: ltrim( 'completed', 'wc-' ) strips the
		// leading "c" and yields "ompleted", which matches no status at all.
		$statuses = array_map(
			static function ( $status ) {
				return (string) preg_replace( '/^wc-/', '', (string) $status );
			},
			$statuses
		);

		$warranty_orders = array();

		foreach ( $order_ids as $order_id ) {
			$order = \wc_get_order( $order_id );

			if ( ! $order instanceof WC_Order ) {
				continue;
			}

			if ( $statuses && ! in_array( $order->get_status(), $statuses, true ) ) {
				continue;
			}

			$warranty_orders[] = $order;
		}

		/**
		 * Filter warranty orders results
		 *
		 * @param array $warranty_orders Array of WC_Order objects.
		 * @param array $args            Additional arguments passed to the method.
		 */
		return \apply_filters( 'warranty_cart_warranty_orders', $warranty_orders, $args );
	}

	/**
	 * Build standard order query arguments
	 *
	 * @param array<string, mixed> $args      Additional arguments to merge.
	 * @param array<string, mixed> $overrides Override default arguments.
	 *
	 * @return array<string, mixed> Complete query arguments for wc_get_orders().
	 */
	private function build_order_query_args( array $args = array(), array $overrides = array() ): array {
		$default_args = array(
			'limit'  => - 1,
			'status' => array( OrderInternalStatus::COMPLETED, OrderInternalStatus::PROCESSING, OrderInternalStatus::ON_HOLD ),
			'return' => 'objects',
		);

		return array_merge( $default_args, $overrides, $args );
	}

	/**
	 * Build order query with date range filtering
	 *
	 * @param int                  $date_range Days to look back (0 = all time).
	 * @param array<string, mixed> $args       Additional arguments.
	 * @param array<string, mixed> $overrides  Override default arguments.
	 *
	 * @return array<string, mixed> Query arguments with date filtering.
	 */
	private function build_order_query_with_date_range( int $date_range = 0, array $args = array(), array $overrides = array() ): array {
		$query_args = $this->build_order_query_args( $args, $overrides );

		// Add date range filtering if specified
		if ( $date_range > 0 ) {
			$end_date   = \current_time( 'Y-m-d H:i:s' );
			$timestamp  = strtotime( "-{$date_range} days" );
			$start_date = $timestamp ? \wp_date( 'Y-m-d H:i:s', $timestamp ) : null;

			if ( $start_date ) {
				$query_args['date_after']  = $start_date;
				$query_args['date_before'] = $end_date;
			}
		}

		return $query_args;
	}

	/**
	 * Build order query for completed orders only
	 *
	 * @param array<string, mixed> $args Additional arguments.
	 *
	 * @return array<string, mixed> Query arguments for completed orders.
	 */
	private function build_completed_orders_query( array $args = array() ): array {
		return $this->build_order_query_args( $args, array( 'status' => OrderInternalStatus::COMPLETED ) );
	}

	/**
	 * Check if a product is a warranty product
	 *
	 * @param WC_Product $product Product to check.
	 *
	 * @return bool True if product is a warranty product.
	 */
	public function is_warranty_product( WC_Product $product ): bool {
		return $product->exists() && $product->get_meta( '_warranty_cart_product_status', true ) === 'enabled' && $product->meta_exists( '_warranty_cart_product_type' );
	}

	/**
	 * A warranty product's type, from whichever key holds it.
	 *
	 * The plugin has written this under two names. The product editor, the REST
	 * controllers and the CSV importer all write `_warranty_cart_product_type`
	 * today; `_warranty_product_type` is what the importer used to write, and it
	 * is still on every product created before that was fixed.
	 *
	 * Reading one key and not the other has now caused the same class of bug in
	 * four places — the shortcode's buckets, the cart controller, the orders
	 * count below, and the checkout-fields section, which never rendered at all
	 * for a warranty made through the plugin's own product editor. This is the
	 * one definition, so the next caller does not have to rediscover it.
	 *
	 * @param mixed $product A product, or whatever the caller had.
	 *
	 * @return string The type, or an empty string when it is not a warranty.
	 */
	public static function product_type( $product ): string {
		if ( ! $product instanceof WC_Product ) {
			return '';
		}

		$type = $product->get_meta( '_warranty_cart_product_type', true );

		if ( ! $type ) {
			$type = $product->get_meta( '_warranty_product_type', true );
		}

		return is_string( $type ) ? $type : '';
	}

	/**
	 * The warranty records written against an order.
	 *
	 * The certificate used to list `$order->get_items()` — every line on the
	 * order, including the physical goods — under a heading reading "Warranty
	 * Products", and had no access to a term, an expiry date or what was
	 * covered, because none of that lives on an order line. It lives here.
	 *
	 * @param int $order_id The order.
	 *
	 * @return array<int, object> Records, each with product_name and covers.
	 */
	public function records_for_order( int $order_id ): array {
		global $wpdb;

		$table = $wpdb->prefix . 'warranty_cart_warranties';

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names cannot be placeholders.
		$records = $wpdb->get_results(
			$wpdb->prepare( "SELECT * FROM {$table} WHERE order_id = %d ORDER BY id ASC", $order_id )
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		if ( ! is_array( $records ) ) {
			return array();
		}

		foreach ( $records as $record ) {
			$product = \wc_get_product( (int) $record->product_id );
			$covered = $record->covered_product_id ? \wc_get_product( (int) $record->covered_product_id ) : null;

			$record->product_name = $product ? $product->get_name() : __( 'Warranty', 'warranty-cart' );
			$record->product_sku  = $product ? $product->get_sku() : '';
			$record->covers       = $covered ? $covered->get_name() : '';
			$record->covers_sku   = $covered ? $covered->get_sku() : '';

			/*
			 * Terms from the record where it has them, falling back to the
			 * product. A record is a snapshot of what was sold, so if the shop
			 * later edits the plan the certificate still describes the cover the
			 * customer actually bought.
			 */
			if ( empty( $record->coverage_details ) && $product ) {
				$record->coverage_details = (string) $product->get_meta( '_warranty_cart_coverage_details', true );
			}
		}

		return $records;
	}

	/**
	 * A warranty product's term length, under either spelling of the key.
	 *
	 * @param WC_Product|null|false $product The product.
	 *
	 * @return int
	 */
	public static function product_duration( $product ): int {
		if ( ! $product instanceof WC_Product ) {
			return 0;
		}

		$duration = $product->get_meta( '_warranty_cart_duration', true );

		if ( ! $duration ) {
			$duration = $product->get_meta( '_warranty_duration', true );
		}

		return (int) $duration;
	}

	/**
	 * The unit that term is counted in.
	 *
	 * @param WC_Product|null|false $product The product.
	 *
	 * @return string One of days, months, years, or ''.
	 */
	public static function product_duration_unit( $product ): string {
		if ( ! $product instanceof WC_Product ) {
			return '';
		}

		$unit = $product->get_meta( '_warranty_cart_duration_unit', true );

		if ( ! $unit ) {
			$unit = $product->get_meta( '_warranty_duration_unit', true );
		}

		return is_string( $unit ) ? $unit : '';
	}

	/**
	 * Get warranty items from an order
	 *
	 * @param WC_Order $order Order to analyze.
	 *
	 * @return array<string, mixed> Array of warranty items with product and total data.
	 */
	public function get_order_warranty_items( WC_Order $order ): array {
		$warranty_items = array();
		$total          = 0;

		foreach ( $order->get_items( 'line_item' ) as $order_item ) {
			if ( ! $order_item instanceof WC_Order_Item_Product ) {
				continue;
			}

			$product = $order_item->get_product();
			if ( $product instanceof WC_Product && $this->is_warranty_product( $product ) ) {
				$item_total = $order_item->get_total();
				$total     += $item_total;

				$warranty_items[] = array(
					'product'      => $product,
					'order_item'   => $order_item,
					'quantity'     => $order_item->get_quantity(),
					'total'        => $item_total,
					'product_id'   => $product->get_id(),
					'product_name' => $product->get_name(),
				);
			}
		}

		return array(
			'items' => $warranty_items,
			'total' => $total,
			'count' => count( $warranty_items ),
		);
	}

	/**
	 * Get top customer segments
	 *
	 * Analyzes customer purchasing patterns and segments.
	 *
	 * @param array $orders Array of WC_Order objects.
	 *
	 * @return array Customer segment data.
	 */
	private function get_top_customer_segments( array $orders ): array {
		$segments = array(
			'new_customers'       => 0,
			'returning_customers' => 0,
			'high_value'          => 0,
			'bulk_purchasers'     => 0,
		);

		$customer_orders = array();
		$customer_totals = array();

		foreach ( $orders as $order ) {
			if ( ! $order instanceof WC_Order ) {
				continue;
			}

			$customer_id = $order->get_customer_id();
			if ( ! $customer_id ) {
				continue; // Skip guest orders
			}

			$warranty_data = $this->get_order_warranty_items( $order );
			if ( 0 === $warranty_data['count'] ) {
				continue;
			}

			if ( ! isset( $customer_orders[ $customer_id ] ) ) {
				$customer_orders[ $customer_id ] = 0;
				$customer_totals[ $customer_id ] = 0;
			}

			++$customer_orders[ $customer_id ];
			$customer_totals[ $customer_id ] += $warranty_data['total'];
		}

		foreach ( $customer_orders as $customer_id => $order_count ) {
			$total_spent = $customer_totals[ $customer_id ];

			if ( 1 === $order_count ) {
				++$segments['new_customers'];
			} else {
				++$segments['returning_customers'];
			}

			if ( $total_spent > 500 ) {
				++$segments['high_value'];
			}

			if ( $order_count > 3 ) {
				++$segments['bulk_purchasers'];
			}
		}

		return $segments;
	}

	/**
	 * Get previous period sales
	 *
	 * Calculates sales for the previous period of the same length.
	 *
	 * @param int $date_range Current date range in days.
	 *
	 * @return float Previous period sales amount.
	 */
	private function get_previous_period_sales( int $date_range ): float {
		$previous_start = strtotime( "-{$date_range} days", strtotime( "-{$date_range} days" ) );
		$previous_end   = strtotime( "-{$date_range} days" );

		$query_args = $this->build_order_query_args(
			array(
				'date_created' => $previous_start . '...' . $previous_end,
				'status'       => OrderInternalStatus::COMPLETED,
			)
		);

		$orders = \wc_get_orders( $query_args );
		if ( ! is_array( $orders ) ) {
			return 0.0;
		}

		$total_sales = 0.0;
		foreach ( $orders as $order ) {
			if ( ! $order instanceof WC_Order ) {
				continue;
			}

			$warranty_data = $this->get_order_warranty_items( $order );
			$total_sales  += $warranty_data['total'];
		}

		return $total_sales;
	}

	/**
	 * Get warranty type distribution
	 *
	 * Analyzes the distribution of warranty types sold.
	 *
	 * @param array $orders Array of WC_Order objects.
	 *
	 * @return array Warranty type distribution data.
	 */
	private function get_warranty_type_distribution( array $orders ): array {
		$distribution = array(
			'main_warranty'      => 0,
			'additional_service' => 0,
		);

		foreach ( $orders as $order ) {
			if ( ! $order instanceof WC_Order ) {
				continue;
			}

			$warranty_data = $this->get_order_warranty_items( $order );

			foreach ( $warranty_data['items'] as $item ) {
				$product = $item['product'];
				if ( $product instanceof WC_Product ) {
					$warranty_type = self::product_type( $product );
					if ( isset( $distribution[ $warranty_type ] ) ) {
						$distribution[ $warranty_type ] += $item['quantity'];
					}
				}
			}
		}

		return $distribution;
	}

	/**
	 * Get average warranty duration
	 *
	 * Calculates the average duration of warranties sold.
	 *
	 * @param array $orders Array of WC_Order objects.
	 *
	 * @return array Average duration data by unit.
	 */
	private function get_average_warranty_duration( array $orders ): array {
		$durations = array(
			'days'   => array(),
			'months' => array(),
			'years'  => array(),
		);

		foreach ( $orders as $order ) {
			if ( ! $order instanceof WC_Order ) {
				continue;
			}

			$warranty_data = $this->get_order_warranty_items( $order );

			foreach ( $warranty_data['items'] as $item ) {
				$product = $item['product'];
				if ( $product instanceof WC_Product ) {
					$warranty_type = self::product_type( $product );
					if ( 'main_warranty' === $warranty_type ) {
						/*
						 * The keys this plugin actually writes.
						 *
						 * These read `_warranty_duration` and
						 * `_warranty_duration_unit`; the product screen saves
						 * `_warranty_cart_duration` and
						 * `_warranty_cart_duration_unit`. Nothing matched, so
						 * `$unit` was '' and never a key of $durations —
						 * "Average warranty duration" read 0 days, 0 months and
						 * 0 years no matter what was on sale.
						 */
						$duration = (int) self::product_duration( $product );
						$unit     = self::product_duration_unit( $product );

						if ( isset( $durations[ $unit ] ) ) {
							$durations[ $unit ][] = $duration;
						}
					}
				}
			}
		}

		$averages = array();
		foreach ( $durations as $unit => $values ) {
			if ( ! empty( $values ) ) {
				$averages[ $unit ] = array_sum( $values ) / count( $values );
			} else {
				$averages[ $unit ] = 0;
			}
		}

		return $averages;
	}

	/**
	 * Get dashboard data
	 *
	 * Retrieves all data needed for the admin dashboard.
	 *
	 * @param int $date_range Date range in days for sales data.
	 *
	 * @return array<string, mixed> Dashboard data array.
	 */
	public function get_dashboard_data( int $date_range = 30 ): array {
		$cache_key   = 'warranty_dashboard_data_' . $date_range;
		$cached_data = \get_transient( $cache_key );

		if ( false !== $cached_data ) {
			return $cached_data;
		}

		// Get sales overview data
		$sales_data = $this->get_sales_data( $date_range );

		// Get popular warranties (top 5)
		$popular_warranties = $this->get_popular_warranties( 5 );

		// Get recent orders (top 5)
		$recent_orders = $this->get_recent_orders( 5 );

		// Get monthly sales data
		$monthly_sales = $this->get_monthly_sales_data();

		// Get warranty status summary
		$warranty_status_summary = $this->get_warranty_status_summary();

		// Get performance metrics
		$performance_metrics = $this->get_performance_metrics( $date_range );

		// Get alerts and notifications
		$alerts = $this->get_system_alerts();

		$dashboard_data = array(
			'sales_overview'          => $sales_data,
			'popular_warranties'      => $popular_warranties,
			'recent_orders'           => $recent_orders,
			'monthly_sales'           => $monthly_sales,
			'warranty_status_summary' => $warranty_status_summary,
			'performance_metrics'     => $performance_metrics,
			'alerts'                  => $alerts,
			'date_range'              => $date_range,
			'generated_at'            => time(),
		);

		// Cache for 10 minutes
		\set_transient( $cache_key, $dashboard_data, 10 * MINUTE_IN_SECONDS );

		return $dashboard_data;
	}

	/**
	 * Get warranty status summary
	 *
	 * Provides a summary of warranty statuses across all products.
	 *
	 * @return array Warranty status summary data.
	 */
	private function get_warranty_status_summary(): array {
		$warranty_products = $this->get_warranty_products();

		$summary = array(
			'total_warranties'    => 0,
			'active_warranties'   => 0,
			'inactive_warranties' => 0,
			'out_of_stock'        => 0,
			'low_stock'           => 0,
		);

		foreach ( $warranty_products as $product ) {
			if ( ! $product instanceof WC_Product ) {
				continue;
			}

			++$summary['total_warranties'];

			if ( 'publish' === $product->get_status() ) {
				++$summary['active_warranties'];
			} else {
				++$summary['inactive_warranties'];
			}

			if ( ! $product->is_in_stock() ) {
				++$summary['out_of_stock'];
			} elseif ( $product->get_stock_quantity() && $product->get_stock_quantity() < 5 ) {
				++$summary['low_stock'];
			}
		}

		return $summary;
	}

	/**
	 * Get performance metrics
	 *
	 * Calculates various performance metrics for the dashboard.
	 *
	 * @param int $date_range Date range in days.
	 *
	 * @return array Performance metrics data.
	 */
	private function get_performance_metrics( int $date_range ): array {
		$current_sales  = $this->get_sales_data( $date_range );
		$previous_sales = $this->get_sales_data( $date_range * 2 );

		$sales_growth = 0;
		if ( $previous_sales['total_sales'] > 0 ) {
			$sales_growth = ( ( $current_sales['total_sales'] - $previous_sales['total_sales'] ) / $previous_sales['total_sales'] ) * 100;
		}

		$order_growth = 0;
		if ( $previous_sales['total_orders'] > 0 ) {
			$order_growth = ( ( $current_sales['total_orders'] - $previous_sales['total_orders'] ) / $previous_sales['total_orders'] ) * 100;
		}

		return array(
			'sales_growth_percentage' => round( $sales_growth, 2 ),
			'order_growth_percentage' => round( $order_growth, 2 ),
			'current_period_sales'    => $current_sales['total_sales'],
			'previous_period_sales'   => $previous_sales['total_sales'],
			'current_period_orders'   => $current_sales['total_orders'],
			'previous_period_orders'  => $previous_sales['total_orders'],
		);
	}

	/**
	 * Get system alerts
	 *
	 * Retrieves important alerts and notifications for the dashboard.
	 *
	 * @return array System alerts data.
	 */
	private function get_system_alerts(): array {
		$alerts = array();

		// Check for out of stock warranties
		$warranty_products  = $this->get_warranty_products();
		$out_of_stock_count = 0;

		foreach ( $warranty_products as $product ) {
			if ( $product instanceof WC_Product && ! $product->is_in_stock() ) {
				++$out_of_stock_count;
			}
		}

		if ( $out_of_stock_count > 0 ) {
			$alerts[] = array(
				'type'    => 'warning',
				'message' => sprintf(
				/* translators: %d: number of out of stock warranties */
					\_n(
						'%d warranty product is out of stock',
						'%d warranty products are out of stock',
						$out_of_stock_count,
						'warranty-cart'
					),
					$out_of_stock_count
				),
				'action'  => \admin_url( 'edit.php?post_type=product&product_cat=warranties' ),
			);
		}

		// Check for low stock warranties
		$low_stock_count = 0;
		foreach ( $warranty_products as $product ) {
			if ( $product instanceof WC_Product && $product->get_stock_quantity() && $product->get_stock_quantity() < 5 && $product->is_in_stock() ) {
				++$low_stock_count;
			}
		}

		if ( $low_stock_count > 0 ) {
			$alerts[] = array(
				'type'    => 'info',
				'message' => sprintf(
				/* translators: %d: number of low stock warranties */
					\_n(
						'%d warranty product has low stock',
						'%d warranty products have low stock',
						$low_stock_count,
						'warranty-cart'
					),
					$low_stock_count
				),
				'action'  => \admin_url( 'edit.php?post_type=product&product_cat=warranties' ),
			);
		}

		// Check for recent orders without PDFs
		$recent_orders      = $this->get_recent_orders( 10 );
		$orders_without_pdf = 0;

		foreach ( $recent_orders as $order_data ) {
			$order = \wc_get_order( $order_data['id'] );
			if ( $order instanceof WC_Order ) {
				$warranty_data = $this->get_order_warranty_items( $order );
				if ( $warranty_data['count'] > 0 ) {
					// Check if PDF exists
					$upload_dir = \wp_upload_dir();
					$pdf_path   = $upload_dir['basedir'] . '/warranties/warranty-' . $order->get_id() . '.pdf';

					if ( ! warranty_cart()->file_exists( $pdf_path ) ) {
						++$orders_without_pdf;
					}
				}
			}
		}

		if ( $orders_without_pdf > 0 ) {
			$alerts[] = array(
				'type'    => 'info',
				'message' => sprintf(
				/* translators: %d: number of orders without PDFs */
					\_n(
						'%d recent order is missing warranty PDF',
						'%d recent orders are missing warranty PDFs',
						$orders_without_pdf,
						'warranty-cart'
					),
					$orders_without_pdf
				),
				'action'  => \admin_url( 'admin.php?page=warranty-orders' ),
			);
		}

		return $alerts;
	}
}
