<?php

namespace CirrusSearch\Maintenance\Validators;

use CirrusSearch\ElasticaErrorHandler;
use CirrusSearch\Maintenance\Printer;
use Elastica\Exception\ExceptionInterface;
use Elastica\Index;
use Elastica\Mapping;
use MediaWiki\Language\RawMessage;
use MediaWiki\Status\Status;
use Wikimedia\Assert\Assert;

class MappingValidator extends Validator {
	/**
	 * @var Index
	 */
	private $index;

	/**
	 * @var string
	 */
	private $masterTimeout;

	/**
	 * @var bool
	 */
	private $optimizeIndexForExperimentalHighlighter;

	/**
	 * @var array
	 */
	private $availablePlugins;

	/**
	 * @var array
	 */
	private $mappingConfig;

	/**
	 * @todo this constructor takes way too much arguments - refactor
	 *
	 * @param Index $index
	 * @param string $masterTimeout
	 * @param bool $optimizeIndexForExperimentalHighlighter
	 * @param array $availablePlugins
	 * @param array $mappingConfig
	 * @param Printer|null $out
	 */
	public function __construct(
		Index $index,
		$masterTimeout,
		$optimizeIndexForExperimentalHighlighter,
		array $availablePlugins,
		array $mappingConfig,
		?Printer $out = null
	) {
		parent::__construct( $out );

		$this->index = $index;
		$this->masterTimeout = $masterTimeout;
		$this->optimizeIndexForExperimentalHighlighter = $optimizeIndexForExperimentalHighlighter;
		$this->availablePlugins = $availablePlugins;
		// Could be supported, but prefer consistency
		Assert::parameter( isset( $mappingConfig['properties'] ), '$mappingConfig',
			'Mapping types are no longer supported, properties must be top level' );
		$this->mappingConfig = $mappingConfig;
	}

	/**
	 * @return Status
	 */
	public function validate() {
		$this->outputIndented( "Validating mappings..." );
		if ( !$this->compareMappingToActual() ) {
			$action = new Mapping( $this->mappingConfig['properties'] );
			$action->setParam( "dynamic", false );

			try {
				$action->send( $this->index, [
					'master_timeout' => $this->masterTimeout,
				] );
				$this->output( "corrected\n" );
			} catch ( ExceptionInterface $e ) {
				$this->output( "failed!\n" );
				$message = ElasticaErrorHandler::extractMessage( $e );
				return Status::newFatal( new RawMessage(
					"Couldn't update existing mappings. You may need to reindex.\nHere is elasticsearch's error message: $message\n" ) );
			}
		}

		return Status::newGood();
	}

	/**
	 * Check that the mapping returned from Elasticsearch is as we want it.
	 *
	 * @return bool is the mapping good enough for us?
	 */
	private function compareMappingToActual() {
		$actualMappings = $this->index->getMapping();
		$this->output( "\n" );
		$this->outputIndented( "\tValidating mapping..." );
		if ( $this->checkConfig( $actualMappings, $this->mappingConfig ) ) {
			$this->output( "ok\n" );
			return true;
		} else {
			$this->output( "different..." );
			return false;
		}
	}
}
