<?php

namespace CelerSearch\Queue;

defined( 'ABSPATH' ) || exit;

use CelerSearch\DataTransfer\IndexableOrder;
use CelerSearch\DataTransfer\IndexablePost;
use CelerSearch\DataTransfer\IndexCandidate;
use CelerSearch\Factories\IndexFactory;
use CelerSearch\Factories\ObserverFactory;
use CelerSearch\Utilities\Logger;
use CelerSearch\Utilities\PostUtilities;

/**
 * Handles sync and delete jobs from the queue
 */
class SyncHandler {

	/**
	 * Handle a sync job
	 *
	 * @param string $item_type Type identifier (e.g., 'post').
	 * @param mixed  $item_id   The item's unique identifier.
	 * @param int    $index_id  The target index ID.
	 *
	 * @return void
	 */
	public static function handle_sync( string $item_type, $item_id, int $index_id ): void {
		$log_context = [
			'item_type' => $item_type,
			'item_id'   => $item_id,
			'index_id'  => $index_id,
		];

		Logger::channel( Logger::CHANNEL_QUEUE )->debug( 'Processing sync job', $log_context );

		try {
			$index = IndexFactory::create( $index_id );
			if ( ! $index ) {
				Logger::channel( Logger::CHANNEL_QUEUE )->warning( 'Index not found, skipping sync', $log_context );
				return;
			}

			// Get candidates by IDs using the index's method
			$candidates = $index->get_candidates_by_ids( [ $item_id ] );

			// For order type, load directly via wc_get_order if candidates empty
			if ( $item_type === 'order' && empty( $candidates ) && function_exists( 'wc_get_order' ) ) {
				$order = wc_get_order( $item_id );
				if ( $order ) {
					$candidates = [ new IndexCandidate( $order ) ];
				}
			}

			if ( empty( $candidates ) ) {
				Logger::channel( Logger::CHANNEL_QUEUE )->info( 'Item no longer exists, skipping sync', $log_context );
				return;
			}

			// Create observer and sync each candidate
			$observer = ObserverFactory::create_for_index( $index );
			if ( ! $observer ) {
				Logger::channel( Logger::CHANNEL_QUEUE )->warning( 'No observer available for index', $log_context );
				return;
			}

			foreach ( $candidates as $candidate ) {
				$observer->process_sync( $candidate->item );
			}

			Logger::channel( Logger::CHANNEL_QUEUE )->info( 'Sync job completed', $log_context );

		} catch ( \Exception $e ) {
			$log_context['exception'] = $e->getMessage();
			Logger::channel( Logger::CHANNEL_ERRORS )->error( 'Sync job failed', $log_context );
		}
	}

	/**
	 * Handle a delete job
	 *
	 * @param string $item_type Type identifier (e.g., 'post').
	 * @param mixed  $item_id   The item's unique identifier.
	 * @param int    $index_id  The target index ID.
	 *
	 * @return void
	 */
	public static function handle_delete( string $item_type, $item_id, int $index_id ): void {
		$log_context = [
			'item_type' => $item_type,
			'item_id'   => $item_id,
			'index_id'  => $index_id,
		];

		Logger::channel( Logger::CHANNEL_QUEUE )->debug( 'Processing delete job', $log_context );

		try {
			$index = IndexFactory::create( $index_id );
			if ( ! $index ) {
				Logger::channel( Logger::CHANNEL_QUEUE )->warning( 'Index not found, skipping delete', $log_context );
				return;
			}

			// For post-based items, delete all possible split records
			if ( $item_type === 'post' && is_numeric( $item_id ) ) {
				self::delete_post_records( $index, (int) $item_id );
				Logger::channel( Logger::CHANNEL_QUEUE )->info( 'Delete job completed', $log_context );
				return;
			}

			// For order-based items, delete the single order record
			if ( $item_type === 'order' && is_numeric( $item_id ) ) {
				self::delete_order_records( $index, (int) $item_id );
				Logger::channel( Logger::CHANNEL_QUEUE )->info( 'Delete job completed', $log_context );
				return;
			}

			// For other item types, let the observer handle it
			// First try to get the item - if it exists, use the observer's delete
			$candidates = $index->get_candidates_by_ids( [ $item_id ] );
			if ( ! empty( $candidates ) ) {
				$observer = ObserverFactory::create_for_index( $index );
				if ( $observer ) {
					foreach ( $candidates as $candidate ) {
						$observer->process_delete( $candidate->item );
					}
				}
			} else {
				// Item doesn't exist, but for posts we should still try to delete records
				Logger::channel( Logger::CHANNEL_QUEUE )->info( 'Item already deleted, nothing to do', $log_context );
			}

			Logger::channel( Logger::CHANNEL_QUEUE )->info( 'Delete job completed', $log_context );

		} catch ( \Exception $e ) {
			$log_context['exception'] = $e->getMessage();
			Logger::channel( Logger::CHANNEL_ERRORS )->error( 'Delete job failed', $log_context );
		}
	}

	/**
	 * Delete all records for a post ID
	 *
	 * Since posts can be split into multiple records (e.g., 123-0, 123-1, 123-2),
	 * we need to delete all possible records.
	 *
	 * @param \CelerSearch\Indices\BaseIndex $index   The index.
	 * @param int                            $post_id The post ID.
	 *
	 * @return void
	 */
	private static function delete_post_records( $index, int $post_id ): void {
		$max_splits = apply_filters( 'celersearch_max_post_splits', 10 );

		for ( $i = 0; $i < $max_splits; $i++ ) {
			$object_id = PostUtilities::get_post_object_id( $post_id, $i );
			$object    = new IndexablePost( $object_id, [ 'post_id' => $post_id ] );

			try {
				$index->delete_item( $object );
			} catch ( \Exception $e ) {
				// Stop trying if we get an error after the first split
				if ( $i > 0 ) {
					break;
				}
			}
		}
	}

	/**
	 * Delete record for an order ID
	 *
	 * Unlike posts, orders don't split into multiple records,
	 * so we only need to delete a single record.
	 *
	 * @param \CelerSearch\Indices\BaseIndex $index    The index.
	 * @param int                            $order_id The order ID.
	 *
	 * @return void
	 */
	private static function delete_order_records( $index, int $order_id ): void {
		$object_id = 'order-' . $order_id;
		$object    = new IndexableOrder( $object_id, [ 'order_id' => $order_id ] );

		try {
			$index->delete_item( $object );
		} catch ( \Exception $e ) {
			Logger::channel( Logger::CHANNEL_ERRORS )->error( 'Order delete failed in queue', [
				'order_id'  => $order_id,
				'exception' => $e->getMessage(),
			] );
		}
	}
}
