package com.cftech.module.activity.core.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cftech.module.activity.core.enums.ProbabilityMethod;
import com.cftech.module.activity.core.model.ActivityBaseEntity;
import com.cftech.module.activity.core.service.ActivityBaseResultService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by liuling on 2017/4/18.
 */
@Slf4j
@Component
public class ActivityBaseRedisAPI<T extends ActivityBaseEntity> {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ActivityBaseResultService resultService;

    // 奖品总库存
    private String TOTAL_INV_KEY;
    // 奖品库存
    private String INV_KEY_PREFIX;
    // 参与人员的储存前缀
    // private String PLAYER_KEY_PREFIX;
    // 中奖人员的储存前缀
    //private String PRIZEPLAYER_KEY_PREFIX;
    // 每天可玩次数
    private String COUNT_DAY_PREFIX;
    // 每人最多玩的次数
    private String COUNT_PERSON_PLAY;
    // 每人最多中奖次数
    //private String COUNT_PRIZEMAX_PREFIX;
    // 按人员的中奖次数
    private String COUNT_PLAYERPRIZE_PREFIX;
    // 所有人总第几次玩
    private String COUNT_TOTAL_PLAY;

    private Random random;

    //初始化
    public ActivityKeys init(T t) {
        ActivityKeys keys = new ActivityKeys();
        //获取当天的字符串
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String today = sdf.format(new Date());

        String REDIS_KEY_PREFIX = redisKeyPrefix(t);
        keys.setTotalInvKey(REDIS_KEY_PREFIX.concat("TOTAL.INV"));
        keys.setInvKeyPrefix(REDIS_KEY_PREFIX.concat("INV"));
        //PLAYER_KEY_PREFIX = REDIS_KEY_PREFIX.concat("PLAYER:");
        //PRIZEPLAYER_KEY_PREFIX = REDIS_KEY_PREFIX.concat("PRIZE:PLAYER:");
        keys.setCountDayPrefix(REDIS_KEY_PREFIX.concat("COUNT:DAY:".concat(today).concat(":")));
        keys.setCountPersonPlay(REDIS_KEY_PREFIX.concat("COUNT:PLAY:"));
        //COUNT_PRIZEMAX_PREFIX = REDIS_KEY_PREFIX.concat("COUNT:PRIZEMAX:");
        keys.setCountPlayerPrizePrefix(REDIS_KEY_PREFIX.concat("COUNT:PLAYERPRIZE:"));
        keys.setCountTotalPlay(REDIS_KEY_PREFIX.concat("COUNT:PLAY:TOTAL"));

        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        //redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        keys.setRandom(new Random(timeGen()));
        return keys;
    }

    protected long timeGen() {
        return System.currentTimeMillis();
    }


    /**
     * 活动的redisKey前缀
     * ACTIVITY:账号Id:活动类名:活动的Id:
     *
     * @param t 活动对象
     * @return
     */
    public String redisKeyPrefix(T t) {
        return "ACTIVITY:" + t.getAccountsId() + ":" + t.getClass().getSimpleName() + ":" + t.getId()+":";
    }


    /**
     * 开始活动
     *
     * @param t
     * @return
     */
    public boolean startActivity(T t, ActivityKeys keys) {
        // 如果已经启动不需要再次启动
        if (t.getStatus().equals("1")) {
            log.info("活动:{}, 活动名称:{}已经启动，不需要再次启动", t.getClass().getSimpleName(), t.getTitle());
            return true;
        }
        // 先确定活动中是否包含奖项
        if (StringUtils.isEmpty(t.getPrizes())) {
            log.error("活动:{}, 活动名称:{}中并没有存在奖项设置", t.getClass().getSimpleName(), t.getTitle());
            return false;
        }
        //与现有时间比较，是否在活动期间内
        Date now = new Date();
        if (now.compareTo(t.getStartTime()) == -1 || now.compareTo(t.getEndTime()) == 1) {
            //时间小于开始时间或者大于结束时间
            log.error("活动:{}, 活动名称:{} 不能开启，因为不在活动期间内 {} - {}", new Object[]{t.getClass().getSimpleName(), t.getTitle(), t.getStartTime().toString(), t.getEndTime().toString()});
            return false;
        }
        // 判断是否存在活动总库存，如果存在则活动已经存在
        if (redisTemplate.hasKey(keys.getTotalInvKey())) {
            log.info("活动:{}, 活动名称:{}已经启动，不需要再次启动", t.getClass().getSimpleName(), t.getTitle());
            return true;
        }

        //获取奖项列表
        JSONArray prizesArr = JSON.parseArray(t.getPrizes());

        if (t.getProbabilityMethod().equals(ProbabilityMethod.NORMAL)) {
            //总库存数量
            int totalInv = 0;
            for (int i = 0, j = prizesArr.size(); i < j; i++) {
                JSONObject obj = prizesArr.getJSONObject(i);
            int qty = obj.getIntValue("qty");
            //设置库存数量
            redisTemplate.opsForValue().increment(keys.getInvKeyPrefix().concat(":").concat(obj.getString("code")), qty);
            totalInv += qty;
        }

            //设置库存总数
            redisTemplate.opsForValue().increment(keys.getTotalInvKey(), totalInv);
            return true;
        } else if (t.getProbabilityMethod().equals(ProbabilityMethod.PERSONNUM)) {
            //如果是人数决定，则使用随机数将奖项分配至玩的人次上
            int accessPersonCount = t.getAttendPersonNum();
            int tmpNum = 0;
            Map<String, String> map = new HashMap<>();
            HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
            for (int i = 0, j = prizesArr.size(); i < j; i++) {
                JSONObject obj = prizesArr.getJSONObject(i);
                int qty = obj.getIntValue("qty");
                // String prizeCode = obj.getString("code");
                int range = accessPersonCount/qty;

                //分割中奖区间
                for (int m = 0; m < qty; m++) {
                    int max = (m+1) * range;
                    int min = m*range;
                    tmpNum = genPrizeNum(map, min, max, keys);
                    if (tmpNum == 0) {
                        log.error("计算随机分布人员中奖时出错了");
                        return false;
                    } else {
                        map.put(String.valueOf(tmpNum), obj.toJSONString());

                    }
                }
            }
            hashOps.putAll(keys.getInvKeyPrefix(), map);
            redisTemplate.opsForValue().increment(keys.getTotalInvKey(), 1);
            return true;
        } else if (t.getProbabilityMethod().equals(ProbabilityMethod.TIMELINE)) {

            // todo 待实现
            // 如果按时间分布决定，则使用随机数将奖项分配至时间上
//            long startTime = t.getStartTime().getTime();
//            long endTime = t.getEndTime().getTime();
//            long dependsTime = endTime - startTime;
//            Map<String, String> map = new HashMap<>();
//            HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
//            long timeline = 0;
//            for (int i = 0, j = prizesArr.size(); i < j; i++) {
//                JSONObject obj = prizesArr.getJSONObject(i);
//                int qty = obj.getIntValue("qty");
//                // String prizeCode = obj.getString("code");
//                long range = dependsTime/qty;
//                //分割中奖区间
//                for (int m = 0; m < qty; m++) {
//                    long max = (m+1) * range;
//                    long min = m*range;
//                    timeline = genPrizeTime(map, min, max);
//                    if (timeline == null) {
//                        log.error("计算随机分布时间中奖时出错了");
//                        return false;
//                    } else {
//                        map.put(timeline, obj.toJSONString());
//
//                    }
//                }
//            }
//            hashOps.putAll(INV_KEY_PREFIX, map);

        }

        return true;
    }

    private long genPrizeTime(Map<String, String> map, long min, long max) {
        return 0;
    }


    private int genPrizeNum(Map<String, String> map, int min, int max, ActivityKeys keys) {
        int tmp = 0;
        boolean flag = true;
        if (min == 0) {
            while (flag) {
                min = 1;
                tmp = keys.getRandom().nextInt(max)%(max-min+1) + min;
                if (!map.containsKey(String.valueOf(tmp))) {

                    return tmp;
                }
            }

        } else {
            while (flag) {
                tmp = keys.getRandom().nextInt(max)%(max-min+1) + min;
                if (!map.containsKey(String.valueOf(tmp))) {
                    return tmp;
                }
            }
        }
        return 0;
    }

    /**
     * 结束活动
     *
     * @param t
     */
    public void endActivity(T t, ActivityKeys keys) {
        // 如果已经启动不需要再次结束
        if (t.getStatus().equals("2")) {
            log.info("活动:{}, 活动名称:{}已经结束，不需要再次结束", t.getClass().getSimpleName(), t.getTitle());
            return;
        }

        // 活动总奖品库存Key值
        if (redisTemplate.hasKey(keys.getTotalInvKey())) {
            Set<String> delKeys = redisTemplate.keys(redisKeyPrefix(t).concat("*"));
            //如果存在则删除key值
            redisTemplate.delete(delKeys);
            log.info("活动:{}, 活动名称:{}已经结束", t.getClass().getSimpleName(), t.getTitle());
        } else {
            log.info("活动:{}, 活动名称:{}已经结束,不需要再次结束", t.getClass().getSimpleName(), t.getTitle());
        }

    }

    /**
     * 中奖之后
     * 1.需要扣减redis中的库存
     * 2.需要更新中奖记录
     *
     * @param openId
     * @param t
     * @param prize
     */
    public JSONObject normalPrizeHandler(String openId, T t, JSONObject prize, ActivityKeys keys) {
        JSONObject retObj = new JSONObject();
        // 活动总库存
        if (!redisTemplate.hasKey(keys.getTotalInvKey())) {
            log.info("活动:{}, 活动名称:{} 不在启动状态", t.getClass().getSimpleName(), t.getTitle());
            retObj.put("errorNo","1");
            retObj.put("errorMsg","活动不在开启状态");
            retObj.put("errorEnMsg","The activity is not in the open state");
            return retObj;
        }

        String prizeCode = prize.getString("code");
        String prizeName = prize.getString("name");

        String prizeInvKey = keys.getInvKeyPrefix().concat(":").concat(prizeCode);


        // 查看活动奖品的库存
        int totalInv = Integer.parseInt(redisTemplate.opsForValue().get(keys.getTotalInvKey()).toString());
        if (totalInv == 0) {
            log.info("活动:{}, 活动名称:{} 奖品总库存为0", t.getClass().getSimpleName(), t.getTitle());
            retObj.put("errorNo","2");
            retObj.put("errorEnMsg","Not winning the prize");
            retObj.put("errorMsg","未中奖");
            return retObj;
        }


        //查看中奖奖品的库存
        long inventoryQty = Integer.parseInt(redisTemplate.opsForValue().get(prizeInvKey).toString());
        // 如果为0，则返回未中奖
        if (inventoryQty <= 0) {
            log.info("活动:{}, 活动名称:{}，奖品:{} 库存为0", new Object[]{t.getClass().getSimpleName(), t.getTitle(), prizeName});
            retObj.put("errorNo","2");
            retObj.put("errorMsg","未中奖");
            retObj.put("errorEnMsg","Not winning the prize");
            return retObj;
        }

        //执行扣减奖品库存
        long leftInventoryQty = redisTemplate.opsForValue().increment(prizeInvKey, -1);

        if (leftInventoryQty <= 0) {
            log.info("活动:{}, 活动名称:{}，奖品:{} 库存为0", new Object[]{t.getClass().getSimpleName(), t.getTitle(), prizeName});
            retObj.put("errorNo","2");
            retObj.put("errorMsg","未中奖");
            retObj.put("errorEnMsg","Not winning the prize");
            return retObj;
        }

        // 扣减总奖品数量
        redisTemplate.opsForValue().increment(keys.getTotalInvKey(), -1);
        // 增加中奖人员和奖项
        // redisTemplate.opsForValue().set(PRIZEPLAYER_KEY_PREFIX.concat(openId), prizeName);
        // 增加人员的中奖次数
        redisTemplate.opsForValue().increment(keys.getCountPlayerPrizePrefix().concat(openId), 1);
        // 记录用户中奖记录
        resultService.prizeLog(t.getAccountsId(), t.getClass().getSimpleName(), t.getId(), t.getTitle(), openId, prizeName, 1, prize.getString("desc"), prize.getString("type"), prize.getString("amount"),t.getMeetingId());
        retObj.put("errorNo","0");
        retObj.put("errorMsg",t.getWintips());
        retObj.put("errorEnMsg",t.getWintipsEn());
        retObj.put("data", prize);

        log.info("活动:{}, 活动名称:{}，用户:{} 中奖，奖品为{}", new Object[]{t.getClass().getSimpleName(), t.getTitle(), openId, prize.toString()});
        return retObj;
    }

    public JSONObject play(String openId, T t, ActivityKeys keys) {
        JSONObject retObj = new JSONObject();

        if (!t.getStatus().equals("1")) {
            log.info("活动:{}, 活动名称:{} 活动不在开启状态", t.getClass().getSimpleName(), t.getTitle());
            retObj.put("errorNo","1");
            retObj.put("errorMsg",t.getEndTitle());
            retObj.put("errorEnMsg",t.getEndTitleEn());
            return retObj;
        }

        //与现有时间比较，是否在活动期间内
        Date now = new Date();
        if (now.compareTo(t.getStartTime()) == -1 || now.compareTo(t.getEndTime()) == 1) {
            //时间小于开始时间或者大于结束时间
            log.error("活动:{}, 活动名称:{} 不能play，因为不在活动期间内 {} - {}", new Object[]{t.getClass().getSimpleName(), t.getTitle(), t.getStartTime().toString(), t.getEndTime().toString()});
            retObj.put("errorNo","1");
            retObj.put("errorEnMsg","Not in the period of the activity");
            retObj.put("errorMsg","不在活动期间内");
            return retObj;
        }

        // 先确定活动中是否包含奖项
        if (StringUtils.isEmpty(t.getPrizes())) {
            log.error("活动:{}, 活动名称:{}中并没有存在奖项设置", t.getClass().getSimpleName(), t.getTitle());
            retObj.put("errorNo","1");
            retObj.put("errorEnMsg","There are no awards in the event");
            retObj.put("errorMsg","活动并没有存在奖项设置");
            return retObj;
        }

        //可中奖次数,这个待定，看客户的需求是否需要控制对每个人的可玩次数
        //String actPersonCanPrizeKey = COUNT_PRIZEMAX_PREFIX.concat(openId);
        //已中奖次数
        String personPrizeNumKey = keys.getCountPlayerPrizePrefix().concat(openId);
        //每天玩的次数
        String actPersonDayKey = keys.getCountDayPrefix().concat(openId);
        //当前人员总共玩的次数
        String actPersonTotalKey = keys.getCountPersonPlay().concat(openId);

        System.out.println("11111111");
        long playDayCount = Long.valueOf(redisTemplate.opsForValue().get(actPersonDayKey)==null?"0":redisTemplate.opsForValue().get(actPersonDayKey).toString());
        long playTotalCount =Long.valueOf(redisTemplate.opsForValue().get(actPersonTotalKey)==null?"0":redisTemplate.opsForValue().get(actPersonTotalKey).toString());
        int personPrizeNum = Integer.parseInt(redisTemplate.opsForValue().get(personPrizeNumKey)==null ? "0" : redisTemplate.opsForValue().get(personPrizeNumKey).toString());
        long totalCount =   Long.valueOf(redisTemplate.opsForValue().get(keys.getCountTotalPlay())==null?"0":redisTemplate.opsForValue().get(keys.getCountTotalPlay()).toString());
        //被玩一次，统计总玩的次数


        if (t.getPerPersonDrawNum() <= playTotalCount) {
            log.error("活动:{}, 活动名称:{}, 用户:{} 已超过总可玩次数", new Object[] {t.getClass().getSimpleName(), t.getTitle(), openId});
            retObj.put("errorNo","1");
            retObj.put("errorMsg",t.getDrawAgainReply());
            retObj.put("errorEnMsg",t.getDrawAgainReplyEn());
            return retObj;
        }

        if (t.getPerDayDrawNum() <= playDayCount) {
            log.error("活动:{}, 活动名称:{}, 用户:{} 已超过每天可玩次数", new Object[] {t.getClass().getSimpleName(), t.getTitle(), openId});
            retObj.put("errorNo","1");
            retObj.put("errorEnMsg",t.getDrawAgainDayReplyEn());
            retObj.put("errorMsg",t.getDrawAgainDayReply());
            return retObj;
        }

        /*if (t.getPerDayDrawNum() <= playDayCount) {
            log.error("活动:{}, 活动名称:{}, 用户:{} 已超过每天可玩次数", new Object[] {t.getClass().getSimpleName(), t.getTitle(), openId});
            retObj.put("errorNo","1");
            retObj.put("errorMsg","超过每天可玩次数");
            return retObj;
        }*/
        //通过次数的判断，消耗机会
        redisTemplate.opsForValue().increment(actPersonDayKey, 1);
        redisTemplate.opsForValue().increment(actPersonTotalKey, 1);
        redisTemplate.opsForValue().increment(keys.getCountTotalPlay(), 1);

        totalCount = Long.valueOf(redisTemplate.opsForValue().get(keys.getCountTotalPlay())==null?"0":redisTemplate.opsForValue().get(keys.getCountTotalPlay()).toString());

        if (t.getPerPersonCanPrize() <= personPrizeNum) {
            log.error("活动:{}, 活动名称:{}, 用户:{} 已达可中奖次数，不能再次中奖", new Object[] {t.getClass().getSimpleName(), t.getTitle(), openId});
            //不能再次中奖，返回未中奖
            retObj.put("errorNo","2");
            retObj.put("errorEnMsg","Not winning the prize");
            retObj.put("errorMsg","未中奖");
            return retObj;
        }

        log.info("活动:{}, 活动名称:{}, 用户:{} 玩了1次", new Object[] {t.getClass().getSimpleName(), t.getTitle(), openId});


        // 使用的概率算法
        if (t.getProbabilityMethod().equals(ProbabilityMethod.NORMAL)) {
            retObj = normalProbability(openId, t, keys);
            log.info("活动:{}, 活动名称:{}, 用户:{} 玩了1次的结果:{}", new Object[] {t.getClass().getSimpleName(), t.getTitle(), openId, retObj.toString()});
            return retObj;
        } else if (t.getProbabilityMethod().equals(ProbabilityMethod.PERSONNUM)) {
            retObj = personProbability(openId,totalCount, t, keys);
            log.info("活动:{}, 活动名称:{}, 用户:{} 玩了1次的结果:{}", new Object[] {t.getClass().getSimpleName(), t.getTitle(), openId, retObj.toString()});
            return retObj;
        } else {

        }

        return retObj;

    }

    /**
     * 做人数的概率计算
     * 说明:待整理
     *
     * @param openId 使用的OpenId，如果是企业号，则传入userId
     * @param totalCount 这次玩的总次数
     *@param t  @return
     */
    private JSONObject personProbability(String openId, long totalCount, T t, ActivityKeys keys) {
        String total = String.valueOf(totalCount);
        JSONObject retObj = new JSONObject();
        HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
        if (hashOps.hasKey(keys.getInvKeyPrefix(), total)) {
            //可发奖
            JSONObject obj = JSONObject.parseObject(hashOps.get(keys.getInvKeyPrefix(), total));
            redisTemplate.opsForValue().increment(keys.getCountPlayerPrizePrefix().concat(openId), 1);
            // 记录用户中奖记录
            resultService.prizeLog(t.getAccountsId(), t.getClass().getSimpleName(), t.getId(), t.getTitle(), openId, obj.getString("name"), 1, obj.getString("desc"), obj.getString("type"), obj.getString("amount"),t.getMeetingId());
            retObj.put("errorNo","0");
            retObj.put("data", obj);
            log.info("活动:{}, 活动名称:{}，用户:{} 中奖，奖品为{}", new Object[]{t.getClass().getSimpleName(), t.getTitle(), openId, obj.toString()});
            return retObj;
        }
        retObj.put("errorNo","2");
        retObj.put("errorEnMsg","Not winning the prize");
        retObj.put("errorMsg","未中奖");

        return retObj;
    }


    /**
     * 做一般的概率计算
     * 说明:待整理
     *
     * @param openId 使用的OpenId，如果是企业号，则传入userId
     * @param t
     * @return
     */
    public JSONObject normalProbability(String openId, T t, ActivityKeys keys) {
        JSONObject retObj = new JSONObject();
        //总权重
        double totalWeight = 0.0;

        //随机数
        double randomNumber = Math.random();

        //获取奖项列表
        JSONArray prizesArr = JSON.parseArray(t.getPrizes());

        //求和总权重，防止如果有人写错数据
        for (int i = 0, j = prizesArr.size(); i < j; i++) {
            JSONObject obj = prizesArr.getJSONObject(i);
            //取得概率
            totalWeight += obj.getDoubleValue("probability");
        }
        //根据随机数在所有奖品分布的区域并确定所抽奖品
        double d1 = 0;
        double d2 = 0;

        for (int i = 0, j = prizesArr.size(); i < j; i++) {
            JSONObject obj = prizesArr.getJSONObject(i);
            //取得概率，譬如0.4即40%，则需要转换成4
            d2 += obj.getDoubleValue("probability") / totalWeight;
            if (i == 0) {
                d1 = 0;
            } else {
                d1 += prizesArr.getJSONObject(i - 1).getDoubleValue("probability") / totalWeight;
            }
            //如果随机数落在区间内，则中奖
            if (randomNumber >= d1 && randomNumber <= d2) {
                /*
                    中奖之后
                    1.需要扣减redis中的库存
                    2.需要更新中奖记录
                 */
                return normalPrizeHandler(openId, t, obj, keys);
            }
        }

        retObj.put("errorNo","2");
        retObj.put("errorEnMsg","Not winning the prize");
        retObj.put("errorMsg","未中奖");
        return retObj;
    }
}
