<?php

    namespace Icing\Authenticator;

    use Authentication\Authenticator\ResultInterface;
    use Authentication\Identifier\AbstractIdentifier;
    use Authentication\PasswordHasher\PasswordHasherTrait;
    use Authentication\UrlChecker\UrlCheckerTrait;
    use Cake\Http\Cookie\Cookie;
    use Cake\Utility\Security;
    use Psr\Http\Message\ServerRequestInterface;
    use RobThree\Auth\Algorithm;
    use TwoFactorAuth\Authenticator\Result;

    class TwoFactorFormAuthenticator extends \TwoFactorAuth\Authenticator\TwoFactorFormAuthenticator
    {

        use PasswordHasherTrait;
        use UrlCheckerTrait;

        protected array $_defaultConfig = [
            'loginUrl'        => null,
            'userSessionKey'  => 'TwoFactorAuth.user',
            'urlChecker'      => 'Authentication.Default',
            'fields'          => [
                AbstractIdentifier::CREDENTIAL_USERNAME => 'username',
                AbstractIdentifier::CREDENTIAL_PASSWORD => 'password',
            ],
            'codeField'       => 'code',
            'secretProperty'  => 'secret',
            'issuer'          => null,
            'digits'          => 6,
            'period'          => 30,
            'algorithm'       => Algorithm::Sha1,
            'qrcodeprovider'  => null,
            'rngprovider'     => null,
            'timeprovider'    => null,
            'rememberMeField' => 'remember_two_factor_authentication',
            'cookie'          => [
                'name' => 'two_factor_authentication_token',
            ],
        ];

        public function authenticateCredentials(ServerRequestInterface $request): ResultInterface
        {
            $result = parent::authenticateCredentials($request);

            if ($result->getStatus() === Result::TWO_FACTOR_AUTH_REQUIRED) {
                if ($this->_checkCookie($request)) {
                    $user = $this->_getSessionUser($request);
                    $this->_unsetSessionUser($request);
                    return new Result($user, ResultInterface::SUCCESS);
                }
            }

            return $result;
        }

        protected function authenticateCode(ServerRequestInterface $request, string $code): ResultInterface
        {
            $user   = $this->_getSessionUser($request);
            $result = parent::authenticateCode($request, $code);

            if ($result->getStatus() === ResultInterface::SUCCESS) {
                $data = $request->getParsedBody();
                if ($data[$this->_config['rememberMeField']]) {
                    $this->_saveCookie($user);
                }
            }

            return $result;
        }

        protected function _checkCookie(ServerRequestInterface $request): bool
        {
            $hash = $request->getCookieParams()[$this->_config['cookie']['name']] ?? null;
            if (!isset($hash)) {
                return false;
            }
            $user  = $this->_getSessionUser($request);
            $token = $this->_createToken($user);

            return $this->getPasswordHasher()->check($token, $hash);
        }

        protected function _saveCookie($user): void
        {
            $value   = $this->getPasswordHasher()->hash($this->_createToken($user));
            $options = $this->getConfig('cookie');

            $cookie = Cookie::create(
                $options['name'],
                $value,
                $options
            );
            setcookie(
                $cookie->getName(),
                $cookie->getValue(),
                $cookie->getExpiresTimestamp(),
                $cookie->getPath(),
                $cookie->getDomain(),
                $cookie->isSecure(),
                $cookie->isHttpOnly()
            );

        }

        protected function _createToken($user): string
        {
            $usernameField = $this->getConfig('fields.username');
            $secretField   = $this->getConfig('secretProperty');
            $value         = $user[$usernameField] . $user[$secretField];
            return Security::hash($value, 'sha256', true);
        }


    }
