<?php

namespace WPDRMS\ASP\Cli;

use WP_CLI;
use WPDRMS\ASP\Index\IndexService;
use WPDRMS\ASP\Utils\Database;
use WPDRMS\ASP\Utils\Str;

class IndexTable {
	private $flags = array();

	/**
	 * Controls the index table generation for Ajax Search Pro.
	 *
	 * ## OPTIONS
	 *
	 * <new|continue|delete|item|info>
	 * : Action to perform.
	 *
	 * [--batch=<batch>]
	 * : How many items to index at a time.
	 * ---
	 * default: 25 (or as set on the index table options page)
	 * ---
	 *
	 * [--skip-confirm]
	 * : Skip confirmation prompt on index creation and deletion.
	 *
	 * [--post_id=<post_id>]
	 * : Used with item & delete commantds. Indexes or deletes a specific post ID.
	 *
	 * [--check-responsiveness]
	 * : Used with new command. Checks the database responsiveness before starting the index table creation.
	 *
	 * [--sleep=<milliseconds>]
	 * : Used with new/continue command. Sleeps for the specified milliseconds inbetween index batches.
	 *
	 * [--separate-process]
	 * : Used with new/continue command. Each batch is processed in a separate process. May save memory for large index tables.
	 *
	 * ## EXAMPLES
	 *
	 *  # Continue indexing the index table until it's done
	 *  $ wp asp index continue
	 *  Indexing 25/45 items.
	 *  Indexing 45/45 items.
	 *  Success: 45 items indexed.
	 *
	 *  # Create a new index table
	 *  $ wp asp index new
	 *  Index table creation started.
	 *  Indexing 25/45 items.
	 *  Indexing 45/45 items.
	 *  Success: 45 items indexed.
	 *  Success: Indexing finished.
	 *
	 *  # Index a specific post
	 *  $ wp asp index item --post_id=123
	 *  Success: Post ID:123 indexed
	 *
	 *  # Delete the index table
	 *  $ wp asp index delete
	 *  Success: Index table cleared
	 *
	 *  # Delete a specific post from the index table
	 *  $ wp asp index delete --post_id=123
	 *  Success: Post ID:123 deleted from index
	 *
	 * @subcommand index
	 * @when init
	 */
	public function index( array $args, array $flags ): void {
		if ( !isset($args[0]) ) {
			WP_CLI::error('No index table action provided');
			return;
		}

		$allowed_flags = array( 'skip-confirm', 'batch', 'post_id', 'check-responsiveness', 'sleep', 'separate-process' );
		$this->flags   = array_intersect_key($flags, array_flip($allowed_flags));
		if ( isset($this->flags['skip-confirm']) ) {
			$this->flags['skip-confirm'] = 1;
		}
		if ( isset($this->flags['batch']) ) {
			$this->flags['batch'] = max( 1, intval($this->flags['batch']) );
		}
		if ( isset($this->flags['post_id']) ) {
			$this->flags['post_id'] = intval($this->flags['post_id']);
		}

		if ( isset($this->flags['check-responsiveness']) ) {
			$this->flags['check-responsiveness'] = 1;
		}

		if ( isset($this->flags['separate-process']) ) {
			$this->flags['separate-process'] = 1;
		}

		$this->flags['sleep'] = isset($this->flags['sleep']) ? abs(intval($this->flags['sleep'])) : 0;

		switch ( $args[0] ) {
			case 'info':
				$this->info();
				break;
			case 'item':
				$this->indexItem();
				break;
			case 'new':
				$this->newIndex();
				break;
			case 'continue':
				$this->continueIndex();
				break;
			case 'index_batch':
				WP_CLI::line(wp_json_encode($this->indexBatch()));
				break;
			case 'delete':
				$this->deleteIndex();
				break;
			default:
				WP_CLI::error('Invalid index table action provided');
		}
	}

	protected function newIndex(): void {
		$this->deleteIndex();
		WP_CLI::line('Index table creation started.');
		$this->continueIndex();
	}

	protected function indexItem(): void {
		if ( isset($this->flags['post_id']) && $this->flags['post_id'] > 0 ) {
			if ( IndexService::instance()->indexPost($this->flags['post_id']) ) {
				WP_CLI::success("Post ID:{$this->flags['post_id']} indexed");
			} else {
				WP_CLI::error("Post ID:{$this->flags['post_id']} not found");
			}
		} else {
			WP_CLI::error('No post ID provided, use the --post_id=<post_id> argument.');
		}
	}

	protected function continueIndex(): void {
		$start                     = microtime(true);
		$total_indexed_since_start = 0;
		while ( true ) {
			if ( isset($this->flags['separate-process']) ) {
				$result = WP_CLI::runcommand(
					'asp index index_batch ' . implode( ' ', array_map(fn( $k ) => " --{$k}={$this->flags[$k]}", array_keys($this->flags)) ),
					array(
						'return' => 'stdout',
						'parse'  => 'json',
					),
				);
			} else {
				$result = $this->indexBatch();
			}
			if ( $result['postsIndexedNow'] === 0 ) {
				break;
			}
			$total_indexed_since_start += $result['postsIndexedNow'];
			$duration_since_start       = Str::secondsToTime( microtime(true) - $start);
			$estimated_time_left        = Str::secondsToTime(
				ceil(microtime(true) - $start) / $total_indexed_since_start * $result['postsToIndex']
			);
			$posts_per_s                = number_format( $total_indexed_since_start / ( microtime(true) - $start ), 2);
			WP_CLI::line("Indexed $total_indexed_since_start, remaining {$result['postsToIndex']}. ({$posts_per_s}/s, in {$duration_since_start}, est. time left: {$estimated_time_left})");

			if ( $result['postsToIndex'] === 0 ) {
				break;
			}

			if ( isset($this->flags['check-responsiveness']) && $this->flags['check-responsiveness'] ) {
				$this->sleepUntilDatabaseReady();
			}

			if ( $this->flags['sleep'] > 0 ) {
				usleep($this->flags['sleep'] * 1000);
				WP_CLI::line("Sleeping for {$this->flags['sleep']} milliseconds");
			}
		}
		WP_CLI::success('Indexing finished for ' . IndexService::instance()->postsIndexed() . ' posts. Total keywords: ' . IndexService::instance()->totalKeywords());
	}

	protected function indexBatch(): array {
		if ( isset($this->flags['batch']) && $this->flags['batch'] > 0 ) {
			$result = IndexService::instance()->index(
				array(
					'limit' => $this->flags['batch'],
				)
			);
		} else {
			$result = IndexService::instance()->index();
		}

		return $result;
	}

	protected function deleteIndex(): void {
		if ( !isset($this->flags['skip-confirm']) || !$this->flags['skip-confirm'] ) {
			if ( isset($this->flags['post_id']) ) {
				WP_CLI::confirm("This will delete post ID:{$this->flags['post_id']} from the current index. Are you sure?");
			} else {
				WP_CLI::confirm('This will delete the current index. Are you sure?');
			}
		}
		if ( isset($this->flags['post_id']) ) {
			IndexService::instance()->delete($this->flags['post_id']);
			WP_CLI::success("Post ID:{$this->flags['post_id']} deleted from index");
		} else {
			IndexService::instance()->delete();
			WP_CLI::success('Index table cleared');
		}
	}

	protected function info(): void {
		$posts_indexed = IndexService::instance()->postsIndexed();
		WP_CLI::line('Indexed ' . $posts_indexed . ' of ' . ( $posts_indexed + IndexService::instance()->postsToIndexCount() ) . ' items.');
		WP_CLI::line('Total keywords: ' . IndexService::instance()->totalKeywords());
	}

	protected function sleepUntilDatabaseReady(): void {
		$max_checks = 10;
		for ( $i = 0; $i < $max_checks; $i++ ) {
			$responsiveness = Database::getResponsiveness();
			if ( $responsiveness > 0.01 ) {
				$sleep_for = 5 * $i;
				WP_CLI::line("Database might be under heavy load, waiting for {$sleep_for} to cool off.");
				sleep(5 * $i);
			} else {
				WP_CLI::line('Database is ready, continuing. (responsiveness:' . number_format($responsiveness, 4) . 's)');
				break;
			}
		}
	}
}
