<?php

/**
 * 用户基础信息
 * @author benzhan
 */

use kornrunner\Keccak;

class Account extends Model {
    protected $tableName = 'user_info';
    protected $dbKey = 'dw_chat';
    const PRE_KEY_TOKEN = 'globals:token:';
    const PRE_KEY_RANDOM = 'globals:random_1:';
    const PRE_KEY_ACCESS_TOKEN = 'globals:access_token:';

    const TYPE_EOS = 'eos';
    const TYPE_ETH = 'eth';
    const TYPE_TRON = 'tron';
    const TYPE_TG = 'tg';

    const TTL_TOKEN = 86400;

    private static $arrType = [
        self::TYPE_EOS => 'EOS',
        self::TYPE_ETH => 'ETH',
        self::TYPE_TRON => 'TRON',
        self::TYPE_TG => 'TG',
    ];

    public static function getAllType() {
        return self::$arrType;
    }

    public static function getTokenKey($token) {
        return self::PRE_KEY_TOKEN . $token;
    }

    /**
     * 检查token是否存在
     * @param $player
     *
     * @return bool
     */
    public static function checkToken() {
        $objRedis = dwRedis::init('dw_chat');
        $token = $_REQUEST['token'];
        $user_id = $_REQUEST['user_id'];

        if ($token) {
            $tokenKey = self::getTokenKey($token);
            $value = $objRedis->get($tokenKey);
            if ($value) {
                if ($value == $user_id) {
                    return true;
                }
            } else {
                self::clearCookie();
            }
        }

        return false;
    }

    /**
     * 刷新token
     * @return bool
     */
    public static function refreshToken() {
        $objRedis = dwRedis::init('dw_chat');
        $token = $_REQUEST['token'];
        $user_id = $_REQUEST['user_id'];

        if ($token) {
            $tokenKey = self::getTokenKey($token);
            $value = $objRedis->get($tokenKey);
            if ($value && $value == $user_id) {
                $objRedis->expire($tokenKey, self::TTL_TOKEN);
                return true;
            }
        }

        return false;
    }

    /**
     * 生成账号
     */
    public static function genToken($user_id) {
        $token = uuid16();
        $tokenKey = self::getTokenKey($token);

        $objRedis = dwRedis::init();
//        $objRedis->set($tokenKey, $account);
        $objRedis->setex($tokenKey, self::TTL_TOKEN, $user_id);

        return $token;
    }

    /**
     * 设置用户的Cookie
     * @param $account
     *
     * @return array
     */
    public static function setCookie($user_id) {
        $token = Account::genToken($user_id);

        // 不再设置cookie
//        $expire = time() + 86400 * 7;
//        $path = '/';
//        $domain = '';
//        if (ENV != ENV_DEV) {
//            $domain = 'mee.chat';
//        }
//
//        // 生成token
//        setcookie('token', $token, $expire, $path, $domain);
//        setcookie('user_id', $user_id, $expire, $path, $domain);

        return compact('user_id', 'token');
    }

    public static function clearCookie() {
        $expire = time() - 86400;
        $path = '/';
        // 删除正式环境的cookie
        setcookie('token', null, $expire, $path, 'mee.chat');
        setcookie('user_id', null, $expire, $path, 'mee.chat');

        if (ENV == ENV_DEV) {
            // 删除cookie
            setcookie('token', null, $expire, $path);
            setcookie('user_id', null, $expire, $path);
        }
    }

    public static function getRandomKey($account) {
        return self::PRE_KEY_RANDOM . $account;
    }

    private static function getAccessTokenKey($accessToken) {
        return self::PRE_KEY_ACCESS_TOKEN . $accessToken;
    }

    /**
     * 刷新access_token
     * @author solu
     * @param $random
     * @param null $objRedis
     * @return string
     */
    private static function refreshAccessToken($random, $objRedis = null) {
        !$objRedis && $objRedis = dwRedis::init();

        $accessToken = uuid();
        $key = self::getAccessTokenKey($accessToken);

        $objRedis->setex($key, 900, $random);

        return $accessToken;
    }

    /**
     * 获取随机数
     * @param $account
     * @param $withToken
     * @return string|array
     */
    public static function getRandom($account, $withToken = false) {
        $objRedis = dwRedis::init();

        $randomKey = self::getRandomKey($account);
        $random = $objRedis->get($randomKey);
        if (!$random) {
//            $random = uuid16();
            $random = date('H:i:s ', time() - 8 * 3600) . rand(0, 999);
        }
        $objRedis->setex($randomKey, 900, $random);

        $accessToken = self::refreshAccessToken($random, $objRedis);

        return !$withToken ? $random : compact('random', 'accessToken');
    }

    /**
     * 校验用户并且登录
     * @author solu
     * @param $account
     * @param $random
     * @param $token
     * @return array
     * @throws Exception
     */
    public static function verifyPlayer($account, $random, $token) {
        if (!$account) {
            throw new Exception('参数错误', CODE_PARAM_ERROR);
        }

        $objRedis = dwRedis::init();

        $tokenKey = self::getAccessTokenKey($token);
        $storeRandom = $objRedis->get($tokenKey);
        if ($storeRandom != $random) {
            throw new Exception('access_token unmatch', CODE_PARAM_ERROR);
        }

        $user_id = User::login($account, Account::TYPE_EOS);
        return self::setCookie($user_id);
    }

    /**
     * 校验数据
     * @author solu
     * @param $account
     * @param $random
     * @param $token
     * @return bool
     * @throws Exception
     */
    public static function verifyEosOnly($account, $random, $token) {
        if (!$account) {
            throw new Exception('参数错误', CODE_PARAM_ERROR);
        }

        $objRedis = dwRedis::init();

        $tokenKey = self::getAccessTokenKey($token);
        $storeRandom = $objRedis->get($tokenKey);
        if ($storeRandom != $random) {
            throw new Exception('access_token unmatch', CODE_PARAM_ERROR);
        }

        return true;
    }

    /**
     * 校验eos
     * @param $pubkey
     * @param $account
     * @param $data
     * @param $sign
     * @return bool
     * @throws Exception
     */
    public static function verifyMsg($pubkey, $account, $data, $sign) {
        $json = EosBase::getAccount($account);
        $user = json_decode($json, true);
//        $user = EosRpcApi::getAccount($account);

        $active = $user['permissions'][0]['required_auth']['keys'][0]['key'];
        $owner = $user['permissions'][1]['required_auth']['keys'][0]['key'];

        if ($pubkey != $active && $pubkey != $owner) {
            throw new Exception('pubkey error', CODE_PARAM_ERROR);
        }

        $cmd = "node {$GLOBALS['verifyPath']} '$data' '$pubkey' '$sign'";
        // 返回true表示验证通过
        $ret = EosBase::execCmd($cmd);

        return trim($ret) == 'true';
    }

    /**
     * 校验eth签名
     * @author solu
     * @param $account
     * @param $data
     * @param $signed
     * @return bool
     * @throws Exception
     */
    public static function verifyEth($account, $data, $signed) {
        $hash = Utils::sha3($data);

        $messageGmp       = gmp_init($hash);
        $rHex   = substr($signed, 2, 64);
        $sHex   = substr($signed, 66, 64);
        $vValue = hexdec(substr($signed, 130, 2));

        $r = $rHex;		//hex string without 0x
        $s = $sHex; 	//hex string without 0x
        $v = $vValue; 	//27 or 28

        $rGmp = gmp_init("0x" . $r);
        $sGmp = gmp_init("0x" . $s);

        $recovery = $v - 27;
        if ($recovery !== 0 && $recovery !== 1) {
            throw new Exception('Invalid signature v value');
        }

        $publicKey = Signature::recoverPublicKey($rGmp, $sGmp, $messageGmp, $recovery);
        $publicKeyString = $publicKey["x"] . $publicKey["y"];
        $recoveryAccount = '0x'. substr(self::keccak256(hex2bin($publicKeyString)), -40);

        return strtolower($recoveryAccount) == strtolower($account);
    }

    /**
     * 校验tron签名
     * @author solu
     * @param $account
     * @param $data
     * @param $signed
     * @return bool
     */
    public static function verifyTron($account, $data, $signed) {
        $data = Utils::sha3($data);

        $cmd = "node {$GLOBALS['tronVerifyPath']} '$data' '$signed' '$account'";
        // 返回true表示验证通过
        $ret = EosBase::execCmd($cmd);

        return trim($ret) == 'true';
    }

    /**
     * keccak256
     * @param $str
     * @return string
     * @throws Exception
     */
    private static function keccak256($str) {
        return '0x'. Keccak::hash($str, 256);
    }
}