<?php

namespace XF\Tfa;

use Base32\Base32;
use Otp\GoogleAuthenticator;
use Otp\Otp;
use XF\Entity\TfaProvider;
use XF\Entity\User;
use XF\Http\Request;
use XF\Mvc\Controller;
use XF\Repository\TfaRepository;

use function is_array;

class Totp extends AbstractProvider
{
	public function generateInitialData(User $user, array $config = [])
	{
		$length = 16;
		$secret = substr(Base32::encode(\XF::generateRandomString($length, true)), 0, $length);

		return [
			'secret' => $secret,
		];
	}

	public function trigger($context, User $user, array &$config, Request $request)
	{
		return [];
	}

	public function render($context, User $user, array $config, array $triggerData)
	{
		$issuer = \XF::app()->stringFormatter()->wholeWordTrim(
			str_replace(':', '', \XF::options()->boardTitle),
			50
		);
		$user = str_replace(':', '', $user->username);

		$otpUrl = GoogleAuthenticator::getKeyUri('totp', "$issuer:$user", $config['secret'], null, [
			'issuer' => $issuer,
		]);

		$params = [
			'secret' => $config['secret'],
			'otpUrl' => $otpUrl,
			'config' => $config,
			'context' => $context,
		];
		return \XF::app()->templater()->renderTemplate('public:two_step_totp', $params);
	}

	public function verify($context, User $user, array &$config, Request $request)
	{
		if (empty($config['secret']))
		{
			return false;
		}

		$code = $request->filter('code', 'str');
		$code = preg_replace('/[^0-9]/', '', $code);
		if (!$code)
		{
			return false;
		}

		if (!empty($config['lastCode']) && $config['lastCode'] === $code)
		{
			// prevent a replay attack: once the code has been used, don't allow it to be used in the slice again
			if (!empty($config['lastCodeTime']) && time() - $config['lastCodeTime'] < 150)
			{
				return false;
			}
		}

		$otp = new Otp();
		if (!$otp->checkTotp(Base32::decode($config['secret']), $code, 2))
		{
			return false;
		}

		$config['lastCode'] = $code;
		$config['lastCodeTime'] = time();

		return true;
	}

	public function canManage()
	{
		return true;
	}

	public function handleManage(Controller $controller, TfaProvider $provider, User $user, array $config)
	{
		$request = $controller->request();
		$session = $controller->session();

		$newProviderData = null;
		$newTriggerData = null;
		$showSetup = false;

		if ($request->isPost())
		{
			$sessionKey = 'tfaData_totp';

			if ($request->filter('regen', 'bool'))
			{
				$newProviderData = $this->generateInitialData($user);
				$newTriggerData = $this->trigger('setup', $user, $newProviderData, $request);

				$session->set($sessionKey, $newProviderData);
				$showSetup = true;
			}
			else if ($request->filter('confirm', 'bool'))
			{
				$newProviderData = $session->get($sessionKey);
				if (!is_array($newProviderData))
				{
					return null;
				}

				if (!$this->verify('setup', $user, $newProviderData, $request))
				{
					return $controller->error(\XF::phrase('two_step_verification_value_could_not_be_confirmed'));
				}

				/** @var TfaRepository $tfaRepo */
				$tfaRepo = \XF::repository(TfaRepository::class);
				$tfaRepo->updateUserTfaData($user, $provider, $newProviderData);

				$session->remove($sessionKey);

				return null;
			}
			else
			{
				return null;
			}
		}

		$viewParams = [
			'provider' => $provider,
			'user' => $user,
			'providerData' => $config,
			'newProviderData' => $newProviderData,
			'newTriggerData' => $newTriggerData,
			'showSetup' => $showSetup,
		];
		return $controller->view(
			'XF:Account\TwoStepTotpManage',
			'account_two_step_totp_manage',
			$viewParams
		);
	}
}
