<?php

namespace WPDRMS\ASP\Index;

use Exception;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WPDRMS\ASP\Index\ORM\IndexTable;
use WPDRMS\ASP\Index\ORM\IndexTableOptions;
use WPDRMS\ASP\Media\RemoteService\License;
use WPDRMS\PluginCore\Rest\AbstractRest;

class IndexTableRoute extends AbstractRest {
	public function registerRoutes( string $route_namespace ): void {
		register_rest_route(
			$route_namespace,
			'options/index',
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'getOptions' ),
					'permission_callback' => array( $this, 'allowOnlyAdmins' ),
				),
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'saveOptions' ),
					'permission_callback' => array( $this, 'allowOnlyAdmins' ),
				),
			),
		);

		register_rest_route(
			$route_namespace,
			'options/index/reset',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'resetOptions' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/new',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'newIndex' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/extend',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'extendIndex' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/delete',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'deleteIndex' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/optimize',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'optimizeIndex' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/stats',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'getStats' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/media-service',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'getMediaServiceStatus' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/media-service/activate',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'activateMediaService' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/media-service/deactivate',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'deactivateMediaService' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/history',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'getHistory' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);

		register_rest_route(
			$route_namespace,
			'index/keyword-history',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'getKeywordHistory' ),
				'permission_callback' => array( $this, 'allowOnlyAdmins' ),
			),
		);
	}

	/** @return WP_Error|WP_REST_Response */
	public function getOptions() {
		try {
			return new WP_REST_Response( IndexTableOptions::instance()->jsonSerialize(), 200 );
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_options_get', $e->getMessage() );
		}
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_Error|WP_REST_Response
	 */
	public function saveOptions( WP_REST_Request $request ) {
		try {
			$params  = self::sanitizeParams( (array) $request->get_json_params() );
			$options = IndexTableOptions::instance();
			$options->setArgs( $params )->save()->load();
			return new WP_REST_Response( $options->jsonSerialize(), 200 );
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_options_save', $e->getMessage() );
		}
	}

	/** @return WP_Error|WP_REST_Response */
	public function resetOptions() {
		try {
			$options = IndexTableOptions::instance();
			$options->saveDefaults()->load();
			return new WP_REST_Response( $options->jsonSerialize(), 200 );
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_options_reset', $e->getMessage() );
		}
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_Error|WP_REST_Response
	 */
	public function newIndex( WP_REST_Request $request ) {
		try {
			$params = (array) $request->get_json_params();
			$limit  = $this->adaptiveLimit(
				(int) ( $params['limit'] ?? IndexTableOptions::instance()->limit->value ),
				(float) ( $params['last_request_duration'] ?? 0 ),
				(float) ( $params['longest_request_duration'] ?? 0 )
			);
			IndexService::instance()->delete( null );
			return $this->doExtend( $limit );
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_new', $e->getMessage() );
		}
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_Error|WP_REST_Response
	 */
	public function extendIndex( WP_REST_Request $request ) {
		try {
			$params = (array) $request->get_json_params();
			$limit  = $this->adaptiveLimit(
				(int) ( $params['limit'] ?? IndexTableOptions::instance()->limit->value ),
				(float) ( $params['last_request_duration'] ?? 0 ),
				(float) ( $params['longest_request_duration'] ?? 0 )
			);
			return $this->doExtend( $limit );
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_extend', $e->getMessage() );
		}
	}

	/** @return WP_Error|WP_REST_Response */
	public function deleteIndex() {
		try {
			IndexService::instance()->delete( null );
			return new WP_REST_Response( array( 'success' => true ), 200 );
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_delete', $e->getMessage() );
		}
	}

	/** @return WP_Error|WP_REST_Response */
	public function optimizeIndex() {
		try {
			return new WP_REST_Response( array( 'success' => IndexTable::optimize() ), 200 );
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_optimize', $e->getMessage() );
		}
	}

	/** @return WP_Error|WP_REST_Response */
	public function getStats() {
		try {
			$service = IndexService::instance();
			return new WP_REST_Response(
				array(
					'postsIndexed'  => $service->postsIndexed(),
					'postsToIndex'  => $service->postsToIndexCount(),
					'totalKeywords' => $service->totalKeywords(),
				),
				200
			);
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_stats', $e->getMessage() );
		}
	}

	/** @return WP_Error|WP_REST_Response */
	public function getHistory() {
		try {
			return new WP_REST_Response(
				(array) get_site_option( 'asp_index_size_history', array() ),
				200
			);
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_history', $e->getMessage() );
		}
	}

	/** @return WP_Error|WP_REST_Response */
	public function getKeywordHistory() {
		try {
			return new WP_REST_Response(
				(array) get_site_option( 'asp_index_keyword_history', array() ),
				200
			);
		} catch ( Exception $e ) {
			return new WP_Error( 'asp_index_keyword_history', $e->getMessage() );
		}
	}

	/** @return WP_REST_Response */
	public function getMediaServiceStatus() {
		$license = License::getInstance();
		$data    = $license->getData();
		return new WP_REST_Response(
			array(
				'active'  => $license->active(),
				'is_free' => $license->isFree(),
				'license' => $data['license'] ? str_repeat( '*', max( 0, strlen( $data['license'] ) - 8 ) ) . substr( $data['license'], -8 ) : '',
				'stats'   => $data['stats'],
			),
			200
		);
	}

	/**
	 * @param WP_REST_Request $request
	 * @return WP_Error|WP_REST_Response
	 */
	public function activateMediaService( WP_REST_Request $request ) {
		$params  = (array) $request->get_json_params();
		$key     = sanitize_text_field( $params['license_key'] ?? '' );
		if ( $key === '' ) {
			return new WP_Error( 'asp_ms_activate', __( 'License key is required.', 'ajax-search-pro' ) );
		}
		$result = License::getInstance()->activate( $key );
		if ( !$result['success'] ) {
			return new WP_Error( 'asp_ms_activate', $result['text'] );
		}
		return $this->getMediaServiceStatus();
	}

	/** @return WP_REST_Response */
	public function deactivateMediaService() {
		License::getInstance()->delete();
		return new WP_REST_Response( array( 'active' => false, 'license' => '', 'is_free' => true, 'stats' => array() ), 200 );
	}

	private function doExtend( ?int $limit = null ): WP_REST_Response {
		$service = IndexService::instance();
		$args    = IndexTableOptions::instance()->toManagerArgs();
		if ( $limit !== null ) {
			$args['limit'] = $limit;
		}
		$service->index( $args );
		if ( $service->postsToIndexCount() === 0 ) {
			$service->recordDailySnapshot();
			delete_option( 'asp_synonyms_index_stale' );
		}
		return new WP_REST_Response(
			array(
				'postsIndexed'  => $service->postsIndexed(),
				'postsToIndex'  => $service->postsToIndexCount(),
				'totalKeywords' => $service->totalKeywords(),
			),
			200
		);
	}

	private function adaptiveLimit( int $current_limit, float $last_duration, float $longest_duration ): int {
		if ( $last_duration <= 0 || $longest_duration <= 0 ) {
			return $current_limit;
		}
		if ( $last_duration > 20 ) {
			return max( 1, (int) ceil( $current_limit / 2 ) );
		}
		if ( $last_duration < 5 && $current_limit < 200 ) {
			return min( 200, $current_limit + 5 );
		}
		return $current_limit;
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private static function sanitizeParams( array $params ): array {
		if ( isset( $params['cf_exclusion_rules']['logic'] ) ) {
			$params['cf_exclusion_rules']['logic'] = in_array( $params['cf_exclusion_rules']['logic'], array( 'AND', 'OR' ), true )
				? $params['cf_exclusion_rules']['logic'] : 'AND';
		}
		if ( isset( $params['cf_exclusion_rules']['rules'] ) && is_array( $params['cf_exclusion_rules']['rules'] ) ) {
			$valid_operators   = array( 'equals', 'not_equals', 'contains', 'not_contains', 'starts_with', 'ends_with', 'greater_than', 'less_than', 'is_empty', 'is_not_empty' );
			$valid_field_types = array( 'text', 'number', 'date', 'boolean', 'array' );
			$clean             = array();
			foreach ( $params['cf_exclusion_rules']['rules'] as $rule ) {
				$field_key = sanitize_key( $rule['field_key'] ?? '' );
				if ( $field_key === '' ) {
					continue;
				}
				$clean[] = array(
					'id'         => sanitize_text_field( $rule['id'] ?? '' ),
					'field_key'  => $field_key,
					'field_type' => in_array( $rule['field_type'] ?? '', $valid_field_types, true ) ? $rule['field_type'] : 'text',
					'operator'   => in_array( $rule['operator'] ?? '', $valid_operators, true ) ? $rule['operator'] : 'equals',
					'value'      => sanitize_text_field( $rule['value'] ?? '' ),
				);
			}
			$params['cf_exclusion_rules']['rules'] = $clean;
		}
		if ( isset( $params['taxonomy_exclusion_rules']['logic'] ) ) {
			$params['taxonomy_exclusion_rules']['logic'] = in_array( $params['taxonomy_exclusion_rules']['logic'], array( 'AND', 'OR' ), true )
				? $params['taxonomy_exclusion_rules']['logic'] : 'AND';
		}
		if ( isset( $params['taxonomy_exclusion_rules']['rules'] ) && is_array( $params['taxonomy_exclusion_rules']['rules'] ) ) {
			$clean = array();
			foreach ( $params['taxonomy_exclusion_rules']['rules'] as $rule ) {
				$taxonomy = sanitize_key( $rule['taxonomy'] ?? '' );
				if ( $taxonomy === '' ) {
					continue;
				}
				$clean[] = array(
					'id'       => sanitize_text_field( $rule['id'] ?? '' ),
					'taxonomy' => $taxonomy,
					'terms'    => array_map( 'intval', (array) ( $rule['terms'] ?? array() ) ),
				);
			}
			$params['taxonomy_exclusion_rules']['rules'] = $clean;
		}
		foreach ( array( 'post_types', 'custom_fields', 'index_taxonomies' ) as $key ) {
			if ( isset( $params[ $key ]['value'] ) && is_array( $params[ $key ]['value'] ) ) {
				$params[ $key ]['value'] = array_map( 'sanitize_text_field', $params[ $key ]['value'] );
			}
		}
		// Author / manual-post exclusions: lists of positive integer IDs.
		foreach ( array( 'author_exclusion', 'post_exclusion' ) as $key ) {
			if ( isset( $params[ $key ]['value'] ) && is_array( $params[ $key ]['value'] ) ) {
				$ids                     = array_map( 'intval', $params[ $key ]['value'] );
				$params[ $key ]['value'] = array_values(
					array_unique(
						array_filter(
							$ids,
							static function ( $id ) {
								return $id > 0;
							}
						)
					)
				);
			}
		}
		// Date exclusion: column enum, a global cutoff, and "post_type|YYYY-MM-DD" overrides.
		if ( isset( $params['date_exclusion_field']['value'] ) ) {
			$params['date_exclusion_field']['value'] = $params['date_exclusion_field']['value'] === 'post_modified' ? 'post_modified' : 'post_date';
		}
		if ( isset( $params['date_exclusion_global']['value'] ) ) {
			$params['date_exclusion_global']['value'] = self::sanitizeDate( (string) $params['date_exclusion_global']['value'] );
		}
		if ( isset( $params['date_exclusion_overrides']['value'] ) && is_array( $params['date_exclusion_overrides']['value'] ) ) {
			$clean = array();
			foreach ( $params['date_exclusion_overrides']['value'] as $entry ) {
				$entry = (string) $entry;
				$pos   = strpos( $entry, '|' );
				if ( $pos === false ) {
					continue;
				}
				$ptype = sanitize_key( substr( $entry, 0, $pos ) );
				$date  = self::sanitizeDate( substr( $entry, $pos + 1 ) );
				if ( $ptype !== '' && $date !== '' ) {
					$clean[] = $ptype . '|' . $date;
				}
			}
			$params['date_exclusion_overrides']['value'] = $clean;
		}
		return $params;
	}

	/**
	 * Returns the value only if it is a strict YYYY-MM-DD date, otherwise ''.
	 *
	 * @param string $value
	 */
	private static function sanitizeDate( string $value ): string {
		$value = trim( $value );
		return preg_match( '/^\d{4}-\d{2}-\d{2}$/', $value ) ? $value : '';
	}
}
