import { ApiCall } from "tsrpc";
import { ReqReceive, ResReceive } from '../../../shared/protocols/event/payForDiamond/PtlReceive';
import { playerCanReceive } from './ApiCanReceive';
import { PublicShared } from "../../../shared/public/public";
import { PlayerFun } from "../../../public/player";

type diamondWeightGroup = {
    weight: number;
    numrange: [ min: number, max: number];
};

const hasGotKeyPrefix = 'payForDiamond:hasGot:'; // 已领取额度
const hasGotLockKey = "payForDiamond:lock"; // 更新锁, TTL 1s
const showOffListKeyPrefix = 'payForDiamond:ShowOff:'; // 需要炫耀的玩家列表。 其中需要包含信息：玩家区服, 玩家玩家名称, 领到的数量
/**
 * @param groups 各分组及其权重
 */
function randomWithWeight(groups: diamondWeightGroup[]) {
    let maxAmount: number; 
    let totalWeights = 0;
    for (const group of groups) {
        totalWeights += group.weight;
        if (!maxAmount) {
            maxAmount = group.numrange[1];
        } else {
            maxAmount = Math.max(maxAmount, group.numrange[1]);
        }
    }
    const randomValue = Math.random() * totalWeights; // 随机值落在[0, totalWeights) 之间
    let currSum = 0;
    for (const group of groups) {
        if (currSum <= randomValue && randomValue < currSum + group.weight) {
            return { group, maxAmount };
        } else {
            currSum += group.weight;
        }
    }
    const length = groups.length;
    return { group: groups[length - 1], maxAmount };
}

/**
 * 计算玩家分得的钻石数量
 * @param remaining 
 * @param group 
 * @param maxAmount
 */
function calcDiamondGot(remaining: number, group: diamondWeightGroup, maxAmount: number) {
    const [min, max] = group.numrange;
    const randomAmount = Math.floor(Math.random() * (max - min)) + min + 1; // max 值应能够取到, 故 +1
    // 剩余数额小于组内随机得到的值, 全给吧
    if (randomAmount > remaining) { 
        return remaining;
    }
    // 随机值大于最大值, 取最大值
    if (randomAmount > maxAmount) {
        return maxAmount;
    }
    return randomAmount;
}

export default async function (call: ApiCall<ReqReceive, ResReceive>) {
    const canReceiveResult = await playerCanReceive(call);
    if (canReceiveResult) { // 该值不存在的情况已被函数直接返回 error, 无需再处理
        if (!canReceiveResult.result) {
            return call.succ({
                amount: 0,
                timesRemaining: 0,
                showOff: false
            });
        }
        const activityData = canReceiveResult.activityInfo.data;
        // 更新 redis 领取记录之前先加锁, 防止多领
        const lockResult = await G.crossioredis.setnx(hasGotLockKey, 1);
        if (lockResult) {
            await G.crossioredis.expire(hasGotLockKey, 1); // 设置 ttl 避免死锁
            const activityId = call.req.activityId;
            const hasReceivedKey = hasGotKeyPrefix + activityId;
            const hasReceivedStr = await G.crossioredis.get(hasReceivedKey);
            const hasReceived = hasReceivedStr? parseInt(hasReceivedStr) : 0;
            const remaining = activityData['totalmoney'] - hasReceived;
            if (remaining <= 0) {
                return call.succ({
                    amount: 0
                });
            } else {
                const { group, maxAmount } = randomWithWeight(activityData['groupConf']['base']['arr']);
                const gotAmount = calcDiamondGot(remaining, group, maxAmount);
                await G.crossioredis.incrby(hasReceivedKey, Math.abs(gotAmount)); // 添加已领取的额度
                await G.crossioredis.del(hasGotLockKey); // 移除锁
                await PlayerFun.sendPrize(call,  [{ 'a': 'attr', 't': 'rmbmoney', 'n': gotAmount }]);
                const showOff = gotAmount >= activityData['groupConf']['base']['loglimit'];
                call.succ({ // 领取核心逻辑完成, 请求可以返回了
                    amount: gotAmount,
                    timesRemaining: 0,
                    showOff,
                });
                // 添加玩家领取记录
                const zeroTime = PublicShared.getToDayZeroTime();
                const setObj = {};
                setObj[zeroTime] = gotAmount;
                await G.mongodb.cEvent('payForDiamond').updateOne({ uid: call.uid, type: 'payForDiamond' }, {
                    $set: setObj
                }, {upsert: true});
                // 炫耀
                if (showOff) {
                    const msg = JSON.stringify({
                        name: call.conn.gud.name, gotAmount, serverID: call.conn.gud.sid
                    });
                    const showOffListKey = showOffListKeyPrefix + activityId;
                    await G.crossioredis.lpush(showOffListKey, msg);
                    await G.crossioredis.ltrim(showOffListKey, 0, 49); // 限制列表保存 50 条消息, 避免无限增长
                }
            }
        } else {
            return call.succ({
                amount: 0
            });
        }
    }
}