<?php
/**
 * This file is part of the ForkBB <https://github.com/forkbb>.
 *
 * @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
 * @license   The MIT License (MIT)
 */

declare(strict_types=1);

namespace ForkBB\Models\Poll;

use ForkBB\Core\Container;
use ForkBB\Models\DataModel;
use ForkBB\Models\Topic\Topic;
use PDO;
use RuntimeException;
use function \ForkBB\__;

class Poll extends DataModel
{
    const JSON_OPTIONS = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR;

    /**
     * Ключ модели для контейнера
     * @var string
     */
    protected $cKey = 'Poll';

    /**
     * Возвращает родительскую тему
     */
    protected function getparent(): Topic
    {
        if ($this->tid < 1) {
            throw new RuntimeException('Parent is not defined');
        }

        $topic = $this->c->topics->get($this->tid);

        if (! $topic instanceof Topic) {
            throw new RuntimeException('Parent not found');
        }

        return $topic;
    }

    /**
     * Устанавливает родительскую тему
     */
    protected function setparent(Topic $topic): void
    {
        if ($topic->id < 1) {
            throw new RuntimeException('Parent has a bad id');
        }

        if (
            $this->tid > 0
            && $this->tid !== $topic->id
        ) {
            throw new RuntimeException('Alien parent');
        }

        $this->tid = $topic->id;
    }

    /**
     * Ссылка на обработчик опроса
     */
    protected function getlink(): ?string
    {
        if ($this->tid > 0) {
            return $this->c->Router->link('Poll', ['tid' => $this->tid]);
        } else {
            return null;
        }
    }

    /**
     * Значение токена для формы голосования
     */
    protected function gettoken(): ?string
    {
        if ($this->tid > 0) {
            return $this->c->Csrf->create('Poll', ['tid' => $this->tid]);
        } else {
            return null;
        }
    }

    /**
     * Статус голосования для текущего пользователя
     */
    protected function getuserVoted(): bool
    {
        if ($this->c->user->isGuest) {
            return true;
        }

        if ($this->tid < 1) {
            return false;
        }

        $vars = [
            ':tid' => $this->tid,
            ':uid' => $this->c->user->id,
        ];
        $query = 'SELECT 1
            FROM ::poll_voted
            WHERE tid=?i:tid AND uid=?i:uid';

        return ! empty($this->c->DB->query($query, $vars)->fetch());
    }

    /**
     * Статус открытости
     */
    protected function getisOpen(): bool
    {
        return $this->tid < 1
            || (
                0 === $this->parent->closed
                && ($type = $this->parent->poll_type) > 0
                && (
                    1 === $type
                    || (
                        $type > 1000
                        && ($type - 1000) * 86400 > \time() - $this->parent->poll_time
                    )
                )
            );
    }

    /**
     * Статус возможности голосовать
     */
    protected function getcanVote(): bool
    {
        return $this->tid > 0
            && $this->c->user->usePoll
            && $this->isOpen
            && ! $this->userVoted;
    }

    /**
     * Статус возможности видеть результат
     */
    protected function getcanSeeResult(): bool
    {
        return (
                $this->c->user->usePoll
                || 1 === $this->c->config->b_poll_guest
            )
            && (
                0 === $this->parent->poll_term
                || $this->parent->poll_term < \max($this->total)
                || ! $this->isOpen
            );
    }

    /**
     * Статус возможности редактировать опрос
     */
    protected function getcanEdit(): bool
    {
        return $this->c->user->usePoll
            && (
                0 === $this->c->config->i_poll_time
                || $this->tid < 1
                || $this->c->user->isAdmin
                || $this->c->user->isModerator($this)
                || 60 * $this->c->config->i_poll_time > \time() - $this->parent->poll_time
            );
    }

    /**
     * Возвращает максимум голосов за один ответ по каждому вопросу
     */
    protected function getmaxVote(): array
    {
        $result = [];

        foreach (\array_keys($this->question) as $q) {
            $result[$q] = \min(\max($this->vote[$q]), $this->total[$q]);
        }

        return $result;
    }

    /**
     * Возвращает процент голосов по каждому ответу кажого вопроса
     */
    protected function getpercVote(): array
    {
        $result = [];

        foreach (\array_keys($this->question) as $q) {
            if ($this->total[$q] > 0) {
                $total = $this->total[$q] / 100;

                foreach (\array_keys($this->answer[$q]) as $a) {
                    $result[$q][$a] = \min(100, \round($this->vote[$q][$a] / $total, 2));
                }
            } else {
                foreach (\array_keys($this->answer[$q]) as $a) {
                    $result[$q][$a] = 0;
                }
            }
        }

        return $result;
    }

    /**
     * Возвращает ширину ответа в процентах
     */
    protected function getwidthVote(): array
    {
        $result = [];

        foreach (\array_keys($this->question) as $q) {
            if ($this->maxVote[$q] > 0) {
                $max = $this->maxVote[$q] / 100;

                foreach (\array_keys($this->answer[$q]) as $a) {
                    $result[$q][$a] = \min(100, \round($this->vote[$q][$a] / $max));
                }
            } else {
                foreach (\array_keys($this->answer[$q]) as $a) {
                    $result[$q][$a] = 0;
                }
            }
        }

        return $result;
    }

    /**
     * Возвращает статус опроса для текущего пользователя или null
     */
    protected function getstatus() /* : null|string|array */
    {
        if ($this->tid < 1) {
            return null;
        } elseif (
            $this->c->user->isGuest
            && 1 !== $this->c->config->b_poll_guest
        ) {
            return 'Poll results are hidden from the guests';
        } elseif (! $this->isOpen) {
            return 'This poll is closed';
        } elseif (! $this->canSeeResult) {
            return ['Poll results are hidden up to %s voters', $this->parent->poll_term];
        } elseif ($this->userVoted) {
            return 'You voted';
        } else {
            return 'Poll status is undefined';
        }
    }

    /**
     * Голосование
     * Возвращает текст ошибки или null
     */
    public function vote(array $vote): ?string
    {
        if (! $this->canVote) {
            return __('You cannot vote on this poll');
        }

        $data = [];

        foreach (\array_keys($this->question) as $q) {
            if ($this->type[$q] > 1) {
                $count = \count($vote[$q]);
                if (0 == $count) {
                    return __(['No vote on question %s', $q]);
                } elseif ($count > $this->type[$q]) {
                    return __(['Too many answers selected in question %s', $q]);
                }

                foreach (\array_keys($vote[$q]) as $a) {
                    if (! isset($this->answer[$q][$a])) {
                        return __(['The selected answer is not present in question %s', $q]);
                    }

                    $data[] = [$q, $a];
                }
            } else {
                if (! isset($vote[$q][0])) {
                    return __(['No vote on question %s', $q]);
                } elseif (! isset($this->answer[$q][$vote[$q][0]])) {
                    return __(['The selected answer is not present in question %s', $q]);
                }

                $data[] = [$q, $vote[$q][0]];
            }

            $data[] = [$q, 0];
        }

        $vars = [
            ':tid' => $this->tid,
            ':uid' => $this->c->user->id,
            ':rez' => \json_encode($data, self::JSON_OPTIONS),
        ];
        $query = 'INSERT INTO ::poll_voted (tid, uid, rez)
            VALUES (?i:tid, ?i:uid, ?s:rez)';

        $this->c->DB->exec($query, $vars);

        $vars = [
            ':tid' => $this->tid,
        ];
        $query = 'UPDATE ::poll
            SET votes=votes+1
            WHERE tid=?i:tid AND question_id=?i:qid AND field_id=?i:fid';

        foreach ($data as list($vars[':qid'], $vars[':fid'])) {
            $this->c->DB->exec($query, $vars);
        }

        $this->c->polls->reset($this->tid);

        return null;
    }
}
