<?php

namespace MediaWiki\Extension\Math\Tests;

use MediaWiki\Extension\Math\Rest\Popup;
use MediaWiki\Languages\LanguageFactory;
use MediaWiki\Languages\LanguageNameUtils;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\HttpException;
use MediaWiki\Rest\RequestData;
use MediaWiki\Tests\Rest\Handler\HandlerTestTrait;
use MediaWiki\Title\Title;
use MediaWiki\Title\TitleFactory;
use Wikibase\DataModel\Entity\Item;
use Wikibase\Lib\Store\EntityRevision;
use Wikimedia\ParamValidator\ParamValidator;

/**
 * @covers \MediaWiki\Extension\Math\Rest\Popup
 */
class PopupTest extends MathWikibaseConnectorTestFactory {

	use HandlerTestTrait;

	public function testNonExistingId() {
		$popupHandler = $this->getPopup();
		$response = $this->executeHandler( $popupHandler, $this->getRequest( '1', 'en' ) );
		$this->assertEquals( 400, $response->getStatusCode() );
		$data = json_decode( $response->getBody(), true );
		$this->assertEquals( 'Non-existing Wikibase ID.', $data[ 'message' ] );
	}

	public function testParameterSettingsSetup() {
		$popupHandler = $this->getPopup();
		$this->assertSame( [ 'qid' => [
			HANDLER::PARAM_SOURCE => 'path',
			ParamValidator::PARAM_TYPE => 'integer',
			ParamValidator::PARAM_REQUIRED => true,
		] ], $popupHandler->getParamSettings() );
	}

	public function testValidationExceptionForMalformedId() {
		$popupHandler = $this->getPopup();
		$this->initHandler( $popupHandler, $this->getRequest( 'Q1', 'en' ) );
		$this->expectException( HttpException::class );
		$this->validateHandler( $popupHandler );
	}

	public function testInvalidLanguage() {
		$languageNameUtilsMock = $this->createMock( LanguageNameUtils::class );
		$languageNameUtilsMock->expects( $this->once() )
			->method( 'isValidCode' )
			->with( 'tmp' )
			->willReturn( false );

		$popupHandler = $this->getPopup( null, $languageNameUtilsMock );
		$response = $this->executeHandler( $popupHandler, $this->getRequest( '1', 'tmp' ) );
		$this->assertEquals( 400, $response->getStatusCode() );
		$data = json_decode( $response->getBody(), true );
		$this->assertEquals( 'Invalid language code.', $data[ 'message' ] );
	}

	/**
	 * @dataProvider provideItemSetups
	 */
	public function testExistingId( Item $item ) {
		$popupHandler = $this->getPopup( null, null, $item );

		$request = $this->getRequest( '1', 'en' );
		$data = $this->executeHandlerAndGetBodyData( $popupHandler, $request );

		$this->assertArrayHasKey( 'title', $data );
		$this->assertArrayHasKey( 'canonicalurl', $data );
		$this->assertArrayHasKey( 'fullurl', $data );
		$this->assertArrayHasKey( 'contentmodel', $data );
		$this->assertArrayHasKey( 'pagelanguagedir', $data );
		$this->assertArrayHasKey( 'pagelanguage', $data );
		$this->assertArrayHasKey( 'pagelanguagehtmlcode', $data );
		$this->assertArrayHasKey( 'extract', $data );

		$this->assertEquals( 'mass–energy equivalence', $data[ 'title' ] );
		$this->assertEquals( 'special/Q1', $data[ 'canonicalurl' ] );
		$this->assertEquals( 'special/Q1', $data[ 'fullurl' ] );
		$this->assertEquals( 'html', $data[ 'contentmodel' ] );

		$html = $data[ 'extract' ];

		// popup contains formula label and description
		$this->assertStringContainsString( self::TEST_ITEMS[ 'Q1' ][0], $html );
		$this->assertStringContainsString( self::TEST_ITEMS[ 'Q1' ][1], $html );

		// popup contains formula and labels of each element
		foreach ( self::TEST_ITEMS as $key => $part ) {
			if ( $key === 'Q1' ) {
				// the formula itself is not shown in the popup, only its elements
				continue;
			}
			$this->assertStringContainsString( $part[0], $html );
			$this->assertStringContainsString( self::getExpectedMathML( $part[2] ), $html );
		}
	}

	private function getRequest( $id, $lang ): RequestData {
		return new RequestData( [
			'pathParams' => [ 'qid' => $id ],
			'headers' => [
				'Accept-Language' => $lang
			]
		] );
	}

	private function getPopup(
		LanguageFactory $languageFactoryMock = null,
		LanguageNameUtils $languageNameUtilsMock = null,
		Item $item = null
	): Popup {
		$languageFactoryMock = $languageFactoryMock ?: $this->createMock( LanguageFactory::class );
		if ( !$languageNameUtilsMock ) {
			$languageNameUtilsMock = $this->createMock( LanguageNameUtils::class );
			$languageNameUtilsMock->method( 'isValidCode' )->willReturn( true );
		}
		$mathWikibaseConnectorMock = $item ?
			$this->getWikibaseConnectorWithExistingItems( new EntityRevision( $item ) ) :
			$this->getWikibaseConnector( $languageFactoryMock, $languageNameUtilsMock );

		$titleMock = $this->createMock( Title::class );
		$titleMock->method( 'getLocalURL' )->willReturn( 'special/Q1' );
		$titleMock->method( 'getFullURL' )->willReturn( 'special/Q1' );
		$titleFactoryMock = $this->createMock( TitleFactory::class );
		$titleFactoryMock->expects( $this->once() )
			->method( 'newFromText' )
			->willReturn( $titleMock );

		return new Popup( $mathWikibaseConnectorMock, $languageFactoryMock, $languageNameUtilsMock, $titleFactoryMock );
	}

	public function provideItemSetups(): array {
		return [
			[ $this->setupMassEnergyEquivalenceItem( true ) ],
			[ $this->setupMassEnergyEquivalenceItem( false ) ],
		];
	}
}
