<?php

namespace XF\Repository;

use XF\Entity\Poll;
use XF\Entity\PollResponse;
use XF\Entity\User;
use XF\Finder\PollResponseFinder;
use XF\Mvc\Entity\Repository;
use XF\Poll\AbstractHandler;

use function count, is_array;

class PollRepository extends Repository
{
	public function resetPollVotes(Poll $poll)
	{
		$db = $this->db();

		$db->beginTransaction();

		$db->delete('xf_poll_vote', 'poll_id = ?', $poll->poll_id);
		$this->rebuildPollData($poll->poll_id);

		$db->commit();
	}

	public function voteOnPoll(Poll $poll, $votes, ?User $voter = null)
	{
		$voter = $voter ?: \XF::visitor();
		$responses = $poll->Responses;

		if (!is_array($votes))
		{
			$votes = [$votes];
		}

		foreach ($votes AS $k => $responseId)
		{
			if (!isset($responses[$responseId]))
			{
				unset($votes[$k]);
			}
		}

		if (!$votes)
		{
			return false;
		}

		$db = $this->db();

		$db->beginTransaction();

		$rawPoll = $db->fetchRow("
			SELECT *
			FROM xf_poll
			WHERE poll_id = ?
			FOR UPDATE
		", $poll->poll_id);

		$previousVotes = $db->delete('xf_poll_vote', 'poll_id = ? AND user_id = ?', [$poll->poll_id, $voter->user_id]);
		$newVoter = ($previousVotes == 0);

		foreach ($votes AS $responseId)
		{
			// votes have been validated against this poll
			/** @var PollResponse $response */
			$response = $responses[$responseId];

			$inserted = $db->insert('xf_poll_vote', [
				'user_id' => $voter->user_id,
				'poll_response_id' => $responseId,
				'poll_id' => $poll->poll_id,
				'vote_date' => \XF::$time,
			], false, false, 'IGNORE');
			if ($newVoter && $inserted)
			{
				// can take a short cut as we know they haven't voted before
				// otherwise we need to do a rebuild
				$response->updateForNewVote($voter);
			}
		}

		$poll->clearCache('Votes');

		if ($newVoter)
		{
			$poll->voter_count = $rawPoll['voter_count'] + 1;
			$poll->save(); // triggers the response cache rebuild
		}
		else
		{
			$this->rebuildPollData($poll->poll_id);
		}

		$pollHandler = $this->getPollHandler($poll->content_type, false);
		if ($pollHandler)
		{
			$entity = $pollHandler->getContent($poll->content_id);
			if ($entity)
			{
				$this->repository(WebhookRepository::class)->queueWebhook(
					$poll->content_type,
					$poll->content_id,
					'poll_vote',
					$entity
				);
			}
		}

		$db->commit();

		return true;
	}

	public function rebuildPollData($pollId)
	{
		$poll = $this->em->find(Poll::class, $pollId);
		if (!$poll)
		{
			return false;
		}

		$db = $this->db();

		$results = $db->fetchAll("
			SELECT *
			FROM xf_poll_vote
			WHERE poll_id = ?
		", $poll->poll_id);

		$votes = [];
		$voters = [];

		foreach ($results AS $vote)
		{
			$votes[$vote['poll_response_id']][$vote['user_id']] = true;
			$voters[$vote['user_id']] = true;
		}

		$db->beginTransaction();

		/** @var PollResponse $response */
		foreach ($poll->Responses AS $response)
		{
			$responseId = $response->poll_response_id;
			$responseVotes = $votes[$responseId] ?? [];

			$response->fastUpdate([
				'response_vote_count' => count($responseVotes),
				'voters' => $responseVotes,
			]);
		}

		$voters = count($voters);
		$responseCache = $this->getResponseCacheData($poll->poll_id);

		$poll->fastUpdate([
			'voter_count' => $voters,
			'responses' => $responseCache,
		]);

		$db->commit();

		return true;
	}

	public function getResponseCacheData($pollId)
	{
		$cache = [];

		$responses = $this->finder(PollResponseFinder::class)
			->where('poll_id', $pollId)
			->order('poll_response_id');

		foreach ($responses->fetch() AS $response)
		{
			$cache[$response->poll_response_id] = [
				'response' => $response->response,
				'response_vote_count' => $response->response_vote_count,
				'voters' => $response->voters,
			];
		}

		return $cache;
	}

	/**
	 * @return AbstractHandler[]
	 */
	public function getPollHandlers()
	{
		$handlers = [];

		foreach (\XF::app()->getContentTypeField('poll_handler_class') AS $contentType => $handlerClass)
		{
			if (class_exists($handlerClass))
			{
				$handlerClass = \XF::extendClass($handlerClass);
				$handlers[$contentType] = new $handlerClass($contentType);
			}
		}

		return $handlers;
	}

	/**
	 * @param string $type
	 * @param bool $throw
	 *
	 * @return AbstractHandler|null
	 */
	public function getPollHandler($type, $throw = false)
	{
		$handlerClass = \XF::app()->getContentTypeFieldValue($type, 'poll_handler_class');
		if (!$handlerClass)
		{
			if ($throw)
			{
				throw new \InvalidArgumentException("No poll handler for '$type'");
			}
			return null;
		}

		if (!class_exists($handlerClass))
		{
			if ($throw)
			{
				throw new \InvalidArgumentException("Poll handler for '$type' does not exist: $handlerClass");
			}
			return null;
		}

		$handlerClass = \XF::extendClass($handlerClass);
		return new $handlerClass($type);
	}
}
