<?php

/**
 * 用户基础信息
 * @author benzhan
 */
class Account extends Model {
    protected $tableName = 'account';
    protected $dbKey = 'dw_eos';
    const PRE_KEY_TOKEN = 'globals:token:';
    const PRE_KEY_RANDOM = 'globals:random_1:';

    const EOS_REWARD_RATE = 0.002;
    const REWARD_GT = 50000;

    public static $vip_return_map = [
        1 => 0.0001,
        2 => 0.0002,
        3 => 0.0003,
        4 => 0.0004,
        5 => 0.0005,
        6 => 0.0007,
        7 => 0.0009,
        8 => 0.0011,
        9 => 0.0013,
        10 => 0.0015,
    ];

    public static $vip_need_map = [
        1 => 1000,
        2 => 5000,
        3 => 10000,
        4 => 50000,
        5 => 100000,
        6 => 500000,
        7 => 1000000,
        8 => 5000000,
        9 => 10000000,
        10 => 50000000,
    ];

    public static $vip_airdrop_map = [
        1 => 66,
        2 => 266,
        3 => 566,
        4 => 2666,
        5 => 5666,
        6 => 26666,
        7 => 56666,
        8 => 266666,
        9 => 566666,
        10 => 2666666,
    ];

    public function getBalance($player, $field = 'balance') {
        $where = compact('player');
        $_field = $field;
        return (int) $this->objTable->getOne($where, compact('_field'));
    }

    /**
     * 修改用户余额
     * @param $player
     * @param $amount
     * @param string $type 类型：pledge:抵押, win:获得奖励, return:归还，deposit:充值, withdraw:提现
     * @param $memo
     * @param $others { transaction_state, transaction_id, block_num, wait_amount}
     * @return array
     * @throws Exception
     */
    public function changeBalance($player, $amount, $type, $memo, $others = null) {
        $objBalaceLog = new TableHelper('balance_log', 'dw_eos');

        $objLock = new Lock('changeBalance', 30);
        $objLock->lockWait($player);

        $this->objTable->autoCommit(false);
        try {
            // 检查修改后，余额是否小于0
            $account = $this->objTable->getRow(['player' => $player]);
            $account_balance = intval($account['balance']) + $amount;
            $beforeBalance = (int)$account['balance'];
            if ($account_balance < 0) {
                // 需要告警
                $backtrace = debug_backtrace();
                $msg = "changeBalance, player:{$player}, memo:{$memo}, account_balance < 0, backtrace:" . json_encode($backtrace);
                alermErrorMsg($msg);

                $objLock->unlock($player);
                throw new Exception('system error: account_balance < 0', CODE_UNKNOW_ERROT);
            }

            // 检查交易id是否存在
            $transaction_id = $others['transaction_id'];
            if ($transaction_id) {
                $count = $objBalaceLog->getCount(['transaction_id' => $transaction_id]);
                if ($count) {
                    // 需要告警
                    $backtrace = debug_backtrace();
                    $msg = "changeBalance, player:{$player}, memo:{$memo}, transaction_id has exist, backtrace:" . json_encode($backtrace);
                    alermErrorMsg($msg);

                    $objLock->unlock($player);
                    throw new Exception('system error, transaction_id has exist.!', CODE_NORMAL_ERROR);
                }
            }

            $newData = [
                'balance' => $account['balance'],
            ];

            // 待上链的余额
            $wait_amount = arrayPop($others, 'wait_amount');
            if ($wait_amount) {
                $newData['wait_amount'] = $account['wait_amount'] + $wait_amount;
            }

            // 处理抵押
            if ($others['roomAmount']) {
                $roomAmount = $others['roomAmount'];
                $newData['game_bet'] = $account['game_bet'] - $roomAmount;

                if ($newData['game_bet'] < 0) {
                    // 需要告警
                    $msg = "changeBalance, player:{$player}, memo:{$memo}, game_bet < 0;";
                    alermErrorMsg($msg);

                    $objLock->unlock($player);
                    throw new Exception('system error: game_bet < 0', CODE_UNKNOW_ERROT);
                }


                // 赢钱的抵押一起归还，输钱的只加奖金
                if ($others['act'] != 'lose') {
                    $newData['balance'] += $roomAmount;

                    // 插入归还记录
                    $balance_log = [
                        'appid' => 'box',
                        'player' => $player,
                        'change_amount' => $roomAmount,
                        'type' => 'return',
                        'before_amount' => $beforeBalance,
                        'after_amount' =>  $newData['balance'],
                        'eos_balance' => $account['eos_balance'],
                        'memo' => $memo,
                        'create_time' => date('Y-m-d H:i:s'),
                    ];
                    $beforeBalance += $roomAmount;

                    if (!$objBalaceLog->addObject($balance_log)) {
                        $objLock->unlock($player);
                        throw new Exception('system error: add balance log, try again!', CODE_NORMAL_ERROR);
                    }
                }
                unset($others['roomAmount'], $others['act']);
            }

            // 本次操作金额
            $newData['balance'] += $amount;

            if ($account) {
                // 不能修改update_time，update_time只能用于同步
                $ret = $this->objTable->updateObject($newData, ['player' => $player, 'balance' => $account['balance']]);
            } else {
                $newData['player'] = $player;
                $newData['create_time'] = date('Y-m-d H:i:s');
                $newData['update_time'] = date('Y-m-d H:i:s');
                $ret = $this->objTable->addObject($newData);
            }

            if (!$ret) {
                // 需要告警
                $backtrace = debug_backtrace();
                $msg = "changeBalance, player:{$player}, memo:{$memo}, balance change fail, backtrace:" . json_encode($backtrace);
                alermErrorMsg($msg);

                $objLock->unlock($player);
                throw new Exception('system error: change account balance, try again!', CODE_UNKNOW_ERROT);
            }

            // 防止关闭游戏重复插入记录
            if (0 != $amount) {
                //插入balance_log
                $balance_log = [
                    'appid' => 'box',
                    'player' => $player,
                    'change_amount' => $amount,
                    'type' => $type,
                    'before_amount' => $beforeBalance,
                    'after_amount' => $newData['balance'],
                    'eos_balance' => $account['eos_balance'],
                    'memo' => $memo,
                    'create_time' => date('Y-m-d H:i:s'),
                ];

                if ($others) {
                    $balance_log += $others;
                }

                if (!$objBalaceLog->addObject($balance_log)) {
                    $objLock->unlock($player);
                    throw new Exception('system error: add balance log, try again!', CODE_NORMAL_ERROR);
                }
            }

            $this->objTable->commit();
            $result =  true;
            $msg = 'success';
        } catch (Exception $e) {
            $this->objTable->rollback();
            // 发出告警
            alermErrorMsg($e->getMessage());
            $result = false;
            $msg = $e->getMessage();
        }

        $objLock->unlock($player);
        return compact('result', 'msg');
    }

    /**
     * 增加下注额
     * @param $player
     * @param $bet_amount
     */
    public function addBetAmount($player, $bet_amount) {
        if ($bet_amount >= 0) {
            // 增加下注总额度
            $account = $this->objTable->getRow(['player' => $player]);
            $vip_level = $this->updateBetAmount($player, $account['total_bet_amount'] + $bet_amount);

            // 增加vip返现
            $map = self::$vip_return_map;

            if ($vip_level && $map[$vip_level]) {
                $return_amount = intval($bet_amount * $map[$vip_level]);
                if ($return_amount > 0) {
                    $bet_eos = sprintf("%.4f EOS", round($bet_amount / 10000, 4));
                    $wait_amount = $return_amount;
                    $memo = "vip{$vip_level} return for {$bet_eos}";
                    $this->changeBalance($player, $return_amount, 'vip_return', $memo, compact('wait_amount'));
                }
            }
        }
    }

    /**
     * 更新下注额
     * @param $player
     * @param $total_bet_amount
     */
    public function updateBetAmount($player, $total_bet_amount) {
        $vip_level = self::getVipLevel($total_bet_amount);
        $newData = compact('total_bet_amount', 'vip_level');
        $this->objTable->updateObject($newData, compact('player'));

        if ($vip_level > 0) {
            // 尝试添加vip空投
            $this->addVipAirdrop($player, $vip_level);
        }

        return $vip_level;
    }

    public function addVipAirdrop($player, $vip_level) {
        $map = self::$vip_airdrop_map;

        // 如果没有空投则空投
        $objVipAirdropLog = new TableHelper('vip_airdrop_log', 'dw_eos');
        $data = compact('player', 'vip_level');
        $data['gt_amount'] = $map[$vip_level] * 10000;
        $data['create_time'] = date('Y-m-d H:i:s');
        $data['update_time'] = $data['create_time'];

        $objVipAirdropLog->addObjectsIfNoExist([$data]);
    }

    /**
     * 根据下注额获取vip等级
     * @param $bet_amount
     *
     * @return int
     */
    public static function getVipLevel($bet_amount) {
        $bet_amount = intval($bet_amount / 10000);

        for ($i = 10; $i > 0; $i--) {
            if ($bet_amount >= self::$vip_need_map[$i]) {
                return $i;
            }
        }

        return 0;
    }

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

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

        if ($_REQUEST['token'] && $_REQUEST['account']) {
            $token = $_REQUEST['token'];
            $account = $_REQUEST['account'];
        } else {
            $token = $_COOKIE['token'];
            $account = $_COOKIE['account'];
        }

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

        return false;
    }

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

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

        return $token;
    }

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

        $expire = time() + 86400 * 7;
        $path = '/';
        $domain = '';
        if (ENV != ENV_DEV) {
            $domain = 'eosget.io';
        }

        // 生成token
        setcookie('token', $token, $expire, $path, $domain);
        setcookie('account', $account, $expire, $path, $domain);

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

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

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

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

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

        $randomKey = self::getRandomKey($account);
        $random = $objRedis->get($randomKey);
        if (!$random) {
            $random = uuid16();
        }
        $objRedis->setex($randomKey, 900, $random);

        return $random;
    }

    public static function verifyMsg($pubkey, $account, $data, $sign) {
        $json = Eos::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) {
            return false;
        }

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

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

    /**
     * 增加推荐佣金
     * @param $bet_player
     * @param $referee_player
     */
    public static function addRefereeReward($bet_player, $referee_player, $bet_data) {
        $objLock = new Lock('addRefereeReward');
        $objLock->lock($bet_player);

        //判断是否为第一次奖励
        $objRefereeReward = new TableHelper('referee_reward_log', 'dw_eos');
        $row = $objRefereeReward->getRow(['player' => $bet_player, 'referee_player' => $referee_player]);

        //奖励
        $objAccountReward = new TableHelper('account_reward_info', 'dw_eos');
        $objRefereeReward->autoCommit(false);
        try {
            $log_data = [
                'appid' => 'k3',
                'player' => $bet_player,
                'referee_player' => $referee_player,
                'reward_eos' => floor(self::EOS_REWARD_RATE * $bet_data['bet_amount']),
                'commission_rate' => self::EOS_REWARD_RATE,
                'create_time' => date('Y-m-d H:i:s'),
            ];
            $log_data += $bet_data;
            if (!$row) {
                //增加初次gt奖励
                $log_data['reward_gt'] = self::REWARD_GT;
            }

            if (!$objRefereeReward->addObject($log_data)) {
                throw new Exception('system error: add balance log, try again!', CODE_NORMAL_ERROR);
            }

            //增加奖励总数
            $user = $objAccountReward->getRow(['player' => $referee_player]);
            $user_data = [];
            if ($user) {
                $user_data['eos_balance'] = $user['eos_balance'] + $log_data['reward_eos'];
                if ($log_data['reward_gt']) {
                    $user_data['gt'] = intval($user['gt']) + intval($log_data['reward_gt']);
                }
                $ret = $objAccountReward->updateObject($user_data, ['player' => $referee_player]);
            } else {
                $user_data['eos_balance'] = $log_data['reward_eos'];
                if ($log_data['reward_gt']) {
                    $user_data['gt'] = $log_data['reward_gt'];
                }
                $user_data['player'] = $referee_player;
                $ret = $objAccountReward->addObject($user_data);
            }

            if (!$ret) {
                throw new Exception('system error: add balance log, try again!', CODE_NORMAL_ERROR);
            }
            $objRefereeReward->commit();
        } catch (Exception $e) {
            $objRefereeReward->rollback();
            $objLock->unlock($bet_player);
            alermErrorMsg($e->getMessage());
            return [false, $e->getMessage()];
        }
        $objLock->unlock($bet_player);

        return [true, ''];
    }

    /**
     * 绑定推荐账户
     * @param $bet_player
     * @param $referee_player
     * @param $objReferee
     * @param $objAccount
     * @return array
     */
    public static function bindReferee($bet_player, $referee_player, $objReferee = null, $objAccount = null) {
        if ($bet_player == $referee_player) {
            return [false, '推荐人不能为自己'];
        }

        //用户只能有一个推荐人
        !$objReferee && $objReferee = new TableHelper('referee_relation', 'dw_eos');
        $row = $objReferee->getRow(['player' => $bet_player]);
        if (!$row) {
            !$objAccount && $objAccount = new Account();
            $account = $objAccount->objTable->getRow(['player' => $bet_player], ['_field' => 'total_bet_amount']);


            if ($account['total_bet_amount'] > 0) {
                return [false, '只有用户第一次下注才能绑定邀请推广'];
            } else {
                // 可以发奖励
                $objReferee->addObject(['player' => $bet_player, 'referee_player' => $referee_player, 'create_time' => date('Y-m-d H:i:s')]);
                return [true, ''];
            }
        } else {
            if ($row['referee_player'] != $referee_player) {
                //一个用户只能有一个上线
                return [false, '一个用户只能有一个邀请人，此用户的邀请人为：'. $row['referee_player']];
            } else {
                return [true, ''];
            }
        }
    }

    /**
     * 玩家vip等级
     * @author solu
     * @param $players
     * @return array
     */
    public function getVipLevels($players) {
        if (!$players) {
            return [];
        }
        $items = $this->objTable->getAll(['player' => $players], ['_field' => 'player, vip_level']);

        return arrayFormatKey($items, 'player', 'vip_level');
    }
}