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.config.rabbit.RabbitConstant;
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.ActivityLotteryDTO;
import com.xxfc.platform.activity.dto.CouponDTO;
import com.xxfc.platform.activity.entity.ActivityPrize;
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.amqp.rabbit.core.RabbitTemplate;
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.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 ActivityAttendanceRecordBiz activityAttendanceRecordBiz;
    private final CouponBiz couponBiz;
    private final RabbitTemplate rabbitTemplate;
    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:";
    public static final String USER_LOTTERY_PRE_KEY = "user: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("奖品不能为空");
        }
        List<Integer> ids = activityPrizes.stream().filter(x -> Objects.nonNull(x.getGoodsId())).map(ActivityPrize::getGoodsId).collect(Collectors.toList());
        List<CouponDTO> coupons = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(ids)){
            coupons = couponBiz.findCouponsByIds(ids);
        }
        Map<Integer, String> couponMap = coupons.stream().collect(Collectors.toMap(CouponDTO::getId, CouponDTO::getTitle));
        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());
               if (Objects.nonNull(x.getGoodsId())){
                   x.setName(couponMap==null?"":couponMap.get(x.getGoodsId()));
               }
            }).count();
            mapper.insertList(activityPrizesOfSave);
        }
        //更新
        List<ActivityPrize> activityPrizesOfUpdate = activityPrizeMap.get(Boolean.TRUE);
        if (CollectionUtils.isNotEmpty(activityPrizesOfUpdate)) {
            for (ActivityPrize activityPrize : activityPrizesOfUpdate) {
                activityPrize.setUpdTime(new Date());
                if (Objects.nonNull(activityPrize.getGoodsId())){
                    activityPrize.setName(couponMap==null?"":couponMap.get(activityPrize.getGoodsId()));
                }
               mapper.updateByPrimaryKey(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();
        ActivityLotteryDTO activityLotteryDTO = new ActivityLotteryDTO();
        activityLotteryDTO.setHasWinning(0);
        //查询奖品信息
        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("%s%d:%d:%d",USER_LOTTERY_PRE_KEY, 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;
                }
            }

            //判断是否有库存
            boolean hasStock = activityPrize.getPrizeGoodsType() != PrizeGoodsTypeEnum.NO_PRIZE.getCode()
                    && activityPrize.getTotalStock() != null
                    && activityPrize.getTotalStock()!=0
                    && prizeStock != null
                    && (Integer) prizeStock != 0;

            if (hasStock) {
                String lockKey = String.format("%s%d:%d:%d", LOTTERY_PRE_KEY, activityId, prizeType, activityPrize.getSerialNumber());
                RLock rLock = redissonLock.getRLock(lockKey);
                try {
                    boolean isSuccess = rLock.tryLock(1, 2, TimeUnit.SECONDS);
                    if (isSuccess) {
                        prizeStock = valueOperations.get(prizeStockKey);
                        log.info("tryLock success, key = [{}]", lockKey);
                        try {
                            if (prizeStock != null && (Integer) prizeStock > 0) {
                                //更新奖品的缓存库存
                                valueOperations.decrement(prizeStockKey);
                                //设置为已中奖
                                activityLotteryDTO.setHasWinning(1);
                                activityLotteryDTO.setIconPath(activityPrize.getIconPath());
                                activityLotteryDTO.setLotteryTime(new Date());
                            } else {
                                activityPrize = notActivityPrize;
                            }
                        } catch (Exception ex) {
                            log.error("抽奖失败:【{}】", ex);
                            rLock.unlock();
                        } finally {
                            rLock.unlock();
                            log.info("releaseLock success, key = [{}]", lockKey);
                        }
                    } else {
                        // 获取锁失败
                        log.info("tryLock fail, key = [{}]", lockKey);
                    }
                } catch (InterruptedException e) {
                    log.error("tryLock fail, key = [{}]", lockKey);
                }
                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());
                    activityLotteryDTO.setExpiryDateCode(expiryDateCode);
                    lotteryVo.setExpiryDateCode(expiryDateCode);
                }
            }
            activityLotteryDTO.setActivityId(activityId);
            activityLotteryDTO.setPrizeType(prizeType);
            activityLotteryDTO.setUserId(appUserDTO.getUserid());
            activityLotteryDTO.setPhone(appUserDTO.getUsername());
            activityLotteryDTO.setPrizeName(activityPrize.getName());
            Optional.ofNullable(activityPrize.getGoodsId()).ifPresent(goodsId->activityLotteryDTO.setGoodsId(goodsId));
            activityLotteryDTO.setSerialNumber(activityPrize.getSerialNumber());
            activityLotteryDTO.setPrizeGoodsType(activityPrize.getPrizeGoodsType());

            lotteryVo.setSerialNumber(activityPrize.getSerialNumber());
            //减少用户抽奖次数
            valueOperations.decrement(lotteryNumKey);
            //发送抽奖结果消息
            rabbitTemplate.convertAndSend(RabbitConstant.ACTIVITY_PRIZE_TOPIC,RabbitConstant.KEY_ACTIVITY_PRIZE_RECORD,activityLotteryDTO);
        } else {
            lotteryVo.setMessage("抽奖次数已用完！！！");
        }
        return lotteryVo;
    }

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