package com.xxfc.platform.activity.biz;

import com.ace.cache.annotation.Cache;
import com.ace.cache.annotation.CacheClear;
import com.github.wxiaoqi.security.admin.feign.dto.AppUserDTO;
import com.github.wxiaoqi.security.common.biz.BaseBiz;
import com.github.wxiaoqi.security.common.exception.BaseException;
import com.github.wxiaoqi.security.common.util.ReferralCodeUtil;
import com.xxfc.platform.activity.config.RedissonLock;
import com.xxfc.platform.activity.constant.PrizeGoodsTypeEnum;
import com.xxfc.platform.activity.constant.PrizeTypeEnum;
import com.xxfc.platform.activity.dto.UserCouponSendDTO;
import com.xxfc.platform.activity.entity.ActivityPrize;
import com.xxfc.platform.activity.entity.ActivityWinningRecord;
import com.xxfc.platform.activity.mapper.ActivityPrizeMapper;
import com.xxfc.platform.activity.util.LotteryUtils;
import com.xxfc.platform.activity.vo.ActivityPrizeVo;
import com.xxfc.platform.activity.vo.LotteryVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.redisson.api.RLock;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 奖品设置
 *
 * @author libin
 * @email 18178966185@163.com
 * @date 2019-12-03 16:46:02
 */
@Slf4j
@Transactional(rollbackFor = Exception.class)
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ActivityPrizeBiz extends BaseBiz<ActivityPrizeMapper, ActivityPrize> {

    private final ActivityWinningRecordBiz activityWinningRecordBiz;
    private final ActivityAttendanceRecordBiz activityAttendanceRecordBiz;
    private final UserCouponBiz userCouponBiz;

    private final RedisTemplate<String, Object> redisTemplate;
    @Resource(name = "redisTemplate")
    private ValueOperations<String, Object> valueOperations;
    private final RedissonLock redissonLock;
    @Value("${lottery.expire.days:60}")
    private int expirDays;
    private static final String LOTTERY_PRE_KEY = "lottery:";

    /**
     * 查询奖品
     *
     * @return
     */
    public ActivityPrizeVo selectActivitys() {
        ActivityPrizeVo activityPrizeVo = new ActivityPrizeVo();
        List<ActivityPrize> activityPrizes = mapper.selectAll();
        if (CollectionUtils.isEmpty(activityPrizes)) {
            return activityPrizeVo;
        }
        Map<Integer, List<ActivityPrize>> activityPrizeMap = activityPrizes.stream().collect(Collectors.groupingBy(ActivityPrize::getType, Collectors.toList()));
        activityPrizeVo.setOnlinePrize(activityPrizeMap.get(PrizeTypeEnum.ONLINE.getCode()));
        activityPrizeVo.setLocalePrize(activityPrizeMap.get(PrizeTypeEnum.LOCALE.getCode()));
        return activityPrizeVo;
    }

    /**
     * 保存奖品设置
     *
     * @param activityPrizes
     */
    @CacheClear(pre = LOTTERY_PRE_KEY)
    public void saveActivityPrizes(List<ActivityPrize> activityPrizes) {
        if (CollectionUtils.isEmpty(activityPrizes)) {
            throw new BaseException("奖品不能为空");
        }
        Map<Boolean, List<ActivityPrize>> activityPrizeMap = activityPrizes.stream().collect(Collectors.partitioningBy(x -> Objects.nonNull(x.getId()), Collectors.toList()));
        //保存
        List<ActivityPrize> activityPrizesOfSave = activityPrizeMap.get(Boolean.FALSE);
        if (CollectionUtils.isNotEmpty(activityPrizesOfSave)) {
            activityPrizesOfSave.stream().peek(x -> x.setCrtTime(new Date())).count();
            mapper.insertList(activityPrizesOfSave);
        }
        //更新
        List<ActivityPrize> activityPrizesOfUpdate = activityPrizeMap.get(Boolean.TRUE);
        if (CollectionUtils.isNotEmpty(activityPrizesOfUpdate)) {
            for (ActivityPrize activityPrize : activityPrizesOfUpdate) {
                activityPrize.setUpdTime(new Date());
                mapper.updateByPrimaryKeySelective(activityPrize);
            }
        }
    }

    /**
     * 根据奖品类型查询
     *
     * @param prizeType
     * @return
     */
    @Cache(key = LOTTERY_PRE_KEY + "{1}")
    public List<ActivityPrize> findActivityPrizeByType(Integer prizeType) {
        Example example = new Example(ActivityPrize.class);
        example.orderBy("serialNumber").asc();
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("type", prizeType);
        List<ActivityPrize> activityPrizes = mapper.selectByExample(example);
        if (CollectionUtils.isEmpty(activityPrizes)) {
            return Collections.EMPTY_LIST;
        }

        for (ActivityPrize activityPrize : activityPrizes) {
            if (activityPrize.getTotalStock() != null) {
                String prizeStockKey = String.format("%s%d:%d", LOTTERY_PRE_KEY, prizeType, activityPrize.getSerialNumber());
                redisTemplate.delete(prizeStockKey);
                valueOperations.set(prizeStockKey, Integer.valueOf(activityPrize.getTotalStock().toString()), expirDays, TimeUnit.DAYS);
            }
        }
        return activityPrizes;
    }


    /**
     * 抽奖
     *
     * @param activityId
     * @param prizeType
     * @param appUserDTO
     * @return
     */
    public LotteryVo activityLottery(Integer activityId, Integer prizeType, String lotteryDate, AppUserDTO appUserDTO) {
        LotteryVo lotteryVo = new LotteryVo();
        List<ActivityPrize> activityPrizes = ((ActivityPrizeBiz) AopContext.currentProxy()).findActivityPrizeByType(prizeType);
        ActivityPrize notActivityPrize = activityPrizes.stream().filter(x -> x.getPrizeGoodsType() == 0).findFirst().orElseGet(() -> {
            ActivityPrize activityPrize = new ActivityPrize();
            activityPrize.setName("谢谢参与");
            activityPrize.setPrizeGoodsType(PrizeGoodsTypeEnum.NO_PRIZE.getCode());
            activityPrize.setType(prizeType);
            activityPrize.setSerialNumber(8);
            return activityPrize;
        });
        String lotteryNumKey = String.format("%d:%d:%d", appUserDTO.getUserid(), activityId, prizeType);
        Object lotteryNum = valueOperations.get(lotteryNumKey);
        boolean hasLotteryNum = lotteryNum == null ? activityAttendanceRecordBiz.hasNumberOfLuckyDrawByType(activityId, prizeType, appUserDTO.getUserid()) : ((Integer) lotteryNum) > 0;
        if (hasLotteryNum) {
            //抽奖
            int index = LotteryUtils.getrandomIndex(activityPrizes);
            //获取对应的奖品
            ActivityPrize activityPrize = activityPrizes.get(index);
            //判断是否设置最大奖品数(谢谢参与排除)
            if (activityPrize.getDayMaxUse() != null && activityPrize.getPrizeGoodsType() != PrizeGoodsTypeEnum.NO_PRIZE.getCode()) {
                String key = String.format("%s:%s%d:%s", lotteryDate, LOTTERY_PRE_KEY, activityId, activityPrize.getSerialNumber());
                Long prizeDayUseStock = valueOperations.increment(key);
                //第一次时设置每天奖品份数的过期时间为1天
                if (prizeDayUseStock.intValue() == 1) {
                    redisTemplate.expire(key, 1, TimeUnit.DAYS);
                }
                //达到日上限
                if (prizeDayUseStock.intValue() > activityPrize.getDayMaxUse()) {
                    activityPrize = notActivityPrize;
                }
            }
            //根据商品key获取库存
            String prizeStockKey = String.format("%s%d:%d", LOTTERY_PRE_KEY, prizeType, activityPrize.getSerialNumber());
            Object prizeStock = valueOperations.get(prizeStockKey);
            if (activityPrize.getPrizeGoodsType() != PrizeGoodsTypeEnum.NO_PRIZE.getCode() && activityPrize.getTotalStock() != null) {
                //库存为0
                if (prizeStock == null || (Integer) prizeStock == 0) {
                    activityPrize = notActivityPrize;
                }
            }

            ActivityWinningRecord activityWinningRecord = new ActivityWinningRecord();
            activityWinningRecord.setActivityId(activityId);
            activityWinningRecord.setPrizeType(prizeType);
            activityWinningRecord.setUserId(appUserDTO.getUserid());
            activityWinningRecord.setHasWinning(0);
            boolean hasStock = activityPrize.getPrizeGoodsType() != PrizeGoodsTypeEnum.NO_PRIZE.getCode() && activityPrize.getTotalStock() != 0 && prizeStock != null && (Integer) prizeStock != 0;
            if (hasStock) {
                String key = String.format("%s%d:%d:%d", LOTTERY_PRE_KEY, activityId, prizeType, activityPrize.getSerialNumber());
                RLock rLock = redissonLock.getRLock(key);
                try {
                    boolean isSuccess = rLock.tryLock(1, 2, TimeUnit.SECONDS);
                    if (isSuccess) {
                        prizeStock = valueOperations.get(prizeStockKey);
                        log.info("tryLock success, key = [{}]", key);
                        try {
                            if (prizeStock != null && (Integer) prizeStock > 0) {
                                //更新库存
                                ((ActivityPrizeBiz) AopContext.currentProxy()).updatePrizeStock(prizeType, activityPrize.getSerialNumber());
                                //更新缓存库存
                                valueOperations.decrement(prizeStockKey);
                                //设置为已中奖
                                activityWinningRecord.setHasWinning(1);
                                activityWinningRecord.setIconPath(activityPrize.getIconPath());
                                activityWinningRecord.setLotteryTime(new Date());
                            } else {
                                activityPrize = notActivityPrize;
                            }
                        } catch (Exception ex) {
                            log.error("抽奖失败:【{}】", ex);
                            rLock.unlock();
                        } finally {
                            rLock.unlock();
                            log.info("releaseLock success, key = [{}]", key);
                        }
                    } else {
                        // 获取锁失败
                        log.info("tryLock fail, key = [{}]", key);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (PrizeTypeEnum.LOCALE.getCode() == prizeType) {
                    //兑奖码生成
                    String expireDateCodeKey = String.format("%s:%s%d",lotteryDate.substring(0,4),LOTTERY_PRE_KEY, activityId);
                    Long expireDateCodeCounter = valueOperations.increment(expireDateCodeKey);
                    if (expireDateCodeCounter == 1) {
                        redisTemplate.expire(expireDateCodeKey, expirDays, TimeUnit.DAYS);
                    }
                    String expiryDateCode = ReferralCodeUtil.encode(expireDateCodeCounter.intValue());
                    activityWinningRecord.setExpiryDateCode(expiryDateCode);
                    lotteryVo.setExpiryDateCode(expiryDateCode);
                }
                //发放优惠券
                if (activityPrize.getPrizeGoodsType() == PrizeGoodsTypeEnum.COUPON.getCode()) {
                    UserCouponSendDTO userCouponSendDTO = new UserCouponSendDTO();
                    userCouponSendDTO.setCouponId(activityPrize.getGoodsId());
                    userCouponSendDTO.setCouponNum(1);
                    userCouponSendDTO.setPhone(appUserDTO.getUsername());
                    userCouponBiz.sendCoupon(userCouponSendDTO);
                    activityWinningRecord.setGoodsId(activityPrize.getGoodsId());
                }
            }
            activityWinningRecord.setPrizeName(activityPrize.getName());
            activityWinningRecordBiz.saveRecord(activityWinningRecord);
            lotteryVo.setSerialNumber(activityPrize.getSerialNumber());
            //更改对应抽奖类型的抽奖次数
            activityAttendanceRecordBiz.updateLotteryNumByActivityIdAndUserIdAndPrizeType(activityId, appUserDTO.getUserid(), prizeType);
            valueOperations.decrement(lotteryNumKey);
        } else {
            lotteryVo.setMessage("抽奖次数已用完！！！");
        }
        return lotteryVo;
    }

    /**
     * 更新库存
     *
     * @param prizeType
     * @param serialNumber
     */
    @Async
    public void updatePrizeStock(Integer prizeType, Integer serialNumber) {
        mapper.updatePrizeStock(prizeType, serialNumber);
    }
}