<?php

namespace XF\Entity;

use XF\Api\Result\EntityResult;
use XF\Mvc\Entity\Entity;
use XF\Mvc\Entity\Structure;
use XF\Poll\AbstractHandler;
use XF\Poll\ResponseEditor;
use XF\Repository\PollRepository;

use function intval, is_int;

/**
 * COLUMNS
 * @property int|null $poll_id
 * @property string $content_type
 * @property int $content_id
 * @property string $question
 * @property string $question_
 * @property array $responses
 * @property int $voter_count
 * @property bool $public_votes
 * @property int $max_votes
 * @property int $close_date
 * @property bool $change_vote
 * @property bool $view_results_unvoted
 *
 * GETTERS
 * @property-read AbstractHandler|null $Handler
 * @property-read Entity|null $Content
 *
 * RELATIONS
 * @property-read \XF\Mvc\Entity\AbstractCollection<\XF\Entity\PollResponse> $Responses
 * @property-read \XF\Mvc\Entity\AbstractCollection<\XF\Entity\PollVote> $Votes
 */
class Poll extends Entity
{
	public function isClosed()
	{
		return ($this->close_date && $this->close_date < \XF::$time);
	}

	public function canVote(&$error = null)
	{
		if ($this->isClosed())
		{
			return false;
		}

		$visitor = \XF::visitor();
		if (!$visitor->user_id)
		{
			return false;
		}

		$handler = $this->Handler;
		$content = $this->Content;
		if (!$handler || !$content || !$handler->canVote($content, $this, $error))
		{
			return false;
		}

		if ($this->change_vote)
		{
			return true;
		}
		else
		{
			return !$this->hasVoted();
		}
	}

	public function canViewResults(&$error = null)
	{
		return ($this->view_results_unvoted || $this->isClosed() || $this->hasVoted());
	}

	public function canViewContent(&$error = null)
	{
		$handler = $this->getHandler();
		$content = $this->getContent();
		return ($content && $handler) ? $handler->canViewContent($content, $error) : false;
	}

	public function canEdit(&$error = null)
	{
		$handler = $this->Handler;
		$content = $this->Content;
		return ($handler && $content && $handler->canEdit($content, $this, $error));
	}

	public function canDelete(&$error = null)
	{
		$handler = $this->Handler;
		$content = $this->Content;
		return ($handler && $content && $handler->canDelete($content, $this, $error));
	}

	public function canEditDetails(&$error = null)
	{
		$handler = $this->Handler;
		$content = $this->Content;
		if ($handler && $content && $handler->canAlwaysEditDetails($content, $this, $error))
		{
			return true;
		}

		return ($this->voter_count == 0);
	}

	public function canEditMaxVotes(&$error = null)
	{
		return ($this->max_votes != 0 || !$this->voter_count);
	}

	public function canChangePollVisibility(&$error = null)
	{
		return ($this->public_votes || !$this->voter_count);
	}

	public function hasVoted($responseId = null, ?User $user = null)
	{
		$user = $user ?: \XF::visitor();
		$userId = $user->user_id;

		if (!$userId)
		{
			return false;
		}

		$responses = $this->responses;
		if ($responseId !== null)
		{
			return isset($responses[$responseId]['voters'][$userId]);
		}
		else
		{
			foreach ($responses AS $response)
			{
				if (isset($response['voters'][$userId]))
				{
					return true;
				}
			}

			return false;
		}
	}

	public function getVotePercentage($totalVotes)
	{
		if ($this->voter_count)
		{
			return ($totalVotes / $this->voter_count) * 100;
		}
		else
		{
			return 0;
		}
	}

	/**
	 * @return ResponseEditor
	 */
	public function getResponseEditor()
	{
		return new ResponseEditor($this);
	}

	/**
	 * @return AbstractHandler|null
	 */
	public function getHandler()
	{
		return $this->getPollRepo()->getPollHandler($this->content_type);
	}

	/**
	 * @return Entity|null
	 */
	public function getContent()
	{
		$handler = $this->getHandler();
		return $handler ? $handler->getContent($this->content_id) : null;
	}

	public function setContent(?Entity $content = null)
	{
		$this->_getterCache['Content'] = $content;
	}

	public function getLink($action, $extraParams = [])
	{
		$handler = $this->getHandler();
		$content = $this->getContent();
		return $content ? $handler->getPollLink($action, $content, $extraParams) : null;
	}

	public function setMaxVotes($type, $value = null)
	{
		if (is_int($type))
		{
			$value = $type;
			$type = 'number';
		}

		switch ($type)
		{
			case 'single':
				$this->max_votes = 1;
				break;

			case 'unlimited':
				$this->max_votes = 0;
				break;

			default:
				if ($value === null)
				{
					$value = $type;
				}
				$this->max_votes = max(0, intval($value));
				break;
		}
	}

	public function setCloseDateRelative($value, $unit)
	{
		$value = max(0, intval($value));
		if (!$value)
		{
			$this->error(\XF::phraseDeferred('close_poll_after_value_nonsense'), 'close_length');
			return;
		}

		$this->close_date = min(
			2 ** 32 - 1,
			strtotime("+$value $unit")
		);
	}

	protected function _preSave()
	{
		if ($this->isUpdate()
			&& $this->isChanged('max_votes')
			&& $this->voter_count
			&& $this->max_votes != 0 // unlimited is always ok
		)
		{
			$newMax = $this->max_votes;
			$oldMax = $this->getExistingValue('max_votes');
			if ($oldMax == 0 || $newMax < $oldMax)
			{
				$this->error(\XF::phraseDeferred('maximum_selectable_responses_value_may_not_be_reduced'), 'max_votes');
			}
		}

		if ($this->isUpdate() && $this->isChanged('voter_count'))
		{
			$this->responses = $this->getPollRepo()->getResponseCacheData($this->poll_id);
		}
	}

	protected function _postSave()
	{
		if ($this->isInsert())
		{
			$content = $this->Content;
			if ($content)
			{
				$this->Handler->finalizeCreation($content, $this);
			}
		}
	}

	protected function _postDelete()
	{
		$id = $this->poll_id;

		$this->db()->delete('xf_poll_response', 'poll_id = ?', $id);
		$this->db()->delete('xf_poll_vote', 'poll_id = ?', $id);

		$content = $this->Content;
		if ($content && $content->exists())
		{
			$this->Handler->finalizeDeletion($content, $this);
		}
	}

	/**
	 * @param EntityResult $result
	 * @param int $verbosity
	 * @param array $options
	 *
	 * @api-out bool $can_vote
	 * @api-out bool $has_voted
	 * @api-out array $responses List of possible responses with text, vote count (if visible) and whether the API user has voted for each
	 */
	protected function setupApiResultData(
		EntityResult $result,
		$verbosity = self::VERBOSITY_NORMAL,
		array $options = []
	)
	{
		$responses = [];

		$canViewResults = $this->canViewResults();

		foreach ($this->responses AS $responseId => $response)
		{
			$value = [
				'text' => $response['response'],
				'vote_count' => $canViewResults ? $response['response_vote_count'] : null,
				'visitor_voted_for' => $this->hasVoted($responseId),
			];

			$responses[$responseId] = $value;
		}

		$result->responses = $responses;
		$result->has_voted = $this->hasVoted();
		$result->can_vote = $this->canVote();
	}

	public static function getStructure(Structure $structure)
	{
		$structure->table = 'xf_poll';
		$structure->shortName = 'XF:Poll';
		$structure->contentType = 'poll';
		$structure->primaryKey = 'poll_id';
		$structure->columns = [
			'poll_id' => ['type' => self::UINT, 'autoIncrement' => true, 'nullable' => true],
			'content_type' => ['type' => self::STR, 'maxLength' => 25, 'required' => true],
			'content_id' => ['type' => self::UINT, 'required' => true],
			'question' => ['type' => self::STR, 'maxLength' => 100,
				'required' => 'please_enter_poll_question',
				'censor' => true,
				'api' => true,
			],
			'responses' => ['type' => self::JSON_ARRAY, 'default' => []],
			'voter_count' => ['type' => self::UINT, 'default' => 0, 'api' => true],
			'public_votes' => ['type' => self::BOOL, 'default' => false, 'api' => true],
			'max_votes' => ['type' => self::UINT, 'default' => 1, 'forced' => true, 'max' => 255, 'api' => true],
			'close_date' => ['type' => self::UINT, 'default' => 0, 'api' => true],
			'change_vote' => ['type' => self::BOOL, 'default' => false, 'api' => true],
			'view_results_unvoted' => ['type' => self::BOOL, 'default' => true, 'api' => true],
		];
		$structure->getters = [
			'Handler' => true,
			'Content' => true,
		];
		$structure->relations = [
			'Responses' => [
				'entity' => 'XF:PollResponse',
				'type' => self::TO_MANY,
				'conditions' => 'poll_id',
			],
			'Votes' => [
				'entity' => 'XF:PollVote',
				'type' => self::TO_MANY,
				'conditions' => 'poll_id',
			],
		];

		return $structure;
	}

	/**
	 * @return PollRepository
	 */
	protected function getPollRepo()
	{
		return $this->repository(PollRepository::class);
	}
}
