<?php
/**
 * Bulk processing class.
 *
 * @package Altify_AI
 */

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

/**
 * Handles AJAX batch processing for media and WooCommerce images.
 */
class Altify_AI_Bulk {
	/**
	 * Client instance.
	 *
	 * @var Altify_AI_Client
	 */
	private $client;

	/**
	 * WooCommerce helper.
	 *
	 * @var Altify_AI_WooCommerce
	 */
	private $woocommerce;

	/**
	 * Logger instance.
	 *
	 * @var Altify_AI_Logger
	 */
	private $logger;

	/**
	 * Constructor.
	 *
	 * @param Altify_AI_Client      $client      Client.
	 * @param Altify_AI_WooCommerce $woocommerce Woo helper.
	 * @param Altify_AI_Logger      $logger      Logger.
	 */
	public function __construct($client, $woocommerce, $logger) {
		$this->client      = $client;
		$this->woocommerce = $woocommerce;
		$this->logger      = $logger;

		add_action('wp_ajax_altify_ai_bulk_process_media', array($this, 'ajax_process_media_batch'));
		add_action('wp_ajax_altify_ai_bulk_process_woo', array($this, 'ajax_process_woo_batch'));
	}

	/**
	 * Process a media-library batch.
	 *
	 * @return void
	 */
	public function ajax_process_media_batch() {
		if (!current_user_can('manage_options')) {
			wp_send_json_error(array('message' => __('Insufficient permissions.', 'altify-ai-auto-alt-text-generator')), 403);
		}

		$nonce_ok = check_ajax_referer('altify_ai_admin_nonce', 'nonce', false);
		if (!$nonce_ok) {
			wp_send_json_error(array('message' => __('Invalid nonce.', 'altify-ai-auto-alt-text-generator')), 403);
		}

		$mode       = sanitize_key((string) $this->get_post_value('mode', 'missing'));
		$mode       = ('all' === $mode) ? 'all' : 'missing';
		$run_mode   = sanitize_key((string) $this->get_post_value('run_mode', 'auto'));
		$run_mode   = ('offline' === $run_mode) ? 'offline' : 'auto';
		$generation_mode = sanitize_key((string) $this->get_post_value('generation_mode', ''));
		if ('' !== $generation_mode) {
			$generation_mode = altify_ai_normalize_generation_mode(
				$generation_mode,
				('offline' === $run_mode) ? 'offline_only' : 'ai_first'
			);
		}
		$offset     = absint($this->get_post_value('offset', 0));
		$batch_size = absint($this->get_post_value('batch_size', 10));
		$batch_size = max(1, min(100, $batch_size));
		$date_from  = sanitize_text_field((string) $this->get_post_value('date_from', ''));
		$date_to    = sanitize_text_field((string) $this->get_post_value('date_to', ''));

		$filters = array(
			'mode'      => $mode,
			'date_from' => $date_from,
			'date_to'   => $date_to,
		);

		$total = $this->count_media_attachments($filters);

		// For "missing" mode, always start from offset 0 because records disappear as they get ALT text.
		$effective_offset = ('missing' === $mode) ? 0 : $offset;
		$ids              = $this->get_media_attachment_ids($filters, $effective_offset, $batch_size);

		$batch_stats = array(
			'processed' => 0,
			'success'   => 0,
			'skipped'   => 0,
			'failed'    => 0,
			'fallback'  => 0,
			'cached'    => 0,
			'method_counts' => array(
				'ai'               => 0,
				'offline'          => 0,
				'offline_fallback' => 0,
				'cache'            => 0,
			),
		);
		$last_error  = '';

		foreach ($ids as $attachment_id) {
			$generation_args = array(
				'source'        => ('offline' === $run_mode) ? 'bulk_offline' : 'bulk_media',
				'force_offline' => ('offline' === $run_mode),
			);

			if ('' !== $generation_mode) {
				$generation_args['generation_mode'] = $generation_mode;
			}

			$result = $this->client->generate_and_save_alt(
				$attachment_id,
				$generation_args
			);

			$batch_stats['processed']++;
			$status = isset($result['status']) ? $result['status'] : 'failed';
			if (isset($batch_stats[$status])) {
				$batch_stats[$status]++;
			} else {
				$batch_stats['failed']++;
			}

			if (!empty($result['fallback_used'])) {
				$batch_stats['fallback']++;
			}

			if (!empty($result['cached'])) {
				$batch_stats['cached']++;
			}

			$method = !empty($result['generation_method']) ? sanitize_key((string) $result['generation_method']) : '';
			if (isset($batch_stats['method_counts'][$method])) {
				$batch_stats['method_counts'][$method]++;
			}

			if (!empty($result['diagnostic'])) {
				$last_error = sanitize_text_field((string) $result['diagnostic']);
			}
		}

		$next_offset = ('missing' === $mode) ? 0 : ($effective_offset + count($ids));
		$done        = (0 === count($ids));
		if ('all' === $mode) {
			$done = ($next_offset >= $total);
		}

		wp_send_json_success(
			array(
				'next_offset' => $next_offset,
				'done'        => (bool) $done,
				'total'       => $total,
				'run_mode'    => $run_mode,
				'generation_mode' => $generation_mode,
				'batch'       => $batch_stats,
				'diagnostics' => array(
					'last_error' => $last_error,
				),
				'logs'        => $this->logger->get_logs(Altify_AI_Logger::MAX_LOGS),
			)
		);
	}

	/**
	 * Run one bounded automation scan for media attachments.
	 *
	 * @param array $args Scan arguments.
	 *
	 * @return array
	 */
	public function run_automation_scan($args = array()) {
		$args = is_array($args) ? $args : array();

		$scope           = altify_ai_normalize_scan_scope(isset($args['scope']) ? $args['scope'] : 'missing');
		$generation_mode = altify_ai_normalize_generation_mode(isset($args['generation_mode']) ? $args['generation_mode'] : 'offline_first', 'offline_first');
		$max_images      = isset($args['max_images']) ? absint($args['max_images']) : 50;
		$max_images      = max(1, min(500, $max_images));
		$offset          = isset($args['offset']) ? absint($args['offset']) : 0;
		$source          = !empty($args['source']) ? sanitize_key((string) $args['source']) : 'daily_scan';

		$filters = array(
			'mode'      => $scope,
			'date_from' => '',
			'date_to'   => '',
		);

		$total            = $this->count_media_attachments($filters);
		$effective_offset = ('missing' === $scope) ? 0 : $offset;
		$ids              = $this->get_media_attachment_ids($filters, $effective_offset, $max_images);

		$batch_stats = array(
			'processed' => 0,
			'success'   => 0,
			'skipped'   => 0,
			'failed'    => 0,
			'fallback'  => 0,
			'cached'    => 0,
			'method_counts' => array(
				'ai'               => 0,
				'offline'          => 0,
				'offline_fallback' => 0,
				'cache'            => 0,
			),
		);
		$last_error = '';

		foreach ($ids as $attachment_id) {
			$result = $this->client->generate_and_save_alt_with_mode(
				$attachment_id,
				$generation_mode,
				array(
					'source'          => $source,
					'force_overwrite' => ('all' === $scope),
				)
			);

			$batch_stats['processed']++;
			$status = isset($result['status']) ? $result['status'] : 'failed';
			if (isset($batch_stats[$status])) {
				$batch_stats[$status]++;
			} else {
				$batch_stats['failed']++;
			}

			if (!empty($result['fallback_used'])) {
				$batch_stats['fallback']++;
			}

			if (!empty($result['cached'])) {
				$batch_stats['cached']++;
			}

			$method = !empty($result['generation_method']) ? sanitize_key((string) $result['generation_method']) : '';
			if (isset($batch_stats['method_counts'][$method])) {
				$batch_stats['method_counts'][$method]++;
			}

			if (!empty($result['diagnostic'])) {
				$last_error = sanitize_text_field((string) $result['diagnostic']);
			}
		}

		$next_offset = ('all' === $scope) ? ($effective_offset + count($ids)) : 0;
		$completed_cycle = ('all' === $scope) ? ($next_offset >= $total) : (count($ids) < $max_images);
		if ('all' === $scope && $completed_cycle) {
			$next_offset = 0;
		}

		return array(
			'scope'           => $scope,
			'generation_mode' => $generation_mode,
			'total'           => $total,
			'next_offset'     => $next_offset,
			'completed_cycle' => $completed_cycle,
			'batch'           => $batch_stats,
			'diagnostics'     => array(
				'last_error' => $last_error,
			),
			'logs'            => $this->logger->get_logs(Altify_AI_Logger::MAX_LOGS),
		);
	}

	/**
	 * Process a WooCommerce batch.
	 *
	 * @return void
	 */
	public function ajax_process_woo_batch() {
		if (!current_user_can('manage_options')) {
			wp_send_json_error(array('message' => __('Insufficient permissions.', 'altify-ai-auto-alt-text-generator')), 403);
		}

		$nonce_ok = check_ajax_referer('altify_ai_admin_nonce', 'nonce', false);
		if (!$nonce_ok) {
			wp_send_json_error(array('message' => __('Invalid nonce.', 'altify-ai-auto-alt-text-generator')), 403);
		}

		if (!$this->woocommerce->is_active()) {
			wp_send_json_error(array('message' => __('WooCommerce is not active.', 'altify-ai-auto-alt-text-generator')), 400);
		}

		if (!$this->woocommerce->is_enabled()) {
			wp_send_json_error(array('message' => __('WooCommerce support is disabled in Altify settings.', 'altify-ai-auto-alt-text-generator')), 400);
		}

		$mode          = sanitize_key((string) $this->get_post_value('mode', 'missing'));
		$mode          = ('all' === $mode) ? 'all' : 'missing';
		$run_mode      = sanitize_key((string) $this->get_post_value('run_mode', 'auto'));
		$run_mode      = ('offline' === $run_mode) ? 'offline' : 'auto';
		$offset        = absint($this->get_post_value('offset', 0));
		$batch_size    = absint($this->get_post_value('batch_size', 10));
		$batch_size    = max(1, min(50, $batch_size));
		$category_slug = sanitize_title((string) $this->get_post_value('product_category', ''));
		$date_from     = sanitize_text_field((string) $this->get_post_value('date_from', ''));
		$date_to       = sanitize_text_field((string) $this->get_post_value('date_to', ''));

		$product_batch = $this->woocommerce->get_product_batch($offset, $batch_size, $category_slug, $date_from, $date_to);
		$product_ids   = !empty($product_batch['product_ids']) ? $product_batch['product_ids'] : array();

		$image_items = $this->woocommerce->get_images_for_products($product_ids, $mode);

		$batch_stats = array(
			'processed' => 0,
			'success'   => 0,
			'skipped'   => 0,
			'failed'    => 0,
			'fallback'  => 0,
			'cached'    => 0,
			'method_counts' => array(
				'ai'               => 0,
				'offline'          => 0,
				'offline_fallback' => 0,
				'cache'            => 0,
			),
		);
		$last_error  = '';

		foreach ($image_items as $item) {
			$result = $this->client->generate_and_save_alt(
				isset($item['attachment_id']) ? absint($item['attachment_id']) : 0,
				array(
					'source'             => ('offline' === $run_mode) ? 'bulk_woo_offline' : 'bulk_woo',
					'force_offline'      => ('offline' === $run_mode),
					'product_name'       => isset($item['product_name']) ? $item['product_name'] : '',
					'product_categories' => isset($item['product_categories']) ? $item['product_categories'] : '',
				)
			);

			$batch_stats['processed']++;
			$status = isset($result['status']) ? $result['status'] : 'failed';
			if (isset($batch_stats[$status])) {
				$batch_stats[$status]++;
			} else {
				$batch_stats['failed']++;
			}

			if (!empty($result['fallback_used'])) {
				$batch_stats['fallback']++;
			}

			if (!empty($result['cached'])) {
				$batch_stats['cached']++;
			}

			$method = !empty($result['generation_method']) ? sanitize_key((string) $result['generation_method']) : '';
			if (isset($batch_stats['method_counts'][$method])) {
				$batch_stats['method_counts'][$method]++;
			}

			if (!empty($result['diagnostic'])) {
				$last_error = sanitize_text_field((string) $result['diagnostic']);
			}
		}

		wp_send_json_success(
			array(
				'next_offset' => isset($product_batch['next_offset']) ? absint($product_batch['next_offset']) : ($offset + $batch_size),
				'done'        => !empty($product_batch['done']),
				'total'       => isset($product_batch['total']) ? absint($product_batch['total']) : 0,
				'run_mode'    => $run_mode,
				'batch'       => $batch_stats,
				'diagnostics' => array(
					'last_error' => $last_error,
				),
				'logs'        => $this->logger->get_logs(Altify_AI_Logger::MAX_LOGS),
			)
		);
	}

	/**
	 * Count media attachments matching filters.
	 *
	 * @param array $filters Filter args.
	 *
	 * @return int
	 */
	private function count_media_attachments($filters) {
		$query = new WP_Query($this->get_media_query_args($filters, 0, 1, false));
		$total = isset($query->found_posts) ? absint($query->found_posts) : 0;
		wp_reset_postdata();

		return $total;
	}

	/**
	 * Get attachment IDs for batch.
	 *
	 * @param array $filters    Filters.
	 * @param int   $offset     Offset.
	 * @param int   $batch_size Batch size.
	 *
	 * @return array
	 */
	private function get_media_attachment_ids($filters, $offset, $batch_size) {
		$query = new WP_Query($this->get_media_query_args($filters, $offset, $batch_size, true));
		$ids   = !empty($query->posts) ? array_map('absint', $query->posts) : array();
		wp_reset_postdata();

		return $ids;
	}

	/**
	 * Build media query args.
	 *
	 * @param array $filters        Filters.
	 * @param int   $offset         Offset.
	 * @param int   $posts_per_page Page size.
	 * @param bool  $no_found_rows  No found rows.
	 *
	 * @return array
	 */
	private function get_media_query_args($filters, $offset, $posts_per_page, $no_found_rows) {
		$mode      = !empty($filters['mode']) ? sanitize_key($filters['mode']) : 'missing';
		$date_from = !empty($filters['date_from']) ? sanitize_text_field($filters['date_from']) : '';
		$date_to   = !empty($filters['date_to']) ? sanitize_text_field($filters['date_to']) : '';

		$args = array(
			'post_type'              => 'attachment',
			'post_status'            => 'inherit',
			'post_mime_type'         => 'image',
			'fields'                 => 'ids',
			'posts_per_page'         => max(1, absint($posts_per_page)),
			'offset'                 => max(0, absint($offset)),
			'orderby'                => 'ID',
			'order'                  => 'ASC',
			'ignore_sticky_posts'    => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
			'no_found_rows'          => (bool) $no_found_rows,
		);

		if ('missing' === $mode) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Required admin-only filter to fetch attachments missing ALT, with pagination.
			$args['meta_query'] = array(
				'relation' => 'OR',
				array(
					'key'     => '_wp_attachment_image_alt',
					'compare' => 'NOT EXISTS',
				),
				array(
					'key'     => '_wp_attachment_image_alt',
					'value'   => '',
					'compare' => '=',
				),
			);
		}

		$date_query = altify_ai_get_date_query($date_from, $date_to);
		if (!empty($date_query)) {
			$args['date_query'] = $date_query;
		}

		return $args;
	}

	/**
	 * Retrieve sanitized scalar value from POST request.
	 *
	 * @param string $key     Field key.
	 * @param mixed  $default Default value.
	 *
	 * @return mixed
	 */
	private function get_post_value($key, $default = '') {
		$value = filter_input(INPUT_POST, $key, FILTER_UNSAFE_RAW);
		if (null === $value || false === $value || is_array($value)) {
			return $default;
		}

		return wp_unslash($value);
	}
}
