<?php

namespace CirrusSearch;

use MediaWiki\Logger\LoggerFactory;

/**
 * Extending from SearchRequestLog doesn't quite feel right, but there
 * is a good amount of shared code. think about best way.
 */
class MultiSearchRequestLog extends SearchRequestLog {

	/**
	 * Not sure what's best to return here, primarily we need whatever
	 * would be interesting when looking at error logging. For now this
	 * just generates a context for the first request and ignores the
	 * existence of the rest.
	 *
	 * Basically this is known to be wrong, but not sure what to do instead.
	 *
	 * @return array
	 */
	public function getLogVariables() {
		$vars = [
			'queryType' => $this->queryType,
			'tookMs' => $this->getTookMs(),
		] + $this->extra;

		if ( !$this->request || !$this->response ) {
			return $vars;
		}

		// In a multi-search instance the plain string, as sent to elasticsearch,
		// is returned by Request::getData(). Each single request is represented
		// by two lines, first a metadata line about the request and second the
		// actual query.
		$lines = explode( "\n", trim( $this->request->getData(), "\n" ) );
		$vars += $this->extractRequestVariables(
			array_slice( $lines, 0, 2 )
		);

		$responseData = $this->response->getData();
		if ( !empty( $responseData['responses'] ) ) {
			// Have to use + $vars, rather than +=, to
			// allow 'suggestion' returned from here to
			// override 'suggestion' provided by $this->extra
			$vars = $this->extractResponseVariables(
				reset( $responseData['responses'] )
			) + $vars;
		}

		// in case of failures from Elastica
		if ( isset( $responseData['message'] ) ) {
			$vars['error_message'] = $responseData['message'];
		}

		return $vars;
	}

	/**
	 * @return array[]
	 */
	public function getRequests() {
		if ( !$this->request || !$this->response ) {
			// we don't actually know at this point how many searches there were,
			// or how many results to return...so just bail and return nothing
			return [];
		}

		$responseData = $this->response->getData();
		if ( !$responseData || !isset( $responseData['responses'] ) ) {
			$message = $responseData['message'] ?? 'no message';
			LoggerFactory::getInstance( 'CirrusSearch' )->warning(
				'Elasticsearch response does not have any data. {response_message}',
				[ 'response_message' => $message ]
			);
			return [];
		}

		// In a multi-search instance the plain string, as sent to elasticsearch,
		// is returned by Request::getData(). Each single request is represented
		// by two lines, first a metadata line about the request and second the
		// actual query.
		$lines = explode( "\n", trim( $this->request->getData(), "\n" ) );
		$requestData = array_chunk( $lines, 2 );

		if ( count( $requestData ) !== count( $responseData['responses'] ) ) {
			// The world has ended...:(
			// @todo add more context.
			throw new \RuntimeException( 'Request and response data does not match' );
		}

		$meta = [
			'queryType' => $this->queryType,
			'tookMs' => $this->getTookMs(),
		] + $this->extra;
		$requests = [];
		foreach ( $responseData['responses'] as $singleResponseData ) {
			$vars = $this->extractRequestVariables( array_shift( $requestData ) ) +
				$this->extractResponseVariables( $singleResponseData );
			$vars['hits'] = $this->extractHits( $singleResponseData );
			// + $meta must come *after* extractResponseVariables, because
			// items like 'suggestion' override data provided in $this->extra
			$requests[] = $vars + $meta;
		}

		return $requests;
	}

	/**
	 * @param array $requestData
	 * @return array
	 */
	protected function extractRequestVariables( $requestData ) {
		// @todo error check decode
		$meta = json_decode( $requestData[0], true );
		$query = json_decode( $requestData[1], true );

		return [
			// @phan-suppress-next-line PhanTypeArraySuspiciousNullable
			'index' => implode( ',', $meta['index'] ),
		] + parent::extractRequestVariables( $query );
	}
}
