<?php
namespace api\validators;

use common\helpers\Error as E;
use common\models\Account;
use OTPHP\TOTP;
use Yii;
use yii\base\InvalidConfigException;
use yii\validators\Validator;

class TotpValidator extends Validator {

    /**
     * @var Account
     */
    public $account;

    /**
     * @var int|null Задаёт окно, в промежуток которого будет проверяться код.
     * Позволяет избежать ситуации, когда пользователь ввёл код в последнюю секунду
     * его существования и пока шёл запрос, тот протух.
     * Значение задаётся в +- кодах, а не секундах.
     */
    public $window;

    /**
     * @var int|callable|null Позволяет задать точное время, относительно которого будет
     * выполняться проверка. Это может быть собственно время или функция, возвращающая значение.
     * Если не задано, то будет использовано текущее время.
     */
    public $timestamp;

    public $skipOnEmpty = false;

    public function init() {
        parent::init();
        if ($this->account === null) {
            $this->account = Yii::$app->user->identity;
        }

        if (!$this->account instanceof Account) {
            throw new InvalidConfigException('account should be instance of ' . Account::class);
        }

        if (empty($this->account->otp_secret)) {
            throw new InvalidConfigException('account should have not empty otp_secret');
        }
    }

    protected function validateValue($value) {
        $totp = new TOTP(null, $this->account->otp_secret);
        if (!$totp->verify((string)$value, $this->getTimestamp(), $this->window)) {
            return [E::OTP_TOKEN_INCORRECT, []];
        }

        return null;
    }

    private function getTimestamp(): ?int {
        if ($this->timestamp === null) {
            return null;
        }

        if (is_callable($this->timestamp)) {
            return (int)call_user_func($this->timestamp);
        }

        return (int)$this->timestamp;
    }

}