<?php

namespace MediaWiki\Extension\AbuseFilter\VariableGenerator;

use LogicException;
use MediaWiki\Extension\AbuseFilter\Hooks\AbuseFilterHookRunner;
use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
use MediaWiki\FileRepo\RepoGroup;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\Page\WikiPageFactory;
use MediaWiki\RecentChanges\RecentChange;
use MediaWiki\Title\Title;
use MediaWiki\User\User;
use MediaWiki\User\UserFactory;
use MediaWiki\Utils\MWFileProps;
use Wikimedia\Mime\MimeAnalyzer;

/**
 * This class contains the logic used to create variable holders used to
 * examine a RecentChanges row.
 */
class RCVariableGenerator extends VariableGenerator {

	public function __construct(
		AbuseFilterHookRunner $hookRunner,
		UserFactory $userFactory,
		private readonly MimeAnalyzer $mimeAnalyzer,
		private readonly RepoGroup $repoGroup,
		private readonly WikiPageFactory $wikiPageFactory,
		private readonly RecentChange $rc,
		private readonly User $contextUser,
		?VariableHolder $vars = null
	) {
		parent::__construct( $hookRunner, $userFactory, $vars );
	}

	public function getVars(): ?VariableHolder {
		if ( $this->rc->getAttribute( 'rc_source' ) === RecentChange::SRC_LOG ) {
			switch ( $this->rc->getAttribute( 'rc_log_type' ) ) {
				case 'move':
					$this->addMoveVars();
					break;
				case 'newusers':
					$this->addCreateAccountVars();
					break;
				case 'delete':
					$this->addDeleteVars();
					break;
				case 'upload':
					$this->addUploadVars();
					break;
				default:
					return null;
			}
		} elseif ( $this->rc->getAttribute( 'rc_this_oldid' ) ) {
			// It's an edit (or a page creation).
			$this->addEditVarsForRow();
		} elseif (
			!$this->hookRunner->onAbuseFilterGenerateVarsForRecentChange(
				$this, $this->rc, $this->vars, $this->contextUser )
		) {
			// @codeCoverageIgnoreStart
			throw new LogicException( 'Cannot understand the given recentchanges row!' );
			// @codeCoverageIgnoreEnd
		}

		$this->addGenericVars( $this->rc );

		return $this->vars;
	}

	/**
	 * @return $this
	 */
	private function addMoveVars(): self {
		$userIdentity = $this->rc->getPerformerIdentity();

		$oldTitle = Title::castFromPageReference( $this->rc->getPage() ) ?: Title::makeTitle( NS_SPECIAL, 'BadTitle' );
		$newTitle = Title::newFromText( $this->rc->getParam( '4::target' ) );

		$this->addUserVars( $userIdentity, $this->rc )
			->addTitleVars( $oldTitle, 'moved_from', $this->rc )
			->addTitleVars( $newTitle, 'moved_to', $this->rc );

		$this->vars->setVar( 'summary', $this->rc->getAttribute( 'rc_comment' ) );
		$this->vars->setVar( 'action', 'move' );

		$this->vars->setLazyLoadVar(
			'moved_from_last_edit_age',
			'revision-age',
			[
				// rc_last_oldid is zero (RecentChangeFactory::createLogRecentChange)
				'revid' => $this->rc->getAttribute( 'rc_this_oldid' ),
				'parent' => true,
				'asof' => $this->rc->getAttribute( 'rc_timestamp' ),
			]
		);
		// TODO: add moved_to_last_edit_age (is it possible?)
		// TODO: add old_wikitext etc. (T320347)

		return $this;
	}

	/**
	 * @return $this
	 */
	private function addCreateAccountVars(): self {
		// XXX: as of 1.43, the following is never true
		$autocreate = $this->rc->getAttribute( 'rc_log_action' ) === 'autocreate';
		$this->vars->setVar( 'action', $autocreate ? 'autocreateaccount' : 'createaccount' );

		$name = Title::castFromPageReference( $this->rc->getPage() )->getText();
		// Add user data if the account was created by a registered user
		$userIdentity = $this->rc->getPerformerIdentity();
		if ( $userIdentity->isRegistered() && $name !== $userIdentity->getName() ) {
			$this->addUserVars( $userIdentity, $this->rc );
		} else {
			// Set the user_type so that creations of temporary accounts vs named accounts can be filtered for an
			// abuse filter that matches account creations.
			$this->vars->setLazyLoadVar(
				'user_type',
				'user-type',
				[ 'user-identity' => $userIdentity ]
			);
		}

		// $name is a valid title, so should pass the only check for UserFactory::RIGOR_NONE (the title does
		// not include a "#" character).
		$createdUser = $this->userFactory->newFromName( $name, UserFactory::RIGOR_NONE );
		if ( $createdUser === null ) {
			throw new \UnexpectedValueException( 'Failed to retrieve the created user' );
		}
		$this->vars->setVar( 'account_name', $name );
		$this->vars->setLazyLoadVar(
			'account_type',
			'account-type',
			[ 'autocreate' => $autocreate, 'createdUser' => $createdUser ]
		);

		$this->hookRunner->onAbuseFilterGenerateAccountCreationVars(
			$this->vars, $userIdentity, $createdUser, $autocreate, $this->rc
		);

		return $this;
	}

	/**
	 * @return $this
	 */
	private function addDeleteVars(): self {
		$title = Title::castFromPageReference( $this->rc->getPage() ) ?: Title::makeTitle( NS_SPECIAL, 'BadTitle' );
		$userIdentity = $this->rc->getPerformerIdentity();

		$this->addUserVars( $userIdentity, $this->rc )
			->addTitleVars( $title, 'page', $this->rc );

		$this->vars->setVar( 'action', 'delete' );
		$this->vars->setVar( 'summary', $this->rc->getAttribute( 'rc_comment' ) );
		// TODO: add page_last_edit_age
		// TODO: add old_wikitext etc. (T173663)

		return $this;
	}

	/**
	 * @return $this
	 */
	private function addUploadVars(): self {
		$title = Title::castFromPageReference( $this->rc->getPage() ) ?: Title::makeTitle( NS_SPECIAL, 'BadTitle' );
		$userIdentity = $this->rc->getPerformerIdentity();

		$this->addUserVars( $userIdentity, $this->rc )
			->addTitleVars( $title, 'page', $this->rc );

		$this->vars->setVar( 'action', 'upload' );
		$this->vars->setVar( 'summary', $this->rc->getAttribute( 'rc_comment' ) );

		$this->vars->setLazyLoadVar(
			'page_last_edit_age',
			'revision-age',
			[
				// rc_last_oldid is zero (RecentChangeFactory::createLogRecentChange)
				'revid' => $this->rc->getAttribute( 'rc_this_oldid' ),
				'parent' => true,
				'asof' => $this->rc->getAttribute( 'rc_timestamp' ),
			]
		);

		$time = $this->rc->getParam( 'img_timestamp' );
		$file = $this->repoGroup->findFile(
			$title, [ 'time' => $time, 'private' => $this->contextUser ]
		);
		if ( !$file ) {
			// @fixme Ensure this cannot happen!
			// @codeCoverageIgnoreStart
			$logger = LoggerFactory::getInstance( 'AbuseFilter' );
			$logger->warning( "Cannot find file from RC row with title $title" );
			return $this;
			// @codeCoverageIgnoreEnd
		}

		// This is the same as FilteredActionsHandler::filterUpload, but from a different source
		$this->vars->setVar( 'file_sha1', \Wikimedia\base_convert( $file->getSha1(), 36, 16, 40 ) );
		$this->vars->setVar( 'file_size', $file->getSize() );

		$this->vars->setVar( 'file_mime', $file->getMimeType() );
		$this->vars->setVar(
			'file_mediatype',
			$this->mimeAnalyzer->getMediaType( null, $file->getMimeType() )
		);
		$this->vars->setVar( 'file_width', $file->getWidth() );
		$this->vars->setVar( 'file_height', $file->getHeight() );

		$mwProps = new MWFileProps( $this->mimeAnalyzer );
		$bits = $mwProps->getPropsFromPath( $file->getLocalRefPath(), true )['bits'];
		$this->vars->setVar( 'file_bits_per_channel', $bits );

		$this->vars->setLazyLoadVar( 'new_wikitext', 'revision-text',
			[ 'revid' => $this->rc->getAttribute( 'rc_this_oldid' ), 'contextUser' => $this->contextUser ] );
		$this->vars->setLazyLoadVar( 'old_wikitext', 'revision-text',
			[
				// rc_last_oldid is zero (RecentChangeFactory::createLogRecentChange)
				'revid' => $this->rc->getAttribute( 'rc_this_oldid' ),
				'parent' => true,
				'contextUser' => $this->contextUser,
			] );

		$this->addDerivedVarsForTitle( $title );

		return $this;
	}

	private function addDerivedVarsForTitle( Title $title ) {
		$page = $this->wikiPageFactory->newFromTitle( $title );
		$this->addDerivedEditVars();

		// TODO: all these are legacy methods
		$this->vars->setLazyLoadVar( 'new_links', 'links-from-wikitext',
			[
				'text-var' => 'new_wikitext',
				'article' => $page,
				'forFilter' => false,
				'contextUserIdentity' => $this->contextUser
			] );

		// Note: this claims "or database" but it will never reach it
		$this->vars->setLazyLoadVar( 'old_links', 'links-from-wikitext-or-database',
			[
				'article' => $page,
				'text-var' => 'old_wikitext',
				'forFilter' => false,
				'contextUserIdentity' => $this->contextUser
			] );

		$this->vars->setLazyLoadVar( 'new_pst', 'parse-wikitext',
			[
				'wikitext-var' => 'new_wikitext',
				'article' => $page,
				'pst' => true,
				'contextUserIdentity' => $this->contextUser
			] );

		$this->vars->setLazyLoadVar( 'new_html', 'parse-wikitext',
			[
				'wikitext-var' => 'new_wikitext',
				'article' => $page,
				'contextUserIdentity' => $this->contextUser
			] );
	}

	/**
	 * @return $this
	 */
	private function addEditVarsForRow(): self {
		$title = Title::castFromPageReference( $this->rc->getPage() ) ?: Title::makeTitle( NS_SPECIAL, 'BadTitle' );
		$userIdentity = $this->rc->getPerformerIdentity();

		$this->addUserVars( $userIdentity, $this->rc )
			->addTitleVars( $title, 'page', $this->rc );

		$this->vars->setVar( 'action', 'edit' );
		$this->vars->setVar( 'summary', $this->rc->getAttribute( 'rc_comment' ) );

		$this->vars->setLazyLoadVar( 'new_wikitext', 'revision-text',
			[ 'revid' => $this->rc->getAttribute( 'rc_this_oldid' ), 'contextUser' => $this->contextUser ] );
		$this->vars->setLazyLoadVar( 'new_content_model', 'content-model',
			[ 'revid' => $this->rc->getAttribute( 'rc_this_oldid' ) ] );

		$parentId = $this->rc->getAttribute( 'rc_last_oldid' );
		if ( $parentId ) {
			$this->vars->setLazyLoadVar( 'old_wikitext', 'revision-text',
				[ 'revid' => $parentId, 'contextUser' => $this->contextUser ] );
			$this->vars->setLazyLoadVar( 'old_content_model', 'content-model',
				[ 'revid' => $parentId ] );
			$this->vars->setLazyLoadVar( 'page_last_edit_age', 'revision-age',
				[ 'revid' => $parentId, 'asof' => $this->rc->getAttribute( 'rc_timestamp' ) ] );
		} else {
			$this->vars->setVar( 'old_wikitext', '' );
			$this->vars->setVar( 'old_content_model', '' );
			$this->vars->setVar( 'page_last_edit_age', null );
		}

		$this->addDerivedVarsForTitle( $title );

		return $this;
	}
}
