/**
 * Project Name:tuia-youtui-web<br>
 * File Name:CommonServiceImpl.java<br>
 * Package Name:com.duiba.tuia.youtui.web.service.impl<br>
 * Date:2017年5月3日下午3:01:14<br>
 * Copyright (c) 2017, duiba.com.cn All Rights Reserved.<br>
 */
package com.duiba.tuia.youtui.web.service.impl;

import cn.com.duiba.consumer.center.api.dto.ConsumerDto;
import cn.com.duiba.stock.service.api.constant.ConsumeStockTypes;
import cn.com.duiba.stock.service.api.remoteservice.RemoteStockService;
import cn.com.duiba.tuia.activity.center.api.common.PageDto;
import cn.com.duiba.tuia.activity.center.api.constant.ActivityOptionType;
import cn.com.duiba.tuia.activity.center.api.constant.AlipayOrderStatus;
import cn.com.duiba.tuia.activity.center.api.constant.AlipayType;
import cn.com.duiba.tuia.activity.center.api.constant.Scene;
import cn.com.duiba.tuia.activity.center.api.dto.*;
import cn.com.duiba.tuia.activity.center.api.remoteservice.RemoteActivityOrderService;
import cn.com.duiba.tuia.activity.center.api.remoteservice.RemoteSystemConfigService;
import cn.com.duiba.tuia.activity.center.api.util.MathUtil;
import cn.com.duiba.wolf.dubbo.DubboResult;
import cn.com.duiba.wolf.utils.DateUtils;
import cn.com.tuia.advert.model.ObtainAdvertRsp;
import cn.com.tuia.advert.model.QueryAdvertRsp;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dianping.cat.Cat;
import com.duiba.tuia.youtui.web.constant.*;
import com.duiba.tuia.youtui.web.embed.ThanksRecomendEmbed;
import com.duiba.tuia.youtui.web.exception.ActivityException;
import com.duiba.tuia.youtui.web.exception.ActivityRuntimeException;
import com.duiba.tuia.youtui.web.handle.FilterParams;
import com.duiba.tuia.youtui.web.handle.prize.PrizeFilterHandle;
import com.duiba.tuia.youtui.web.log.InnerLogService;
import com.duiba.tuia.youtui.web.log.StatCouponDownLog;
import com.duiba.tuia.youtui.web.model.AdBlockDto;
import com.duiba.tuia.youtui.web.model.RecordGeneralVO;
import com.duiba.tuia.youtui.web.model.UserAccount;
import com.duiba.tuia.youtui.web.model.req.DoJoinActivityReq;
import com.duiba.tuia.youtui.web.service.*;
import com.duiba.tuia.youtui.web.tool.*;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;


/**
 * ClassName: CommonServiceImpl <br/>
 * Function: 实现类. <br/>
 * Reason: . <br/>
 * date: 2017年5月3日 下午3:01:14 <br/>
 *
 * @author wubo
 * @since JDK 1.7
 */
@Service
public class  CommonServiceImpl extends BaseCacheService implements CommonService, InitializingBean {


    private String serverId;

    /**
     * The remote activity order service.
     */
    @Autowired
    private RemoteActivityOrderService remoteActivityOrderService;

    @Autowired
    private SkinService skinService;

    @Resource
    private ExecutorService executorService;        // 定义线程池，异步化参加活动流程

    @Autowired
    private PrizeService prizeService;

    @Autowired
    private ActivityEngineService activityEngineService;

    /**
     * The activity service.
     */
    @Autowired
    private ActivityService activityService;

    /**
     * The activity order service.
     */
    @Autowired
    private ActivityOrderService activityOrderService;

    /**
     * The remote stock service.
     */
    @Autowired
    private RemoteStockService remoteStockService;

    /**
     * The luck bag service.
     */
    @Autowired
    private LuckBagService luckBagService;

    @Autowired
    private StockService stockService;

    @Autowired
    private SystemConfigService systemConfigService;

    @Autowired
    private PrizeFilterHandle prizeFilterHandle;

    @Autowired
    private RedPacketService redPacketService;
    /** 直投页来源 */
    @Autowired
    private DirectPageSourceService directPageSourceService;

    private LoadingCache<String, AtomicLong> joinTimes;//用户参与时间



    private int n = 100;

    private synchronized int getNextInt() {
        if (n >= 100) {
            n = 10;
        }
        return n++;
    }

    @Override
    public void afterPropertiesSet() {
        setServerId();

        joinTimes = CacheBuilder
                .newBuilder().concurrencyLevel(20).refreshAfterWrite(1, TimeUnit.SECONDS)
                .build(new CacheLoader<String, AtomicLong>() {
                    @Override
                    public AtomicLong load(String s) {
                        return new AtomicLong(0);
                    }
                });

    }

    /**
     * 给机器设置一个id
     * 尽可能保证不同机器获取的是不同的值
     */
    private void setServerId() {

        try {
            int day = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
            long after = this.stringRedisTemplate.opsForValue().increment(CacheKey.SERVER_ID, 1);
            stringRedisTemplate.expire(CacheKey.SERVER_ID, 1L, TimeUnit.DAYS);
            serverId = Long.toString(71 + day * 29 + after);
            logger.error("为该服务器分配server id: {}", serverId);
        } catch (Exception e) {
            serverId = Integer.toString(100 + new Random().nextInt(100));
            logger.error("获取server id error,随机生成一个id:" + serverId, e);
        }

    }

    /**
     * 通过缓存查询系统配置
     */
    @Override
    public String getSystemConfig(String key) {

        return systemConfigService.getSystemConfig(key);
    }


    @Override
    public boolean checkEffective(int seconds, String biz, String... params) {
        boolean rs;
        if (StringUtils.isBlank(biz)) {
            throw new ActivityRuntimeException("biz is null");
        }
        // 1 生成key
        String key;
        try {
            key = generatorKey(biz, params);
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            logger.error("", e);
            throw new ActivityRuntimeException(e.getMessage());
        }

        // 2. redis 查询key是否存在
        String value = this.advancedCacheClient.get(key);
        if (value == null) {
            // 2.1 如果key没有，返回有效，并set key
            rs = true;
            this.advancedCacheClient.set(key, biz, seconds, TimeUnit.SECONDS);
        } else {
            // 2.2 如果有key，返回无效
            rs = false;
        }

        return rs;
    }

    private String generatorKey(String biz, String... params) throws NoSuchAlgorithmException,
            UnsupportedEncodingException {
        String key;
        StringBuilder sb = new StringBuilder();
        for (String temp : params) {
            sb.append(temp);
            sb.append("-");
        }
        key = MD5.md5(sb.toString());
        key = key + biz;
        return key;
    }


    /**
     * 参与次数校验.
     *
     * @param activityId 活动id
     * @param consumerId 用户id
     * @param limitCount 领取限制次数 =null时候不走参与次数校验
     * @param scene 应用场景
     * @throws ActivityException the activity exception
     */
    @Override
    public int checkConsumer(Long activityId, Long consumerId, Integer limitCount, Scene scene)
            throws ActivityException {

        UserAccount account = RequestLocal.get().getUserAccount();
        // 预览用户无法参与抽奖
        if (null == account || ConsumerDto.PREVIEWUSERID.equals(account.getPartnerUserId())) {
            throw new ActivityException(ErrorCode.E0200004.geteCode(), ErrorCode.E0200004.geteDesc());
        }

        // 限制用户1秒内频繁下单
        if (!canPass(consumerId, scene)) {
            throw new ActivityException(ErrorCode.E0120005.geteCode(), ErrorCode.E0120005.geteDesc());
        }

        int joinTimes = this.activityService.updateConsumerActivityDojoinCache(activityId, consumerId);
        // 用户参与次数验证
        if (null != limitCount) {


            if (limitCount.intValue() < joinTimes) {
                logger.info("the joinTimes=[{}] >= limitCount=[{}], the consumerId=[{}], the activityId =[{}]", joinTimes,
                        limitCount, consumerId, activityId);
                throw new ActivityException(ErrorCode.E0120005.geteCode(), ErrorCode.E0120005.geteDesc());
            }
        }
        return joinTimes;
    }

    /**
     * 用户频繁兑换限制.
     *
     * @param consumerId 用户ID
     * @param s          秒
     * @return true, if can pass
     */
    private boolean canPass(Long consumerId, int s) {
        if (Environment.isDaily()) {
            return true;
        }
        String key = CachedKeyUtils.getRedisKey(CacheKey.FREQU_DOJOIN_KEY, consumerId);

        Object obj = advancedCacheClient.get(key);
        if (null == obj) {
            advancedCacheClient.set(key, new Date().getTime(), 2, TimeUnit.SECONDS);
        } else {
            Long time = Long.valueOf(obj.toString());
            if (time > (new Date().getTime() - s * 1000L)) {
                return false;
            }
        }
        return true;
    }

    private boolean canPass(Long consumerId, Scene scene) {
        if (Environment.isDaily()) {
            return true;
        }
        String key = consumerId + "_" + scene.getSceneId();
        try {
            return this.joinTimes.get(key).getAndIncrement() == 0L;
        } catch (Exception e) {
            logger.warn("canPass error", e);
        }

        return true;
    }


    /**
     * 构建抽奖结果.
     *
     * @return the JSON object
     * @throws ActivityException the activity exception
     */
    @Override
    public JSONObject queryJoinResultRsp(HttpServletRequest request, String orderId, Long appId, Long consumerId,
                                         Boolean showVersion) throws ActivityException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("status", "success");
        return jsonObject;
    }

    @Override
    public String getNextOrderId(String biz) {
        String bizStr = biz == null ? "" : biz;
        StringBuilder sb = new StringBuilder(bizStr);
        sb.append(System.currentTimeMillis()).append(serverId).append(getNextInt());
        return sb.toString();
    }

    @Override
    public Map<String, Object> getThankRecommend(Long consumerId, HttpServletRequest request) throws ActivityException {
        // 1.根据concumerId分页查询订单列表（只包括中奖类型为福袋和优惠券)  默认查询100条
        DubboResult<PageDto<ActivityOrderDto>> result = remoteActivityOrderService.selectByConsumerId(consumerId, 0, 200);

        // 2.循环解析订单实体使用couponData中的fee进行排序
        if (!result.isSuccess()) {
            logger.warn("getThankRecommend get order from tuia-activity-center fail.");
            throw new ActivityException(ErrorCode.E0400008.geteCode(), "获取兑换记录列表失败");
        }
        // 2.1 解析订单集合
        List<ActivityOrderDto> orderList = result.getResult().getList();
        List<RecordGeneralVO> recordVOList = new ArrayList<>();
        for (ActivityOrderDto orderDto : orderList) {
            RecordGeneralVO rc = getCouponData(orderDto, request);
            if (rc.getEndValid() != null && DateUtils.getDayDate(rc.getEndValid()).before(DateUtils.getDayDate(new Date()))) {
                continue;
            }
            recordVOList.add(rc);
        }

        //  2.2对广告按费用进行排序
        Collections.sort(recordVOList, (Comparator) (a, b) -> {
            Integer one = ((RecordGeneralVO) a).getFee();
            Integer two = ((RecordGeneralVO) b).getFee();
            return two.compareTo(one);
        });


        // 3.构建返回的推荐广告区块1，2
        List<RecordGeneralVO> resultRecordVOList = new ArrayList<>();
        for (RecordGeneralVO recordGeneralVO : recordVOList) {

            //判断是否展示过
            Set<String> adIds = getAdIdsForThanks(consumerId);
            if (CollectionUtils.isNotEmpty(adIds) && adIds.contains(String.valueOf(recordGeneralVO.getAdvertId()))) {
                continue;
            }

            resultRecordVOList.add(recordGeneralVO);
            if (resultRecordVOList.size() == 2) break;

        }


        return buildLottery(resultRecordVOList, consumerId, request);
    }

    /**
     * 构件lottery返回数据
     *
     * @param resultRecordVOList resultRecordVOList
     * @param consumerId         consumerId
     * @param request            request
     * @throws ActivityException ActivityException
     */
    private Map<String, Object> buildLottery(List<RecordGeneralVO> resultRecordVOList, Long consumerId, HttpServletRequest request) throws ActivityException {
        Map<String, Object> lottery = Maps.newHashMap();
        // 查询谢谢参与的弹层ID，用于数据埋点
        Long layerId;
        String thankLayer = request.getParameter("thankLayerId");
        layerId = StringUtils.isBlank(thankLayer) ? this.skinService.selectThanksLayerId() : Long.valueOf(thankLayer);

        Long directpage = this.directPageSourceService.get(consumerId);
        if (!resultRecordVOList.isEmpty()) {
            // 多次查询广告券详情，1:广告引擎暂不提供批量查询，2:修改订单表做冗余，会造成锁表，一段时间表不可用
            RecordGeneralVO recordAd1 = resultRecordVOList.get(0);
            QueryAdvertRsp queryAdvertRsp = luckBagService.queryAdvert(consumerId, recordAd1.getOrderId(), 1, 0L);
            if (null != queryAdvertRsp) {
                AdBlockDto adBlockDto = buildAdBlockDto(request, queryAdvertRsp, recordAd1, 1, layerId,directpage);
                lottery.put("block1", adBlockDto);
            }

            if (1 < resultRecordVOList.size()) {
                RecordGeneralVO recordAd2 = resultRecordVOList.get(1);
                QueryAdvertRsp queryAdvertRsp2 = luckBagService.queryAdvert(consumerId, recordAd2.getOrderId(), 1, 0L);
                if (null != queryAdvertRsp2) {
                    AdBlockDto adBlockDto = buildAdBlockDto(request, queryAdvertRsp2, recordAd2, 2, layerId,directpage);
                    lottery.put("block2", adBlockDto);
                }
            }
        }

        AdBlockDto adBlockDto = new AdBlockDto();
        //我的奖品dpm
        String thinksListDpm = DpmInfo.getDpmInfo(RequestLocal.get().getAppId(), Constants.DPM.DPM_B_53, Constants.DPM.DPM_C_3,
                Constants.DPM.DPM_D_1);
        //兑换记录列表dcm
        String recordlistDcm = DpmInfo.dcmClickInfo(Constants.DCM.DCM_A_1010, 0L, String.valueOf(layerId), 0L);
        //兑换记录列表
        String thinksDcm = DpmInfo.dcmClickInfo(Constants.DCM.DCM_A_1013, 0L, String.valueOf(layerId), 0L);
        //谢谢参与关闭按钮dpm
        String thinksCloseDpm = DpmInfo.getDpmInfo(RequestLocal.get().getAppId(), Constants.DPM.DPM_B_53, Constants.DPM.DPM_C_1,
                Constants.DPM.DPM_D_0);
        //整个谢谢参与dpm
        String thinksDpm = DpmInfo.getDpmInfo(RequestLocal.get().getAppId(), Constants.DPM.DPM_B_53, Constants.DPM.DPM_C_0,
                Constants.DPM.DPM_D_0);
        ThanksRecomendEmbed thanksRecomendEmbed = ThanksRecomendEmbed.ThanksRecomendEmbedBuilder.newThanksRecomendEmbed()
                .withDcm(thinksDcm).withDpm(thinksDpm).withConsumerId(consumerId).withAppId(RequestLocal.get().getAppId())
                .withHost(request.getHeader("host"))
                .withDirectpage(directpage)
                .build();
        // 兑换记录列表页链接
        adBlockDto.setActUrl("//" + request.getHeader("host") + "/activity/indexRecord?vmName=list");
        adBlockDto.setClick(DpmInfo.setClickForThaks("//" + request.getHeader("host"), thinksListDpm, recordlistDcm));
        lottery.put("block3", adBlockDto);
        lottery.put("stRecommendInfo", thanksRecomendEmbed.btnExposureInfo());
        lottery.put("closeClick", DpmInfo.setClickForThaks("//" + request.getHeader("host"), thinksCloseDpm, thinksDcm));
        lottery.put("imgurl", null);

        return lottery;
    }


    /**
     * Gets the coupon data.
     *
     * @param order the order
     * @return the coupon data
     */
    private RecordGeneralVO getCouponData(ActivityOrderDto order, HttpServletRequest request) {
        RecordGeneralVO record = new RecordGeneralVO();
        record.setOrderId(order.getOrderId());
        record.setActivityId(order.getActivityId());
        record.setAdvertId(order.getAdvertId());
        JSONObject jsonObject = JSON.parseObject(order.getCouponData());
        if (null != jsonObject) {
            record.setTitle(jsonObject.getString(Constants.VALUENAME.TITLE));
            record.setThumbnailPng(jsonObject.getString(Constants.VALUENAME.THUMBNAIL_PNG));
            record.setEndValid(jsonObject.getString(Constants.VALUENAME.END_VALID));
            record.setFee(jsonObject.containsKey(Constants.VALUENAME.ADVERT_FEE) ? jsonObject.getInteger(Constants.VALUENAME.ADVERT_FEE) : 0);
            record.setRemainDay(DateUtils.daysBetween(new Date(), new DateTime(record.getEndValid()).toDate()));
        }
        record.replaceDomain(DomainUtil.getCurrentYunDomain(request));
        return record;
    }

    /**
     * 获取谢谢参与展示过的广告ID
     *
     * @param consumerId consumerId
     */
    private Set<String> getAdIdsForThanks(Long consumerId) {
        String key = CachedKeyUtils.getRedisKey(CacheKey.THANKS_SHOW_AD_IDS, consumerId);

        Set<String> advertIds = stringRedisTemplate.opsForSet().members(key);
        if (CollectionUtils.isEmpty(advertIds)) {
            return Collections.emptySet();
        }

        return advertIds;
    }

    /**
     * 增加用户在谢谢参与中已曝光的广告ID
     *
     * @param consumerId 用户id
     * @param advertId   广告id
     * @return boolean
     */
    public boolean addAdIdsForThanks(Long consumerId, Long advertId) {
        String key = CachedKeyUtils.getRedisKey(CacheKey.THANKS_SHOW_AD_IDS, consumerId);
        stringRedisTemplate.opsForSet().add(key, String.valueOf(advertId));
        stringRedisTemplate.expire(key, getRestSecondsToday(), TimeUnit.SECONDS);
        return true;
    }

    /**
     * Builds the button url.
     */
    private AdBlockDto buildAdBlockDto(HttpServletRequest request, QueryAdvertRsp queryAdvertRsp,
                                       RecordGeneralVO recordGeneralVO, int dpmD, Long layerId, Long directpage)
            throws ActivityException {
        Long appId = RequestLocal.get().getAppId();
        Long consumerId = RequestLocal.get().getCid();
        String promoteUrl = EmbedUtil.getEncryptUrl(queryAdvertRsp.getPromoteUrl(), consumerId);
        String orderId = recordGeneralVO.getOrderId();
        //构建埋点
        String dpm = DpmInfo.getDpmInfo(appId, Constants.DPM.DPM_B_53, Constants.DPM.DPM_C_2,
                dpmD);
        String dcm = DpmInfo.getThanksDcmInfo(queryAdvertRsp.getAdvertId(), queryAdvertRsp.getMaterialId(), layerId);
        ThanksRecomendEmbed thanksRecomendEmbed = ThanksRecomendEmbed.ThanksRecomendEmbedBuilder.newThanksRecomendEmbed()
                .withDcm(dcm).withDpm(dpm).withConsumerId(consumerId).withAppId(appId)
                .withHost(request.getHeader("host")).withOrderId(Constants.THE_PREFIX_ORDERID + recordGeneralVO.getOrderId())
                .withMaterialId(queryAdvertRsp.getMaterialId()).withActivityId(recordGeneralVO.getActivityId())
                .withCouponSource(CouponSource.ACTIVITY.getCode()).withDirectpage(directpage).build();
        // 5. 跳转url组装并urlEncode
        try {
            promoteUrl = URLEncoder.encode(promoteUrl, "utf-8");
        } catch (UnsupportedEncodingException e) {

            throw new ActivityException(e);

        }

        // 6.需要隐藏特殊网址
        Boolean showUse = true;

        // 6.1如果推广网址不为空或为微信券， 则不展示
        if (((queryAdvertRsp.getCouponType() == 2 || queryAdvertRsp.getCouponType() == 3) && queryAdvertRsp.getIsWeixin() == 1)
                || StringUtils.isBlank(promoteUrl)) {
            showUse = false;
        }


        AdBlockDto adBlockDto = new AdBlockDto();
        if (showUse) {
            // 7.跳转web中间页
            promoteUrl = PathUtil.getButtonUrl("//" + request.getHeader("host"), promoteUrl, orderId, dcm, dpm, recordGeneralVO.getActivityId(), CouponSource.ACTIVITY.getCode(), null);
            adBlockDto.setIosDownloadUrl(promoteUrl);
            adBlockDto.setAndroidDownloadUrl(promoteUrl);
        }
        adBlockDto.setBannerUrl(queryAdvertRsp.getBannerPngUrl());
        adBlockDto.setTitle(queryAdvertRsp.getTitle());
        adBlockDto.setTitle(queryAdvertRsp.getTitle());
        adBlockDto.setClick(thanksRecomendEmbed.btnClickInfo());
        adBlockDto.setExposure(thanksRecomendEmbed.advertExposureInfo());
        adBlockDto.setAdvertId(queryAdvertRsp.getAdvertId());

        //更新展示过的广告Id
        addAdIdsForThanks(consumerId, recordGeneralVO.getAdvertId());

        return adBlockDto;
    }

    @Override
    public void doJoin(RspOrder rspOrder, DoJoinActivityReq doJoinActivityReq, List<ActivityOptionDto> options) {
        executorService.submit(new DoWinPrizeTask(rspOrder, doJoinActivityReq, options));
    }

    /**
     * ClassName: doWinPrizeTask <br/>
     * Function: 中奖运行任务. <br/>
     * date: 2017年3月10日 下午5:29:00 <br/>
     *
     * @author cdm
     * @version JoinActivityBOImpl
     * @since JDK 1.7
     */
    class DoWinPrizeTask implements Runnable {

        private RspOrder rspOrder;

        private DoJoinActivityReq doJoinActivityReq;

        private List<ActivityOptionDto> options;

        public DoWinPrizeTask(RspOrder rspOrder, DoJoinActivityReq doJoinActivityReq, List<ActivityOptionDto> options) {
            this.rspOrder = rspOrder;
            this.doJoinActivityReq = doJoinActivityReq;
            this.options = options;
        }

        @Override
        public void run() {
            try {
                doRunWinPrize(rspOrder, doJoinActivityReq, options);
            } catch (ActivityException e) {
                logger.error(e.getMessage(), e);
            }
        }

        /**
         * 具体的抽奖逻辑.
         *
         * @param rspOrder          the rsp order
         * @param doJoinActivityReq the do join activity req
         * @param options           the options
         * @return the obtain advert rsp
         * @throws ActivityException the activity exception
         * @author zp
         * @since JDK 1.6
         */
        private void doRunWinPrize(RspOrder rspOrder, DoJoinActivityReq doJoinActivityReq, List<ActivityOptionDto> options)
                throws ActivityException {
            // 1.订单空指针验证
            if (null == rspOrder) {
                throw new ActivityException(ErrorCode.E0120008.geteCode(), ErrorCode.E0120008.geteDesc());
            }

            // 2.概率抽奖,随机选取一个奖项
            ActivityOptionDto finalOption = getRandomOption(doJoinActivityReq.getActivityId(), options);
            // 2.1奖项验证
            boolean optionCheck = optionCheck(finalOption);
            if (!optionCheck) {
                // 2.1降级中福袋
                finalOption = doGetOptionLuck(options);
            }
            try {
                // 3.开奖
                doOpenWinPrize(finalOption, doJoinActivityReq, rspOrder, options);
            } catch (Exception e) {
                logger.warn("开奖异常,", e);
                // 4.降级走中福袋流程
                try {
                    //如果 本身中福袋的话降级谢谢参与，避免重复请求福袋，else 降级福袋
                    if (ActivityOptionType.TYPE_LUCKY.getCode().equals(finalOption.getPrizeType())) {
                        finalOption = doGetOptionThanks(options);
                        doOpenWinPrize(finalOption, doJoinActivityReq, rspOrder, options);
                    } else {
                        finalOption = doGetOptionLuck(options);
                        doOpenWinPrize(finalOption, doJoinActivityReq, rspOrder, options);
                    }
                } catch (Exception exc) {
                    logger.warn("开奖降级中福袋仍然异常,", exc);
                    throw new ActivityException(ErrorCode.E0120003.geteCode(), ErrorCode.E0120003.geteDesc());
                }
            }
        }

        /**
         * 统一一下，filter 被过滤，返回true，没被过滤返回false
         * <p>
         * 有的返回true，有的返回false 贼恐怖
         */
        private boolean filterOption(ActivityOptionDto finalOption, int amount, DoJoinActivityReq doJoinActivityReq, String orderId) {
            Long appId = doJoinActivityReq.getAppId();
            String deviceId = doJoinActivityReq.getDeviceId();
            Integer activityType = doJoinActivityReq.getActivityType();
            String userId = doJoinActivityReq.getUserId();

            //实发奖品做真实概率限制
            String prizeType = finalOption.getPrizeType();

            if (ActivityType.isActivity(activityType) && filterAppDirect(finalOption, appId, prizeType)) return true;

            if (prizeFilterHandle.doFilter(FilterParams.adapter(finalOption, doJoinActivityReq, amount, orderId))) {
                return true;
            }

            // 2.过滤支付宝红包奖品
            if (ActivityOptionType.TYPE_ALIPAY.getCode().equals(prizeType) && filterAlipay(finalOption, amount, deviceId, appId, doJoinActivityReq.getIp()))
                return true;
            // 3.过滤虚拟物品奖项
            return (ActivityOptionType.TYPE_VIRTUAL.equalsCode(prizeType) && filterVirtual(finalOption, deviceId, userId, appId, doJoinActivityReq.getIp()));


        }

        /**
         * 定向媒体一致校验
         * 特殊媒体校验
         * 校验不通过返回true
         */
        private boolean filterAppDirect(ActivityOptionDto finalOption, Long appId, String prizeType) {
            if (ActivityOptionType.TYPE_PHYSICAL.equalsCode(prizeType) || ActivityOptionType.TYPE_ALIPAY.equalsCode(prizeType) || ActivityOptionType.TYPE_QB.equalsCode(prizeType) || ActivityOptionType.TYPE_BILL.equalsCode(prizeType)) {
                //
                //中奖用户定向媒体强一致校验
                List<Long> directedAppByAct = activityEngineService.getDirectedAppByAct(finalOption.getActivityId());
                List<Long> speApp = activityEngineService.getSpeApp();
                return (CollectionUtils.isEmpty(directedAppByAct) || !directedAppByAct.contains(appId)) && (CollectionUtils.isEmpty(speApp) || !speApp.contains(appId));
            }
            return false;
        }


        /*
         * 虚拟奖品过滤逻辑
         */
        private boolean filterVirtual(ActivityOptionDto finalOption, String deviceId, String userId, Long appId, String ip) {

            String user = deviceId;
            //逻辑：如果api类型，如果媒体有传userId,则依据媒体的userId做用户限制
            if (AlipayType.isVirtualApi(finalOption.getAlipayType()) && StringUtils.isNotBlank(userId)) {
                user = userId + "-" + appId;
            }

            return filterAwardLimit(finalOption, user, ip);
        }

        /*
         * 中奖限制过滤逻辑
         * 被过滤返回true
         * @param finalOption 奖项
         * @param deviceId 设备id
         * @return true/false
         */
        private boolean filterAwardLimit(ActivityOptionDto finalOption, String deviceId, String ip) {
            Long optionId = finalOption.getId();
            Integer awardLimit = finalOption.getAwardLimit();
            if (awardLimit == null) {
                return false;
            }
            // 2.1 用户中奖限制
            switch (awardLimit) {
                case 0:
                    break;
                case 1: {
                    if (activityService.isHaveWonPrizeDay(optionId, deviceId, ip)) {
                        return true;
                    }
                    break;
                }
                case 2: {
                    if (activityService.isHaveWonPrizeWeek(optionId, deviceId, ip)) {
                        return true;
                    }
                    break;
                }
                default:
                    return false;
            }
            return false;
        }

        private boolean filterAlipay(ActivityOptionDto finalOption, int amount, String deviceId, Long appId, String ip) {
            Long optionId = finalOption.getId();

            // 2.2 媒体上一时段保底收益限制
            int appEarn = activityEngineService.getAppEarn(appId);
            if (finalOption.getGuaranteedCount() != null && finalOption.getGuaranteedCount() > appEarn) {
                return true;
            }

            // 2.2 校验每日预算
            if (finalOption.getDailyBudget() != null && finalOption.getDailyBudget() < activityService.incrAlipayDailyBudget(optionId, amount)) {
                activityService.incrAlipayDailyBudget(optionId, -amount);
                return true;
            }
            return false;
        }

        /*
         *用户每日中奖限制
         * consumerId + ip
         */
        private boolean filterUserTodayLimit(ActivityOptionDto finalOption, String deviceId, String ip) {
            Long optionId = finalOption.getId();

            // 1.3校验同一用户当日抽奖限制，如果该用户已经中过该奖项，不抽取该奖品
            return activityService.isHaveWonPrizeDay(optionId, deviceId, ip);
        }

        /**
         * 得到一个随机奖项.
         *
         * @param activityId      the activity id
         * @param activityOptions the activity options
         * @return the random option
         * @throws ActivityException the activity exception
         */
        private ActivityOptionDto getRandomOption(Long activityId, List<ActivityOptionDto> activityOptions) {
            // 1.如果活动奖项列表为空， 则返回一个谢谢参与
            if (CollectionUtils.isEmpty(activityOptions)) {
                logger.warn("the options is empty of activityId=[{}]", activityId);
                return getThanksOption();
            }
            // 2.初始化参数
            // 奖品总概率
            double totalRate = 0.0;
            // 能够中奖的奖品列表
            List<ActivityOptionDto> canWinOptions = new ArrayList<>();
            // 是否配置了福袋标志位
            boolean isHaveFudai = false;

            // 3.求出所有奖项中奖总概率
            for (ActivityOptionDto activityOption : activityOptions) {

                String prizeType = activityOption.getPrizeType();
                if (isStockOption(activityOption)) {
                    try {
                        // 3.1 求出所有奖项中奖总概率
                        totalRate += Double.valueOf(activityOption.getRate());
                        // 3.2 汇总所有奖项奖品列表
                        canWinOptions.add(activityOption);
                    } catch (NumberFormatException e) {
                        logger.warn("the activityOption=[{}] probability=[{}] configuration error,", activityId,
                                activityOption.getRate(), e);
                    }
                }

                // 3.2如果配置了福袋， 则把标志位true
                if (!isHaveFudai && StringUtils.equals(prizeType, ActivityOptionType.TYPE_LUCKY.getCode())) {
                    isHaveFudai = true;
                }
            }

            // 4.校验所有奖项中奖总概率
            if (totalRate > 100.001) {
                // 如果超过100%，返回福袋并记录日志
                logger.warn("Total activity=[{}] options probability=[{}] more than 100%", activityId);
                return doGetOptionLuck(activityOptions);
            }

            // 5.如果配置了福袋， 则求出福袋的中奖概率
            if (isHaveFudai && totalRate < 100) {
                canWinOptions.add(getLuckOption(100.0 - totalRate));
            }

            // 6.返回最终出奖的奖项
            return getFianllyOption(canWinOptions, activityOptions);
        }

        /**
         * 得到一个最终出奖的奖项.
         *
         * @param canWinOptions   the can win options
         * @param activityOptions the activity options
         * @return the fianlly option
         */
        private ActivityOptionDto getFianllyOption(List<ActivityOptionDto> canWinOptions,
                                                   List<ActivityOptionDto> activityOptions) {
            // 1.随机排序奖项List
            if (canWinOptions.size() > 1) {
                Collections.shuffle(canWinOptions);
            }

            // 2.中奖的随机数(0.0000000000000000 到100.0000000000000000)之间的随机数
            double randomNumber = Math.random() * 100;

            // 4.初始化福袋中奖奖项
            ActivityOptionDto winOption = getLuckOption(0);
            double temp = 0.0;
            // 5.循环遍历得到最终出奖的奖项
            for (ActivityOptionDto option : canWinOptions) {
                temp += Double.valueOf(option.getRate());
                if (randomNumber <= temp) {
                    winOption = option;
                    break;
                }
            }

            // 6.构造奖项返回
            if (StringUtils.equals(ActivityOptionType.TYPE_LUCKY.getCode(), winOption.getPrizeType())) {
                // 6.1如果为福袋集合
                return doGetOptionLuck(activityOptions);
            } else {
                // 6.2 如果不为福袋， 则直接返回该奖项
                return winOption;
            }
        }

        /**
         * 后置抽奖验证，没配福袋，依然让它中福袋 doGetOptionLuck:(这里用一句话描述这个方法的作用). <br/>
         *
         * @param options
         * @return
         * @author zp
         * @since JDK 1.6
         */
        private ActivityOptionDto doGetOptionLuck(List<ActivityOptionDto> options) {
            // 赛选出福袋奖项集合
            List<ActivityOptionDto> lucks = new ArrayList<>();
            for (ActivityOptionDto option : options) {
                if (ActivityOptionType.TYPE_LUCKY.getCode().equals(option.getPrizeType())) {
                    lucks.add(option);
                }
            }
            if (lucks.isEmpty()) {
                // 若没有配置福袋奖项，则中谢谢参与
                return doGetOptionThanks(options);
            } else {
                // 随机取一个福袋
                Random random = new Random();
                int thanksIndex = Math.abs(random.nextInt(lucks.size())) % lucks.size();
                return lucks.get(thanksIndex);
            }
        }

        /**
         * 获取谢谢参与.
         *
         * @param options the options
         * @return the hdtool option thanks
         * @since JDK 1.6
         */
        private ActivityOptionDto doGetOptionThanks(List<ActivityOptionDto> options) {
            // 赛选出谢谢参与奖项集合
            List<ActivityOptionDto> thanks = new ArrayList<>();
            for (ActivityOptionDto option : options) {
                if (ActivityOptionType.TYPE_THANKS.getCode().equals(option.getPrizeType())) {
                    thanks.add(option);
                }
            }
            if (thanks.isEmpty()) {
                // 活动没有配置谢谢参与时，抽中的奖项达到限制条件，返回一个谢谢参与（未中奖）
                return getThanksOption();
            } else {
                // 随机取一个谢谢参与
                Random random = new Random();
                int thanksIndex = Math.abs(random.nextInt(thanks.size())) % thanks.size();
                return thanks.get(thanksIndex);
            }
        }

        /**
         * 获取谢谢参与.
         *
         * @return the thanks option
         */
        private ActivityOptionDto getThanksOption() {
            ActivityOptionDto thanksOption = new ActivityOptionDto();
            thanksOption.setPrizeType(ActivityOptionType.TYPE_THANKS.getCode());
            thanksOption.setTitle("谢谢参与");
            return thanksOption;
        }

        /**
         * 获取福袋类型.
         *
         * @param rate the rate
         * @return the luck option
         */
        private ActivityOptionDto getLuckOption(double rate) {
            ActivityOptionDto luckOption = new ActivityOptionDto();
            luckOption.setPrizeType(ActivityOptionType.TYPE_LUCKY.getCode());
            luckOption.setRate(String.valueOf(rate));
            return luckOption;
        }

        /**
         * 随机取一个福袋 前置抽奖验证
         *
         * @param options the options
         * @return the activity option dto
         */
        private ActivityOptionDto doGetOptionLuckForever(List<ActivityOptionDto> options) {
            // 赛选出福袋奖项集合
            List<ActivityOptionDto> lucks = new ArrayList<>();
            for (ActivityOptionDto option : options) {
                if (ActivityOptionType.TYPE_LUCKY.getCode().equals(option.getPrizeType())) {
                    lucks.add(option);
                }
            }
            if (lucks.isEmpty()) {
                // 若没有配置福袋奖项，则生成一个福袋
                return getLuckOption(0.0);
            } else {
                // 随机取一个福袋
                Random random = new Random();
                int thanksIndex = Math.abs(random.nextInt(lucks.size())) % lucks.size();
                return lucks.get(thanksIndex);
            }
        }

        /**
         * 开奖逻辑（请求福袋活动奖品库，更新订单，记录日志等）.
         *
         * @param option            the option
         * @param doJoinActivityReq the do join activity req
         * @param rspOrder          the rsp order
         * @param options           the options
         * @throws ActivityException the activity exception
         * @author zp
         * @since JDK 1.6
         */
        private void doOpenWinPrize(ActivityOptionDto option, DoJoinActivityReq doJoinActivityReq, RspOrder
                rspOrder,
                                    List<ActivityOptionDto> options) throws ActivityException {
            String orderId = rspOrder.getOrderId();
            Long consumerId = doJoinActivityReq.getConsumerId();

            String prizeType = option.getPrizeType();

            // 1.概率命中实物奖项
            if (ActivityOptionType.TYPE_PHYSICAL.equalsCode(prizeType) && openPhysical(option, doJoinActivityReq, orderId, consumerId))
                return;

            // 2.概率命中支付宝红包
            if (ActivityOptionType.TYPE_ALIPAY.equalsCode(prizeType) && openAlipay(option, doJoinActivityReq, orderId, consumerId))
                return;

            // 3.虚拟奖品
            if (ActivityOptionType.TYPE_VIRTUAL.equalsCode(prizeType) && openVirtual(option, doJoinActivityReq, orderId, consumerId))
                return;

            // 4.概率命中腾讯qb
            if (ActivityOptionType.TYPE_QB.getCode().equals(prizeType) && openQB(option, doJoinActivityReq, orderId, consumerId))
                return;

            // 5.话费
            if (ActivityOptionType.TYPE_BILL.equalsCode(prizeType) && openBill(option, doJoinActivityReq, orderId, consumerId))
                return;
            // 5.概率命中谢谢参与
            if (ActivityOptionType.TYPE_THANKS.equalsCode(prizeType)) {
                try {
                    Cat.logMetricForCount("活动中心-中谢谢参与");
                } catch (Exception e) {
                    logger.error(Constants.CAT_ERROR, e);
                }
                // 1. 更新订单
                activityOrderService.updateOrderOfOptionThanks(orderId, consumerId, option);
                // 2. 生成订单并记录日志
                InnerLogService.log(doJoinActivityReq.getActivityId(), doJoinActivityReq.getAppId(),
                        doJoinActivityReq.getActivityType().toString(), orderId,
                        ActivityErrorCodeEnum.E0100002.getErrorCode(), ActivityErrorCodeEnum.E0100002.getDesc());
            }
            openLuckBag(option, doJoinActivityReq, rspOrder, options, orderId, consumerId);

        }

        private void openLuckBag(ActivityOptionDto option, DoJoinActivityReq doJoinActivityReq, RspOrder
                rspOrder, List<ActivityOptionDto> options, String orderId, Long consumerId) throws ActivityException {
            // 4.请求福袋接口
            ObtainAdvertRsp advert = doWinLuckBag(doJoinActivityReq, rspOrder, option, options);
            // 请求福袋后打日志
            StatCouponDownLog.log(option, doJoinActivityReq, rspOrder, advert);
            // 中优惠券处理
            if (ActivityOptionType.TYPE_COUPON.getCode().equals(option.getPrizeType())
                    && (null != advert && advert.getAdvertId() == option.getAdvertId())) {
                try {
                    Cat.logMetricForCount("活动中心-中优惠券");
                } catch (Exception e) {
                    logger.error(Constants.CAT_ERROR, e);
                }
                // 扣库存
                boolean subStock = subStock(option, orderId, 0);
                if (subStock) {
                    try {
                        // 如果扣减库存成功， 则更新订单状态
                        activityOrderService.updateOrder(orderId, consumerId, option,
                                buildCouponData(advert));
                    } catch (Exception e) {
                        logger.warn("优惠券更新订单失败,", e);
                        // 更新失败，返库存
                        paybackStock(option, orderId);
                    }
                }
            }
        }

        /**
         * 执行福袋抽奖操作.
         *
         * @param doJoinActivityReq the do join activity req
         * @param rspOrder          the rsp order
         * @param finalOption       the final option
         * @param options           the options
         * @return the long
         * @throws ActivityException the activity exception
         */
        private ObtainAdvertRsp doWinLuckBag(DoJoinActivityReq doJoinActivityReq, RspOrder rspOrder,
                                             ActivityOptionDto finalOption, List<ActivityOptionDto> options)
                throws ActivityException {
            Long activityId = doJoinActivityReq.getActivityId();
            ObtainAdvertRsp advert = luckBagService.obtainAdvert(doJoinActivityReq,
                    activityService.getActivityTag(activityId, doJoinActivityReq.getActivityType()),
                    Constants.THE_PREFIX_ORDERID + rspOrder.getOrderId(),
                    ActivityOptionType.TYPE_COUPON.getCode().equals(finalOption.getPrizeType()) ? finalOption.getAdvertId() : null);

            if (advert == null || advert.getAdvertId() == 0) {
                try {
                    Cat.logMetricForCount("活动中心-中谢谢参与");
                } catch (Exception e) {
                    logger.error(Constants.CAT_ERROR, e);
                }
                // 降级处理， 返回谢谢参与
                updateOrderOfOptionThanks(rspOrder, doJoinActivityReq, doGetOptionThanks(options),
                        ActivityErrorCodeEnum.E0100022.getErrorCode(),
                        ActivityErrorCodeEnum.E0100022.getDesc());
            } else {
                try {
                    Cat.logMetricForCount("活动中心-中福袋");
                } catch (Exception e) {
                    logger.error(Constants.CAT_ERROR, e);
                }
                finalOption = doGetOptionLuckForever(options);
                // 保存福袋接口返回的广告券信息
                finalOption.setAdvertId(advert.getAdvertId());

                activityOrderService.updateOrder(rspOrder.getOrderId(), doJoinActivityReq.getConsumerId(), finalOption,
                        buildCouponData(advert));
            }
            return advert;
        }

        /**
         * 更新谢谢参与的订单.
         *
         * @param doJoinActivityReq the do join activity req
         * @param option            the option
         * @param errorCode         the error code
         * @throws ActivityException the activity exception
         */
        private void updateOrderOfOptionThanks(RspOrder rspOrder, DoJoinActivityReq doJoinActivityReq,
                                               ActivityOptionDto option, String errorCode, String msg)
                throws ActivityException {
            // 1. 更新订单
            activityOrderService.updateOrderOfOptionThanks(rspOrder.getOrderId(), doJoinActivityReq.getConsumerId(), option);

            // 2. 生成订单并记录日志
            InnerLogService.log(doJoinActivityReq.getActivityId(), doJoinActivityReq.getAppId(),
                    doJoinActivityReq.getActivityType().toString(), rspOrder.getOrderId(), errorCode, msg);
        }


        /**
         * Builds the coupon data.
         *
         * @param advert the advert
         * @return the string
         */
        private String buildCouponData(ObtainAdvertRsp advert) {
            if (advert == null) {
                return null;
            }
            Object fee = (advert.getLogExtMap() != null && advert.getLogExtMap().containsKey("fee")) ? advert.getLogExtMap().get("fee") : "0";
            JSONObject jsonObject = new JSONObject();
            jsonObject.put(Constants.VALUENAME.TITLE, advert.getTitle());
            jsonObject.put(Constants.VALUENAME.THUMBNAIL_PNG, advert.getThumbnailPngUrl());
            jsonObject.put(Constants.VALUENAME.END_VALID, advert.getEndValid());
            jsonObject.put(Constants.VALUENAME.ADVERT_FEE, fee);

            return jsonObject.toJSONString();
        }

        private boolean openAlipay(ActivityOptionDto option, DoJoinActivityReq doJoinActivityReq, String
                orderId, Long consumerId) {
            // 计算中奖金额
            int min = option.getMinAmount();
            int amount = option.getAlipayType() == AlipayType.QUOTA.getType() ? min : (min + new Random().nextInt(option.getMaxAmount() - min));

            // 校验实物奖品中奖限制
            if (!filterOption(option, amount, doJoinActivityReq, orderId)) {
                //2.1 扣奖项总库存
                if (!subStock(option, orderId, amount)) {
                    //2.2 回滚每日库存
                    activityService.incrAlipayDailyBudget(option.getId(), -amount);
                } else {
                    // 2.3 请求支付宝直冲奖品
                    PrizeDto prizeDto = prizeService.obtainPrize(orderId, option.getPrizeId(), amount);
                    // 2.4更新订单 创建转账记录
                    if (prizeDto != null) {
                        //记录cat监控

                        activityOrderService.createAlipayOrder(orderId, option, amount, doJoinActivityReq);
                        try {
                            activityOrderService.updateActivityOrderOfPrize(orderId, consumerId, option, prizeDto);
                        } catch (Exception e) {
                            logger.warn("支付宝红包奖项更新订单失败,降级福袋。", e);
                            // 2.5 回滚每日库存和奖项库存;奖品库存
                            activityService.incrAlipayDailyBudget(option.getId(), -amount);
                            paybackStock(option, orderId);
                            //奖品库存在orderID上添加了前缀,和奖品库存区分开
                            paybackStock(option, Constants.THE_PREFIX_ORDERID + orderId);
                            //更新活动订单失败时，需要将支付宝订单设置为删除态，降级福袋
                            AlipayOrderDto alipayOrderDto = new AlipayOrderDto();
                            alipayOrderDto.setOrderId(orderId);
                            alipayOrderDto.setStatus(AlipayOrderStatus.DELETED.getStatus());
                            activityOrderService.updateAlipayOrder(alipayOrderDto);
                            return false;
                        }
                        //7.2 更新用户中奖记录
                        activityService.updatePrizeWinConsumers(option.getId(), doJoinActivityReq.getDeviceId(), doJoinActivityReq.getIp());
                        Cat.logMetricForCount("活动中心-中支付宝");
                        return true;
                    } else {
                        // 2.5 回滚每日库存和奖项库存
                        activityService.incrAlipayDailyBudget(option.getId(), -amount);
                        paybackStock(option, orderId);
                    }
                }
            }

            return false;
        }

        private boolean openPhysical(ActivityOptionDto option, DoJoinActivityReq doJoinActivityReq, String
                orderId, Long consumerId) {
            // 校验实物奖品中奖限制
            if (!filterOption(option, 0, doJoinActivityReq, orderId) && subStock(option, orderId, 0)) {
                // 1.1 请求实物奖品
                PrizeDto prizeDto = prizeService.obtainPrize(orderId, option.getPrizeId(), null);
                // 1.2更新订单
                if (prizeDto != null) {
                    //记录cat监控
                    activityOrderService.createPhysicalOrder(orderId, option, doJoinActivityReq, prizeDto);
                    try {
                        activityOrderService.updateActivityOrderOfPrize(orderId, consumerId, option, prizeDto);
                    } catch (Exception e) {
                        logger.warn("实物奖品奖项更新订单失败,降级福袋。", e);
                        paybackStock(option, orderId);
                        //奖品库存在orderID上添加了前缀,和奖品库存区分开
                        paybackStock(option, Constants.THE_PREFIX_ORDERID + orderId);
                        //更新活动订单失败时，需要将支付宝订单设置为删除态，降级福袋
                        AlipayOrderDto alipayOrderDto = new AlipayOrderDto();
                        alipayOrderDto.setOrderId(orderId);
                        alipayOrderDto.setStatus(AlipayOrderStatus.DELETED.getStatus());
                        activityOrderService.updateAlipayOrder(alipayOrderDto);
                    }

                    // 6.2 更新该奖项当日已经中奖用户列表
                    activityService.updatePrizeWinConsumers(option.getId(), doJoinActivityReq.getDeviceId(), doJoinActivityReq.getIp());
                    Cat.logMetricForCount("活动中心-中实物");
                    return true;
                } else {
                    paybackStock(option, orderId);
                }
            }

            return false;
        }

        /**
         * 回滚库存.
         *
         * @param option  the option
         * @param orderId the order id
         * @return true, if payback stock
         * @author zp
         * @since JDK 1.6
         */
        private boolean paybackStock(ActivityOptionDto option, String orderId) {
            // 优惠券奖项扣库存
            if (isStockOption(option)) {
                Long stockId = option.getStockId();
                DubboResult<Boolean> paybackStockResult = remoteStockService.rollbackStock(ConsumeStockTypes.ActivityOrder.getType(),
                        orderId);
                if (!paybackStockResult.isSuccess()) {
                    logger.warn("[STOCK] paybackStockResult failed, the activityId=[{}], the orderId=[{}], the stockId=[{}], and because of=[{}]",
                            option.getActivityId(), orderId, stockId, paybackStockResult.getMsg());
                    return false;
                }
            }
            return true;
        }

        private boolean openQB(ActivityOptionDto option, DoJoinActivityReq doJoinActivityReq, String
                orderId, Long consumerId) {
            // 计算获得的QB数量
            int min = option.getMinAmount();
            int amount = option.getAlipayType().equals(AlipayType.QB_QUOTA.getType()) ? min : (min + new Random().nextInt(option.getMaxAmount() - min));

            // 校验QB中奖限制(QB其实是被当着实物.但是数量扣除却和支付宝一致)
            if (!filterOption(option, 0, doJoinActivityReq, orderId) && subStock(option, orderId, amount)) {
                // 1.1 请求QB奖品
                PrizeDto prizeDto = prizeService.obtainPrize(orderId, option.getPrizeId(), amount);
                // 1.2更新订单
                if (prizeDto != null) {
                    //记录cat监控
                    activityOrderService.createQBOrder(orderId, option, amount, doJoinActivityReq, prizeDto);
                    try {
                        activityOrderService.updateActivityOrderOfPrize(orderId, consumerId, option, prizeDto);
                    } catch (Exception e) {
                        logger.warn("QB奖项更新订单失败,降级福袋。", e);
                        paybackStock(option, orderId);
                        //奖品库存在orderID上添加了前缀,和奖品库存区分开
                        paybackStock(option, Constants.THE_PREFIX_ORDERID + orderId);
                        //更新活动订单失败时，需要将QB订单设置为删除态，降级福袋
                        AlipayOrderDto alipayOrderDto = new AlipayOrderDto();
                        alipayOrderDto.setOrderId(orderId);
                        alipayOrderDto.setStatus(AlipayOrderStatus.DELETED.getStatus());
                        activityOrderService.updateAlipayOrder(alipayOrderDto);
                    }
                    activityService.updatePrizeWinConsumers(option.getId(), doJoinActivityReq.getDeviceId(), doJoinActivityReq.getIp());
                    Cat.logMetricForCount("活动中心-中QB");
                    return true;
                } else {
                    paybackStock(option, orderId);
                }
            }

            return false;
        }

        private boolean openBill(ActivityOptionDto option, DoJoinActivityReq doJoinActivityReq, String orderId, Long consumerId) {
            // 计算中奖金额
            int min = option.getMinAmount();
            int max = option.getMaxAmount();
            int amount = RandomUtils.nextInt(min, max);
            if (!filterOption(option, amount, doJoinActivityReq, orderId) && subStock(option, orderId, amount)) {
                // 请求话费奖品
                PrizeDto prizeDto = prizeService.obtainPrize(orderId, option.getPrizeId(), amount);
                if (prizeDto != null) {
                    activityOrderService.createBillOrder(orderId, option, amount, doJoinActivityReq);
                }
                try {
                    activityOrderService.updateActivityOrderOfPrize(orderId, consumerId, option, prizeDto);
                } catch (Exception e) {
                    logger.warn("话费奖项更新订单失败,降级福袋。", e);
                    paybackStock(option, orderId);
                    //奖品库存在orderID上添加了前缀,和奖品库存区分开
                    paybackStock(option, Constants.THE_PREFIX_ORDERID + orderId);
                    //更新活动订单失败时，需要将话费订单设置为删除态，降级福袋
                    AlipayOrderDto alipayOrderDto = new AlipayOrderDto();
                    alipayOrderDto.setOrderId(orderId);
                    alipayOrderDto.setStatus(AlipayOrderStatus.DELETED.getStatus());
                    activityOrderService.updateAlipayOrder(alipayOrderDto);
                }
                activityService.updatePrizeWinConsumers(option.getId(), doJoinActivityReq.getDeviceId(), doJoinActivityReq.getIp());
                Cat.logMetricForCount("活动中心-中话费");
                return true;
            } else {
                paybackStock(option, orderId);
            }

            return false;
        }

        private boolean obtainVirtual(ActivityOptionDto option, DoJoinActivityReq doJoinActivityReq, String
                orderId, Long consumerId, PrizeDto prizeDto, Integer amount, String amountStr) throws ActivityException {
            Integer alipayType = option.getAlipayType();
            if (AlipayType.isVirtualApi(alipayType) || AlipayType.VIRTUAL_ACCOUNT.getType().equals(alipayType)) {//api/账户充值类型
                // 创建账户类型 订单
                activityOrderService.createVirtualApiOrder(orderId, option, doJoinActivityReq, prizeDto, amount, amountStr);
            } else if (AlipayType.isVirtualTicket(option.getAlipayType())) {//券码

                // 获取券码
                CouponsCodeDto coupon = prizeDto.getCouponsCodeDto();

                //创建 券码 订单
                activityOrderService.createVirtualTicketOrder(orderId, option, doJoinActivityReq, coupon);

            } else if (AlipayType.VIRTUAL_RED_PACKET.getType().equals(alipayType)) {
                // 获取瓜分红包
                boolean success = redPacketService.obtainPacket(doJoinActivityReq.getAppId(), consumerId, prizeDto.getRedPacket().getPageId(), orderId);
                if (!success) {
                    return false;
                }
                // 创建瓜分红包类型 订单
                activityOrderService.createRedPacketOrder(orderId, option, doJoinActivityReq);

            } else {
                return false;
            }
            // 更新活动订单
            activityOrderService.updateActivityOrderOfPrize(orderId, consumerId, option, prizeDto);
            //更新每日中奖记录

            //逻辑：如果api类型，如果媒体有传userId,则依据媒体的userId做用户限制
            String userId = doJoinActivityReq.getUserId();
            String user = doJoinActivityReq.getDeviceId();
            if (AlipayType.isVirtualApi(option.getAlipayType()) && StringUtils.isNotBlank(userId)) {
                user = userId + "-" + doJoinActivityReq.getAppId();
            }
            if (Integer.valueOf(1).equals(option.getAwardLimit())) {
                activityService.updatePrizeWinConsumers(option.getId(), user, doJoinActivityReq.getIp());
            }
            if (Integer.valueOf(2).equals(option.getAwardLimit())) {
                activityService.updatePrizeWinConsumersWeek(option.getId(), user, doJoinActivityReq.getIp());
            }
            Cat.logMetricForCount("活动中心-中虚拟奖品");
            return true;
        }

        private boolean openVirtual(ActivityOptionDto option, DoJoinActivityReq doJoinActivityReq, String
                orderId, Long consumerId) throws ActivityException {
            // 计算任意数值类型 中奖金额
            // amount 中奖金额(单位 分) (扣库存时，扣此值)
            // amountStr 中奖金额(单位 元，小数位数由 randomType决定)
            int amount = 0;
            String amountStr = null;
            if (AlipayType.VIRTUAL_API_1.getType().equals(option.getAlipayType())) {
                int min = option.getMinAmount();
                int max = option.getMaxAmount();
                amount = RandomUtils.nextInt(min, max);

                Integer randomType = option.getRandomType();

                if (randomType == 0) {//整数
                    int temp = amount / 100;
                    amountStr = String.valueOf(temp);
                    amount = temp * 100;
                } else if (randomType == 1) {//一位小数
                    int temp = amount / 10;
                    amountStr = new BigDecimal(temp).divide(new BigDecimal(10)).setScale(1, BigDecimal.ROUND_FLOOR).toString();
                    amount = temp * 10;
                } else if (randomType == 2) {//二位小数
                    amountStr = MathUtil.fen2Yuan(amount);
                }
            }

            //
            if (!filterOption(option, amount, doJoinActivityReq, orderId) && subStock((option), orderId, amount)) {
                //请求虚拟奖品
                try {
                    PrizeDto prizeDto = prizeService.obtainPrize(orderId, option.getPrizeId(), amount == 0 ? null : amount);
                    if (prizeDto != null && obtainVirtual(option, doJoinActivityReq, orderId, consumerId, prizeDto, amount, amountStr)) {
                        return true;
                    }

                } catch (Exception e) {
                    logger.error("请求虚拟奖品异常，降级福袋，回滚库存", e);
                }
                paybackStock(option, orderId);

            }
            return false;
        }

        /**
         * 奖项验证.
         *
         * @param option the option
         * @return true, if option check
         * @author zp
         * @since JDK 1.6
         */

        private boolean optionCheck(ActivityOptionDto option) {
            // 库存型奖项库存验证
            if (isStockOption(option)) {
                return checkStock(option);
            }
            return true;
        }

        /**
         * 校验库存.
         *
         * @param option the option
         * @return true, if check stock
         */
        private boolean checkStock(ActivityOptionDto option) {
            // 1.如果奖项是库存型奖品， 校验库存是否足够
            Long stockId = option.getStockId();

            // 2. 查询库存
            Long stock = stockService.find(stockId);

            // 3.库存记录不存在， 降级走福袋
            if (stock == null) {
                return false;
            }
            // 4.库存不足， 降级走福袋
            if (stock <= 0) {
                logger.info("the stock of option=[{}] is not enough", option.getId());
                return false;
            }
            return true;
        }

        /**
         * 扣库存 subStock:(这里用一句话描述这个方法的作用). <br/>
         *
         * @param option  the option
         * @param orderId the order id
         * @return true, if sub stock
         * @author zp
         * @since JDK 1.6
         */
        private boolean subStock(ActivityOptionDto option, String orderId, int num) {
            // 优惠券奖项扣库存
            if (isStockOption(option)) {
                Long stockId = option.getStockId();
                DubboResult<Boolean> consumeStockResult;
                if (num == 0) {
                    consumeStockResult = remoteStockService.consumeStockOne(ConsumeStockTypes.ActivityOrder.getType(),
                            orderId, stockId);
                } else {
                    consumeStockResult = remoteStockService.consumeStockNum(ConsumeStockTypes.ActivityOrder.getType(),
                            orderId, stockId, num);
                }
                if (!consumeStockResult.isSuccess()) {
                    logger.warn("[STOCK] consumeStock failed, the activityId=[{}], the orderId=[{}], the stockId=[{}], and because of=[{}]",
                            option.getActivityId(), orderId, stockId, consumeStockResult.getMsg());
                    return false;
                }
            }
            return true;
        }

        /**
         * @Description: isStockOption 判断是否为库存型奖品
         * @Param: [option]
         * @Return: boolean
         * @throws:
         * @author: youhaijun
         * @Date: 2017/7/12
         */
        private boolean isStockOption(ActivityOptionDto option) {
            return ActivityOptionType.isStockType(option.getPrizeType());
        }

    }
}
