<?php

namespace XF\Tfa;

use Authy\AuthyApi;
use Authy\AuthyFormatException;
use XF\Authy\Api;
use XF\Entity\TfaProvider;
use XF\Entity\User;
use XF\Http\Request;
use XF\Mvc\Controller;
use XF\PrintableException;

use function strval;

class Authy extends AbstractProvider
{
	public function isDeprecated(): bool
	{
		return true;
	}

	public function renderOptions(TfaProvider $provider)
	{
		$params = [
			'provider' => $provider,
		];
		return \XF::app()->templater()->renderTemplate('admin:two_step_config_authy', $params);
	}

	public function isUsable()
	{
		return !empty($this->getProvider()->options['authy_api_key']);
	}

	public function requiresConfig()
	{
		return true;
	}

	public function handleConfig(
		Controller $controller,
		TfaProvider $provider,
		User $user,
		array &$config
	)
	{
		if ($controller->filter('setup', 'bool'))
		{
			$email = $controller->filter('email', 'str');
			if (!$email)
			{
				$email = \XF::visitor()->email;
			}
			if (!$email)
			{
				throw $controller->exception(
					$controller->error(\XF::phrase('please_enter_valid_email'))
				);
			}

			$dialCode = $controller->filter('dial_code', 'uint');
			$intlNumb = $controller->filter('intl_numb', 'str');
			$number = preg_replace('/^\+' . $dialCode . '/', '', $intlNumb);

			$authy = $this->getAuthy();
			$authyUser = $authy->registerUser($email, $number, $dialCode);

			if (!$authyUser->ok())
			{
				$errors = (array) $authyUser->errors();
				if (!$errors)
				{
					$errors = [\XF::phrase('something_went_wrong_please_try_again')];
				}

				throw $controller->exception(
					$controller->error($errors)
				);
			}

			$config['authy_id'] = $authyUser->id();

			return null;
		}
		else
		{
			$viewParams = [
				'provider' => $provider,
				'config' => $config,
			];
			return $controller->view('XF:Account\TwoStepAuthyConfig', 'account_two_step_authy_config', $viewParams);
		}
	}

	public function generateInitialData(User $user, array $config = [])
	{
		return $config;
	}

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

	public function render($context, User $user, array $config, array $triggerData)
	{
		$uuid = null;

		if ($context == 'login')
		{
			$authy = $this->getAuthy();
			$authyRequest = $authy->createApprovalRequest(
				$config['authy_id'],
				strval(\XF::phrase('approve_login_request_for_x_at_y', [
					'username' => $user->username,
					'boardTitle' => \XF::options()->boardTitle,
				])),
				[
					'details' => [
						'user_id' => $user->user_id,
						'username' => $user->username,
					],
				]
			);

			if (!$authyRequest->ok())
			{
				$errorJson = json_encode($authyRequest->errors());
				$errors = json_decode($errorJson, true);
				throw new PrintableException($errors);
			}

			$approvalRequest = $authyRequest->bodyvar('approval_request');
			$uuid = $approvalRequest->uuid ?? '';
		}

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

	public function verify($context, User $user, array &$config, Request $request)
	{
		$this->bypassFailedAttemptLog = false;

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

			$authy = $this->getAuthy();

			try
			{
				$authyVerification = $authy->verifyToken($config['authy_id'], $code);
			}
			catch (AuthyFormatException $e)
			{
				return false;
			}

			if (!$authyVerification->ok())
			{
				return false;
			}
		}
		else
		{
			$uuid = $request->filter('uuid', 'str');
			if (!$uuid)
			{
				throw new PrintableException('authy_no_uuid');
			}

			$authy = $this->getAuthy();
			$authyApprovalCheck = $authy->getApprovalRequest($uuid);

			if (!$authyApprovalCheck->ok())
			{
				throw new PrintableException('authy_no_approval_request');
			}

			$approvalRequest = $authyApprovalCheck->bodyvar('approval_request');

			if ($approvalRequest->status == 'pending')
			{
				$this->bypassFailedAttemptLog = true;
				return false;
			}
			else if ($approvalRequest->status == 'denied')
			{
				throw new PrintableException('authy_denied');
			}
		}

		return true;
	}

	/**
	 * @return AuthyApi
	 */
	protected function getAuthy()
	{
		$provider = $this->getProvider();
		return new Api($provider->options['authy_api_key']);
	}
}
