<?php

namespace XF\Job;

use XF\Entity\Forum;
use XF\Entity\Reaction;
use XF\Entity\Thread;
use XF\Finder\ReactionFinder;
use XF\Finder\ThreadFinder;
use XF\ForumType\SuggestionHandler;
use XF\Repository\ContentVoteRepository;

use function count;

class SuggestionConvertReactions extends AbstractJob
{
	protected $defaultData = [
		'node_id' => null,

		'count' => 0,
		'last' => 0,
		'total' => null,
	];

	/**
	 * @var Forum
	 */
	protected $forum;

	/**
	 * @var null|Reaction[]
	 */
	protected $countableReactions;

	public function run($maxRunTime)
	{
		$s = microtime(true);

		if (!$this->data['node_id'])
		{
			throw new \InvalidArgumentException('Cannot change forum thread types without a node_id.');
		}

		/** @var Forum $forum */
		$forum = $this->app->find(Forum::class, $this->data['node_id']);
		if (!$forum || (!$forum->TypeHandler instanceof SuggestionHandler))
		{
			return $this->complete();
		}

		$this->forum = $forum;
		$typeHandler = $forum->TypeHandler;

		$threadFinder = $this->app->finder(ThreadFinder::class)
			->where('node_id', $this->data['node_id'])
			->where('discussion_type', $typeHandler->getDefaultThreadType($forum))
			->order('thread_id');

		if ($this->data['total'] === null)
		{
			$this->data['total'] = $threadFinder->total();
			if (!$this->data['total'])
			{
				return $this->complete();
			}
		}

		$threadFinder->where('thread_id', '>', $this->data['last']);

		$maxFetch = 1000;

		$threadIds = $threadFinder->pluckFrom('thread_id')->fetch($maxFetch)->toArray();
		if (!$threadIds)
		{
			return $this->complete();
		}

		$continue = count($threadIds) < $maxFetch ? false : true;

		$this->countableReactions = \XF::finder(ReactionFinder::class)
			->where('reaction_score', '<>', 0)
			->fetch()
			->toArray();

		foreach ($threadIds AS $threadId)
		{
			$this->data['count']++;
			$this->data['last'] = $threadId;

			/** @var Thread $thread */
			$thread = $this->app->find(Thread::class, $threadId);
			if (!$thread)
			{
				continue;
			}

			$forum = $thread->Forum;

			// sanity check - shouldn't fail
			if ($thread->discussion_type !== $typeHandler->getDefaultThreadType($forum))
			{
				continue;
			}

			$this->convertReactions($thread);

			if ($maxRunTime && microtime(true) - $s > $maxRunTime)
			{
				$continue = true;
				break;
			}
		}

		return $continue ? $this->resume() : $this->complete();
	}

	protected function convertReactions(Thread $thread)
	{
		$firstPostId = $thread->first_post_id;
		if (!$firstPostId)
		{
			return;
		}

		$db = \XF::db();

		$reactions = $db->fetchAll("
			SELECT reaction_user_id, reaction_id, reaction_date
			FROM xf_reaction_content
			WHERE content_type = 'post' AND content_id = ?
		", $firstPostId);
		if (!$reactions)
		{
			return;
		}

		$allowDownvote = $this->forum->type_config['allow_downvote'];

		$voteRepo = \XF::repository(ContentVoteRepository::class);
		$isContentUserCounted = $voteRepo->getVoteHandler('thread')->isCountedForContentUser($thread);

		$inserts = [];

		foreach ($reactions AS $reaction)
		{
			$reactionDef = $this->countableReactions[$reaction['reaction_id']] ?? null;

			if (!$reactionDef)
			{
				// deleted reaction or a neutral reaction
				continue;
			}

			if ($reactionDef['reaction_score'] < 0 && !$allowDownvote)
			{
				// negative reaction but no downvotes allowed
				continue;
			}

			$inserts[] = [
				'content_type' => 'thread',
				'content_id' => $thread->thread_id,
				'vote_user_id' => $reaction['reaction_user_id'],
				'content_user_id' => $thread->user_id,
				'is_content_user_counted' => $isContentUserCounted ? 1 : 0,
				'score' => $reactionDef['reaction_score'] > 0 ? 1 : -1,
				'vote_date' => $reaction['reaction_date'],
			];
		}

		if (!$inserts)
		{
			return;
		}

		$db->beginTransaction();

		$db->insertBulk('xf_content_vote', $inserts, false, false, 'IGNORE');
		$voteRepo->rebuildVoteCache('thread', $thread->thread_id);

		$db->commit();
	}

	public function getStatusMessage()
	{
		$actionPhrase = \XF::phrase('converting_reactions');
		return sprintf(
			'%s... (%s/%s)',
			$actionPhrase,
			\XF::language()->numberFormat($this->data['count']),
			\XF::language()->numberFormat($this->data['total'])
		);
	}

	public function canCancel()
	{
		return true;
	}

	public function canTriggerByChoice()
	{
		return false;
	}
}
