<?php

namespace app\api\controller\coin;

use app\admin\model\user\User;
use app\admin\model\user\UserBill;
use app\models\coin\UserCoinTransfer;
use tw\redis\UserRds;
use app\models\store\StoreOrderCartInfo;
use app\models\system\DictCoin;
use app\models\user\UserCoin;
use app\Request;
use crmeb\services\{UtilService, JsonService};
use think\facade\Config;
use think\facade\Cache;

// 精度相关的函数
// 转换为 string, 不同于 strval()，这个对于极小 float 不会使用科学计数法表示
function twstr($val, $scale = 2): string
{
    return number_format($val, $scale, '.', '');
}

function twadd($l, $r, $scale = 2): string
{
    return bcadd(twstr($l, $scale + 2), twstr($r, $scale + 2), $scale);
}

function twsub($l, $r, $scale = 2): string
{
    return bcsub(twstr($l, $scale + 2), twstr($r, $scale + 2), $scale);
}

function twmul($l, $r, $scale = 2): string
{
    return bcmul(twstr($l, $scale + 2), twstr($r, $scale + 2), $scale);
}

function twdiv($l, $r, $scale = 2): string
{
    return bcdiv(twstr($l, $scale + 2), twstr($r, $scale + 2), $scale);
}

function twmod($l, $r, $scale = 2): string
{
    return bcmod(twstr($l, $scale + 2), twstr($r, $scale + 2), $scale);
}

function twcomp($l, $r, $scale = 2): string
{
    return bccomp(twstr($l, $scale + 2), twstr($r, $scale + 2), $scale);
}

class UserCoinController
{
    /**
     * @api {get} /coin/status 挖矿当前状态
     * @apiName GetCoinStatus
     * @apiGroup User.Coin
     * 
     */
    public function status(Request $request)
    {
        // 是否开启挖矿
        $symbol = Config::get('app.mining_symbo');
        //
        $coinInfo = Cache::get($symbol);
        if (!$coinInfo) {
            $coinInfo = DictCoin::where('symbol', $symbol)->find();
            if (!$coinInfo) {
                return app('json')->failed('活动临时关闭，请联系客服');
            }
            $coinInfo = $coinInfo->toArray();
            Cache::set($symbol, $coinInfo);
        }
        // print_r($coinInfo);
        //
        $defStatus = [
            'boot' => 0,
            'stop' => 0,
            'progress' => 0,
            'symbol' => $symbol,
            'icon' => $coinInfo['icon'],
            'price' => $coinInfo['price'],
            'total' => 0,
            'step' => 0,
            'ts' => 0,
        ];

        // 沒配置挖什麼
        if (!$symbol) {
            warnlog('mining disabled: $symbol');
            return app('json')->successful($defStatus);
        }

        // 獲取用戶進度
        $uid = $request->uid();
        $rds = new UserRds();
        $mymining = json_decode($rds->get($uid, UserRds::FIELD_MINING), true);
        if (!$mymining) {
            $mymining = $defStatus;
            return app('json')->successful($defStatus);
        }
        //
        if ($mymining['progress'] > 0.0) {
            $mymining = $this->calcMining($uid, $coinInfo, $mymining);
            $rds->set($uid, UserRds::FIELD_MINING, json_encode($mymining));
        } else {    // 本次挖矿已经结束
            $mymining['total'] = UserCoin::where('uid', $uid)->where('symbol', $symbol)->value('balance') ?? 0.0;
            $mymining['symbol'] = $symbol;
            $mymining['icon'] = $coinInfo['icon'];
            $mymining['price'] = $coinInfo['price'];
            $mymining['step'] = self::get_step();
        }
        return app('json')->successful([
            'symbol' => $mymining['symbol'],
            'icon' => $mymining['icon'],
            'price' => $mymining['price'],
            'step' => $mymining['step'],
            'progress' => $mymining['progress'],
            'total' => $mymining['total'],
        ]);
    }

    /**
     * 根据 $step 返回一个新 $step
     * 新的 $step 值为围绕 旧 $step 周围的随机值，上下波动幅度为 10%
     */
    protected function floatStep($step)
    {
        $amp = twdiv($step, 10, 8);
        $min = twsub($step, $amp, 8);
        $max = twadd($step, $amp, 8);
        $distance = twsub($max, $min, 8);
        $section = twdiv($distance, 10, 8);
        return twmul($section, mt_rand(0, 10), 8) + $min;
    }

    /**
     * 根据上次挖矿状态, 和过去的时长, 计算当前的状态
     * @param $p
     * @return mixed
     */
    protected function calcMining($uid, $coinInfo, $p)
    {
        if (
            !isset($p['ts']) ||
            !isset($p['boot']) ||
            !isset($p['stop']) ||
            !isset($p['step']) ||
            !isset($p['progress'])
        ) {
            warnlog('error format.');
            return $p;
        }

        if ($p['progress'] <= 0) {
            return $p;
        }
        $step = $this->floatStep($p['step']);
        $now = time();
        $secs_passed = $now - $p['ts'];     // 从上次到现在
        //
        if ($now >= $p['stop']) { // 挖矿结束
            $secs_remain = $p['stop'] - $p['ts'];
            if ($secs_remain < 0) {
                $secs_remain = 0;
            }
            // 本次个数
            $count = floatval(twmul($step, $secs_remain, 8));
            $p['progress'] += floatval(twadd($p['progress'], $count, 8));
            // save to db
            UserCoinTransfer::beginTrans();
            $r1 = UserCoinTransfer::addMining($uid, $p['order_id'], $p['symbol'], $p['progress']);
            $r2 = UserCoin::upsertCoin($uid, $p['symbol'], $p['progress']);
            UserCoinTransfer::checkTrans($r1 && $r2);
            if (!$r1 || !$r2) {
                $amount = $p['progress'];
                errlog("user<$uid> save transfer failed, amount<$amount>");
                return $p;
            }
            // -- save to db
            $p['ts'] = $now;
            $p['total'] = floatval(twadd($p['total'], $p['progress'], 8));
            $p['progress'] = 0;
            // 更换为可能的新的币种
            $p['symbol'] = $coinInfo['symbol'];
            $p['icon'] = $coinInfo['icon'];
            $p['price'] = $coinInfo['price'];
            $p['step'] = self::get_step();
            return $p;
        }
        // 进行中
        $count = floatval(twmul($step, $secs_passed, 8));
        $p['progress'] = floatval(twadd($p['progress'], $count, 8));
        $p['ts'] = $now;

        return $p;
    }

    public static function get_step(): float
    {
        $secs_unit = Config::get('app.mining_sec_unit');
        $reward_unit = Config::get('app.mining_num_per_unit');
        return twdiv($reward_unit, $secs_unit, 8);
    }

    /**
     * @api {post} /coin/boot 启动挖矿
     * @apiName PostCoinBoot
     * @apiGroup User.Coin
     * 
     */
    public function boot(Request $request)
    {
        $uid = $request->uid();
        $now = time();
        // 是否停止
        if (Config::get('activity.mining_stopped')) {
            return app('json')->fail('活动已暂停,稍后开启');
        }

        // 是否开启活动
        $symbol = Config::get('app.mining_symbo');
        if (!$symbol) {
            return app('json')->fail('本活动未开启');
        }
        // 是否已经开启
        $rds = new UserRds();
        $mining = json_decode($rds->get($uid, UserRds::FIELD_MINING), true);
        if ($mining) {
            if (
                isset($mining['progress']) &&
                $mining['progress'] > 0.0 &&
                isset($mining['boot']) &&
                isset($mining['stop']) &&
                isset($mining['ts'])
            ) {
                return app('json')->fail('已启动');
            }
        }

        // 是否有订单
        $orderId = StoreOrderCartInfo::getMiningOrderId($uid);
        if (!$orderId) {
            return app('json')->fail('参加活动下单后可启动，请参看 首页->官方资讯->新手教程');
        }
        // 标记订单
        if (!StoreOrderCartInfo::setMining($orderId)) {
            return app('json')->fail('启动失败');
        }
        // 开启
        $step = self::get_step();
        $progress = $step;
        $icon = DictCoin::getIcon($symbol);
        // 已挖总额
        $balance = UserCoin::where('uid', $uid)->where('symbol', $symbol)->value('balance') ?? 0.0;
        // 单次活动时长
        $hours = Config::get('app.mining_time');
        if (count($hours) != 2 || $hours[0] > $hours[1]) {
            warnlog('app.mining_time config error.');
            return app('json')->fail('启动失败，请联系客服');;
        }
        $hour = random_int($hours[0], $hours[1]);
        $stop = $now + $hour * 60 * 60;

        $suc = $rds->set($uid, UserRds::FIELD_MINING, json_encode([
            'boot' => $now,
            'step' => $step,
            'stop' => $stop,
            'symbol' => $symbol,
            'icon' => $icon,
            'price' => 0,
            'progress' => $progress,
            'total' => $balance,
            'ts' => $now + 1,
            'order_id' => $orderId,
        ]));

        if ($suc != 0 && $suc != 1) {
            StoreOrderCartInfo::setMining($orderId, 0);
            return app('json')->fail('未成功启动');
        }

        return app('json')->successful([
            'symbol' => $symbol,
            'icon' => $icon,
            'price' => 0,
            'step' => $progress,
            'progress' => $progress,
            'total' => $balance,
        ]);
    }

    /**
     * @api {get} /coin/history 请求转账记录
     * @apiName GetCoinHistory
     * @apiGroup User.Coin
     * 
     */
    public function history(Request $request)
    {
        [$page, $limit] = UtilService::getMore([
            ['page', 1],
            ['limit', 20],
        ], $request, true);
        $uid = $request->uid();
        $rows = UserCoinTransfer::getUserTransferred($uid, $page, $limit);
        return app('json')->successful($rows);
    }

    /**
     * @api {post} /coin/addr 更新钱包地址
     * @apiName PostCoinAddr
     * @apiGroup User.Coin
     * 
     */
    public function updateAddr(Request $request)
    {
        list($symbol, $addr) = UtilService::postMore([
            ['symbol', ''],
            ['addr', ''],
        ], $request, true);
        if (!$symbol || !$addr) {
            return app('json')->fail('参数不可为空');
        }
        if (!DictCoin::where('symbol', $symbol)->select()->toArray()) {
            return app('json')->fail('未找到目标');
        }

        $uid = $request->uid();
        $suc = UserCoin::upsertAddr($uid, $symbol, $addr);
        if (!$suc) {
            return app('json')->fail('执行失败');
        }
        return app('json')->successful();
    }

    /**
     * @api {post} /coin/transfer 提现
     * @apiName PostCoinTransfer
     * @apiGroup User.Coin
     * 
     */
    public function transfer(Request $request)
    {
        list($symbol, $amount) = UtilService::postMore([
            ['symbol', ''],
            ['amount', 0]
        ], $request, true);

        $uid = $request->uid();

        $user = User::getUserinfos($uid);
        if (!$symbol) {
            return app('json')->fail('参数不可为空');
        }
        $meta = DictCoin::where('symbol', $symbol)->find();
        if (!$meta) {
            return app('json')->fail('未找到币种');
        }
        $meta = $meta->toArray();
        if ($amount < $meta['min_withdrawal']) {
            return app('json')->fail('未达到最低限额');
        }
        $userCoin = UserCoin::where(['uid' => $uid, 'symbol' => $symbol])->find();
        if (!$userCoin) {
            return app('json')->fail('不适用的用户');
        }
        $userCoin = $userCoin->toArray();

        if ($symbol == 'RMB') {
            return $this->_transfer_fiat($user, $amount, $userCoin);
        } else {
            return $this->_transfer_crypto_coin($amount, $symbol);
        }
    }

    protected function _transfer_crypto_coin($amount, $symbol)
    {
        return app('json')->fail('转账加密货币，请联系客服');
    }

    public function _transfer_fiat($user, $amount, $userCoin)
    {
        $uid = $user['uid'];
        $symbol = $userCoin['symbol'];

        $amount = floor($amount);
        if ($userCoin['balance'] < $amount) {
            return app('json')->fail('余额不足');
        }
        // transfer
        UserCoin::beginTrans();
        // 扣除
        $res1 = UserCoin::where(['uid' => $uid, 'symbol' => $symbol])->dec('balance', $amount)->update();
        // 添加佣金
        $res3 = UserBill::income(
            '钱包转出' . $symbol,
            $uid,
            'now_money',
            'brokerage',
            $amount,
            0,
            twadd($user['brokerage_price'], $amount, 2),
            '黑洞星球转入' . $amount . '元'
        );
        $res4 = User::bcInc($uid, 'brokerage_price', $amount, 'uid');
        // 转账记录
        $res2 = true; // UserCoinTransfer::withdrawal($uid, $symbol, $userCoin['addr'], $amount);

        $ok = $res1 && $res2 && $res3 && $res4;
        UserCoin::checkTrans($ok);
        if (!$ok) {
            return app('json')->fail('执行失败');
        }
        return app('json')->successful('转出成功');
    }
}
