<?php

namespace Application\Service\Login\Adapter;

use Application\Common\RedisEnum;
use Application\Common\StatusCode;
use Application\Service\DB\Admin\User;
use Application\Service\Exceptions\InvalidArgumentException;
use Application\Service\Extension\Identity\Identity;
use Application\Service\Extension\Validator\ValidatorApplication;
use Application\Service\Login\LoginClient;
use Application\Service\Redis\RedisExtend;

class AccoutPwd
{
    public const LOGIN_CACHE_NAMESPACE = 'LOGIN_ACCOUNT_PWD';
    public const LOGIN_LOCKED_NAMESPACE = 'LOGIN_LOCKED_ACCOUNT_PWD';
    public const LOGIN_ACCOUNT_KEY = 'LOGIN_ACCOUNT';
    public const IP_ADDRESS_KEY = 'IP_ADDRESS';
    protected string $mobile;
    protected string $password;

    private array $user;

    private User $userModel;
    private Identity $identity;
    private ValidatorApplication $validatorApplication;
    private RedisExtend $redisExtend;
    private string $ip_address;
    public function __construct(User $userModel, Identity $identity, RedisExtend $redisExtend) {
        $this->userModel = $userModel;
        $this->identity = $identity;
        $this->redisExtend = $redisExtend;
        $this->init();
    }

    public function login(): array
    {
        $this->valid();

        $this->removeLoginFailNum();
        return $this->identity->getLoginInfo($this->user['id']);
    }

    private function valid()
    {
        $this->validLocked();
        $this->validSmsCode();
        $this->validForm();
        $this->validUser();
    }

    /**
     * @note 验证手机验证码
     * @return void
     * @throws \RedisException
     */
    private function validSmsCode() {
        $redisSmsCode = $this->redisExtend->setDatabase(6)->getRedisInstance()->get(RedisEnum::SMS_LOGIN . $this->mobile);

        // 登录提交的验证码
        $loginSmsCode = $_POST['sms_code'];

        if (!$redisSmsCode || $redisSmsCode != $loginSmsCode) {
            $this->incrLoginFailNum();
            throw new InvalidArgumentException("验证码错误！");
        }
    }

    private function validLocked() {
        $this->redisExtend->setNamespace(self::LOGIN_LOCKED_NAMESPACE);
        // 验证账户是否锁定
        $loginAccountKey = $this->formatRedisKey(self::LOGIN_ACCOUNT_KEY, $this->mobile);
        if( $this->redisExtend->getItem($loginAccountKey) ) {
            throw new InvalidArgumentException(sprintf("账户已被锁定，请%s分钟后重试!", $this->getTimeout(self::LOGIN_LOCKED_NAMESPACE, $loginAccountKey)));
        }

        // 验证IP是否锁定
        $ipAddressKey = $this->formatRedisKey(self::IP_ADDRESS_KEY, $this->ip_address);
        if( $this->redisExtend->getItem($ipAddressKey) ) {
            throw new InvalidArgumentException(sprintf("您的IP地址已被锁定，请%s分钟后重试", $this->getTimeout(self::LOGIN_LOCKED_NAMESPACE, $ipAddressKey)));
        }
    }

    private function validForm()
    {
        $this->validatorApplication->attach(
            [['mobile', 'password'], 'required'],
        );

        if (! $this->validatorApplication->isValid()) {
            $this->incrLoginFailNum();
            throw new InvalidArgumentException($this->validatorApplication->getFirstErrorToString(), StatusCode::E_FIELD_VALIDATOR['code']);
        }
    }
    private function validUser()
    {
        $adminUser = $this->userModel->fetchOne([
            'where' => [
                'mobile' => $this->mobile,
                'is_del' => 0,
            ],
            'columns' => ['id', 'password', 'status', 'real_status']
        ]);

        if (empty($adminUser)) {
            $this->incrLoginFailNum();
            throw new InvalidArgumentException('用户不存在！');
        }

        if ($adminUser['status'] && $adminUser['real_status']) {
            $this->incrLoginFailNum();
            throw new InvalidArgumentException('当前账户已禁用，请联系相关管理员进行核实！');
        }

        // 验证密码是否正确
        if ( !password_verify($this->password, $adminUser['password'])) {
            $this->incrLoginFailNum();
            throw new InvalidArgumentException('密码错误！');
        }

        $this->user = $adminUser;
    }

    private function init()
    {
        $this->mobile = $_POST['mobile'] ?? '';
        $this->password = $_POST['password'] ?? '';

        $this->validatorApplication  = new ValidatorApplication();

        // 初始redis
        $this->redisExtend->setNamespace(self::LOGIN_CACHE_NAMESPACE);

        $this->ip_address = $_SERVER['REMOTE_ADDR'] ?? '';
    }

    private function removeLoginFailNum()
    {
        $this->redisExtend->setNamespace(self::LOGIN_LOCKED_NAMESPACE)->removeItems([
            $this->formatRedisKey(self::LOGIN_ACCOUNT_KEY, $this->mobile),
            $this->formatRedisKey(self::IP_ADDRESS_KEY, $this->ip_address),
        ]);

        $this->redisExtend->setNamespace(self::LOGIN_CACHE_NAMESPACE)->removeItems([
            $this->formatRedisKey(self::LOGIN_ACCOUNT_KEY, $this->mobile),
            $this->formatRedisKey(self::IP_ADDRESS_KEY, $this->ip_address),
        ]);
    }

    private function incrLoginFailNum()
    {
        $this->redisExtend->setNamespace(self::LOGIN_CACHE_NAMESPACE)->setTtl(300);
        $loginAccountKey = $this->formatRedisKey(self::LOGIN_ACCOUNT_KEY, $this->mobile);
        $failedNum = $this->redisExtend->getItem($loginAccountKey) ?: 0;
        $this->redisExtend->setItem($loginAccountKey, $failedNum + 1);
        $this->setLocked($loginAccountKey);

        $this->redisExtend->setNamespace(self::LOGIN_CACHE_NAMESPACE)->setTtl(300);
        $ipAddressKey = $this->formatRedisKey(self::IP_ADDRESS_KEY, $this->ip_address);
        $failedNum = $this->redisExtend->getItem($ipAddressKey) ?: 0;
        $this->redisExtend->setItem($ipAddressKey, $failedNum + 1);
        $this->setLocked($ipAddressKey);
    }

    public static function formatRedisKey(string $prefix, string $key): string
    {
        return sprintf("%s:%s",$prefix, $key);
    }

    private function getTimeout(string $namespace, string $key) {
        $ttl = $this->redisExtend->getRedisInstance()->ttl("{$namespace}:{$key}") ?? 0;
        return ceil( $ttl / 60 );
    }

    private function setLocked($key) {
        if($this->redisExtend->setNamespace(self::LOGIN_CACHE_NAMESPACE)->getItem($key) == 5) {
            $this->redisExtend->setNamespace(self::LOGIN_LOCKED_NAMESPACE)->setTtl(1800);
            // 锁定
            $this->redisExtend->setItem($key, 1);
        }
    }

}