
package com.tmerclub.cloud.coupon.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.tmerclub.cloud.api.coupon.constant.CouponType;
import com.tmerclub.cloud.api.coupon.constant.PutOnStatus;
import com.tmerclub.cloud.api.coupon.vo.CouponDataVO;
import com.tmerclub.cloud.api.feign.SearchSpuFeignClient;
import com.tmerclub.cloud.api.multishop.bo.EsShopDetailBO;
import com.tmerclub.cloud.api.multishop.feign.ShopDetailFeignClient;
import com.tmerclub.cloud.api.multishop.vo.ShopDetailVO;
import com.tmerclub.cloud.api.platform.constant.OfflineHandleEventStatus;
import com.tmerclub.cloud.api.platform.constant.OfflineHandleEventType;
import com.tmerclub.cloud.api.platform.dto.OfflineHandleEventDTO;
import com.tmerclub.cloud.api.platform.feign.OfflineHandleEventFeignClient;
import com.tmerclub.cloud.api.platform.vo.OfflineHandleEventVO;
import com.tmerclub.cloud.api.product.feign.SpuFeignClient;
import com.tmerclub.cloud.api.user.feign.PopupFeignClient;
import com.tmerclub.cloud.api.user.feign.UserFeignClient;
import com.tmerclub.cloud.api.vo.EsPageVO;
import com.tmerclub.cloud.common.cache.bo.MultiDecrementBO;
import com.tmerclub.cloud.common.cache.constant.CacheNames;
import com.tmerclub.cloud.common.cache.enums.MultiStockEnum;
import com.tmerclub.cloud.common.cache.manager.MultiRedisStockManager;
import com.tmerclub.cloud.common.cache.util.RedisUtil;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.constant.JumpType;
import com.tmerclub.cloud.common.constant.StatusEnum;
import com.tmerclub.cloud.common.database.dto.PageDTO;
import com.tmerclub.cloud.common.database.util.PageAdapter;
import com.tmerclub.cloud.common.database.util.PageUtil;
import com.tmerclub.cloud.common.database.vo.PageVO;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.leaf.manager.SegmentManager;
import com.tmerclub.cloud.common.order.vo.CouponOrderVO;
import com.tmerclub.cloud.common.product.constant.SpuType;
import com.tmerclub.cloud.common.product.dto.ProductSearchDTO;
import com.tmerclub.cloud.common.product.dto.ProductSearchLimitDTO;
import com.tmerclub.cloud.common.product.vo.search.ProductSearchVO;
import com.tmerclub.cloud.common.product.vo.search.SpuSearchVO;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.util.BeanUtil;
import com.tmerclub.cloud.constant.DistributedIdKey;
import com.tmerclub.cloud.coupon.bo.mongo.MongoCouponUserBO;
import com.tmerclub.cloud.coupon.constant.CouponConstant;
import com.tmerclub.cloud.coupon.constant.CouponStatus;
import com.tmerclub.cloud.coupon.constant.SuitableProdType;
import com.tmerclub.cloud.coupon.constant.ValidTimeType;
import com.tmerclub.cloud.coupon.dto.CouponDTO;
import com.tmerclub.cloud.coupon.manager.CouponStockManager;
import com.tmerclub.cloud.coupon.mapper.CouponMapper;
import com.tmerclub.cloud.coupon.model.Coupon;
import com.tmerclub.cloud.coupon.service.CouponService;
import com.tmerclub.cloud.coupon.service.CouponSpuService;
import com.tmerclub.cloud.coupon.service.CouponUserService;
import com.tmerclub.cloud.coupon.vo.CouponAppVO;
import com.tmerclub.cloud.coupon.vo.CouponVO;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 优惠券
 *
 * @author YXF
 */
@Service
public class CouponServiceImpl implements CouponService {

    private static final Logger logger = LoggerFactory.getLogger(CouponServiceImpl.class);

    @Autowired
    private CouponMapper couponMapper;
    @Autowired
    private CouponSpuService couponSpuService;
    @Autowired
    private CouponUserService couponUserService;
    @DubboReference
    private SearchSpuFeignClient searchSpuFeignClient;
    @DubboReference
    private ShopDetailFeignClient shopDetailFeignClient;
    @DubboReference
    private SpuFeignClient spuFeignClient;
    @DubboReference
    private OfflineHandleEventFeignClient offlineHandleEventFeignClient;
    @DubboReference
    private UserFeignClient userFeignClient;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private CouponStockManager couponStockManager;
    @Autowired
    private SegmentManager segmentManager;
    @Autowired
    private MultiRedisStockManager multiRedisStockManager;
    @DubboReference
    private PopupFeignClient popupFeignClient;

    @Override
    public PageVO<CouponVO> page(PageDTO pageDTO, CouponDTO couponDTO) {
        PageVO<CouponVO> page = PageUtil.doPage(pageDTO, () -> couponMapper.list(couponDTO));
        for (CouponVO couponVO : page.getList()) {
            if (Objects.equals(couponVO.getStatus(), CouponStatus.NO_OVERDUE.value()) && Objects.equals(couponVO.getPutonStatus(), PutOnStatus.PUT_ON.value())) {
                // 正在投放的优惠券的库存
                Integer stock = couponStockManager.getStockByCouponId(couponVO.getCouponId());
                couponVO.setStocks(stock);
            }
        }
        return page;
    }

    @Override
    @Cacheable(cacheNames = CacheNames.COUPON_LIST_BY_SHOP_KEY, key = "#shopId")
    public List<CouponAppVO> getShopCouponList(Long shopId) {
        List<CouponAppVO> coupons = couponMapper.getAvailableShopCouponList(shopId);
        if (CollectionUtil.isEmpty(coupons)) {
            return coupons;
        }
        Set<Long> spuIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        CouponAppVO couponAppVO = coupons.get(0);
        String shopName = "";
        if (!Objects.equals(shopId, Constant.PLATFORM_SHOP_ID)) {
            ServerResponseEntity<EsShopDetailBO> shopResponse = shopDetailFeignClient.getShopByShopId(couponAppVO.getShopId());
            if (!Objects.equals(shopResponse.getCode(), ResponseEnum.OK.value())) {
                throw new LuckException(shopResponse.getMsg());
            }
            shopName = shopResponse.getData().getShopName();
        }
        for (CouponAppVO coupon : coupons) {
            coupon.setShopName(shopName);
            spuIds.addAll(coupon.getSpuIds());
            coupon.setHasReceive(CouponAppVO.NOT_RECEIVE);
        }
        if (CollUtil.isEmpty(spuIds)) {
            return coupons;
        }
        if (CollectionUtil.isNotEmpty(spuIds)) {
            ServerResponseEntity<List<SpuSearchVO>> spuListResponse = searchSpuFeignClient.listSpuBySpuIds(new ArrayList<>(spuIds));
            for (CouponAppVO coupon : coupons) {
                List<SpuSearchVO> spuList = spuListResponse.getData()
                        .stream()
                        .filter(spuSearch -> CollUtil.isNotEmpty(coupon.getSpuIds()) && coupon.getSpuIds().contains(spuSearch.getSpuId()))
                        .collect(Collectors.toList());
                coupon.setSpus(spuList);
            }
        }
        coupons.sort(Comparator.comparingLong(CouponAppVO::getCashCondition));
        return coupons;
    }

    @Override
    @Cacheable(cacheNames = CacheNames.COUPON_AND_SPU_DATA, key = "#couponId")
    public CouponVO getCouponAndProdData(Long couponId) {
        CouponVO coupon = couponMapper.getCouponAndCouponProdsByCouponId(couponId);
        if (Objects.isNull(coupon) || CollUtil.isEmpty(coupon.getSpuIds())) {
            return coupon;
        }
        ServerResponseEntity<List<SpuSearchVO>> spusRespon = searchSpuFeignClient.listSpuBySpuIds(coupon.getSpuIds());
        coupon.setSpus(spusRespon.getData());
        return coupon;
    }

    @Override
    public Coupon getByCouponId(Long couponId) {
        return couponMapper.getByCouponId(couponId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long save(CouponDTO couponDTO) {
        checkCoupon(couponDTO);
        Coupon coupon = BeanUtil.map(couponDTO, Coupon.class);
        coupon.setTotalStock(coupon.getStocks());
        //判断为领取后生效类型时，开始时间和结束时间为空
        if (Objects.equals(coupon.getValidTimeType(), ValidTimeType.RECEIVE.value())) {
            coupon.setEndTime(null);
            coupon.setStartTime(null);
        }
        // 结束时间小于等于当前时间
        Date nowTime = new Date();
        if (coupon.getValidTimeType() == 1 && coupon.getEndTime().getTime() < nowTime.getTime()) {
            coupon.setStatus(CouponStatus.OVERDUE.value());
            coupon.setPutonStatus(PutOnStatus.CANCEL.value());
        }
        if (Objects.equals(coupon.getPutonStatus(), PutOnStatus.PUT_ON.value())) {
            coupon.setLaunchTime(new Date());
        }
        couponMapper.save(coupon);
        if (Objects.isNull(coupon.getCouponId())) {
            throw new LuckException("优惠券数据保存异常，请刷新后重试");
        }
        if (Objects.equals(coupon.getSuitableProdType(), SuitableProdType.ASSIGN_SPU.value())) {
            // 保存优惠券商品关联信息
            couponSpuService.save(coupon.getCouponId(), couponDTO.getSpuIds());
        }
        // 把库存存入redis
        MultiDecrementBO multiDecrementBO = new MultiDecrementBO(MultiStockEnum.COUPON, null, null,
                coupon.getCouponId(), coupon.getCouponId(), null, null, null);
        multiRedisStockManager.doCouponZone(multiDecrementBO, coupon.getTotalStock());
        // 删除商品缓存
        removeSpuCache(coupon.getShopId(), couponDTO.getSpuIds());

        return coupon.getCouponId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void update(CouponDTO couponDTO) {
        checkCoupon(couponDTO);
        CouponVO dbCoupon = couponMapper.getCouponAndCouponProdsByCouponId(couponDTO.getCouponId());
        if (!PutOnStatus.offline(couponDTO.getPutonStatus()) && PutOnStatus.offline(dbCoupon.getPutonStatus())) {
            throw new LuckException("优惠券已下线，不能修改状态");
        } else if (!Objects.equals(dbCoupon.getShopId(), AuthUserContext.get().getTenantId())) {
            throw new LuckException(ResponseEnum.UNAUTHORIZED);
        }
        couponDTO.setStatus(null);
        // 结束时间小于等于当前时间
        if (Objects.equals(couponDTO.getValidTimeType(), ValidTimeType.FIXED.value())
                && !Objects.equals(dbCoupon.getStatus(), StatusEnum.DELETE.value())) {
            Date nowTime = new Date();
            if (couponDTO.getEndTime().getTime() < nowTime.getTime()) {
                couponDTO.setStatus(CouponStatus.OVERDUE.value());
                couponDTO.setPutonStatus(PutOnStatus.CANCEL.value());
            } else {
                couponDTO.setStatus(CouponStatus.NO_OVERDUE.value());
            }
        }
        boolean isPutOn = Objects.equals(dbCoupon.getPutonStatus(), PutOnStatus.WAIT_PUT_ON.value()) && Objects.equals(couponDTO.getPutonStatus(), PutOnStatus.PUT_ON.value());
        if (isPutOn) {
            couponDTO.setLaunchTime(new Date());
        }
        couponMapper.updateCoupon(couponDTO);
        // 更新优惠券商品关联信息
        couponSpuService.update(couponDTO, dbCoupon);
        // 投放状态及商品列表没有改变， 不需要更新缓存
        boolean notRemoveCahce = Objects.equals(couponDTO.getPutonStatus(), dbCoupon.getPutonStatus()) &&
                new HashSet<>(couponDTO.getSpuIds()).containsAll(dbCoupon.getSpuIds()) &&
                Objects.equals(couponDTO.getSuitableProdType(), dbCoupon.getSuitableProdType());
        if (notRemoveCahce) {
            return;
        }
        boolean cancelBinding = Objects.equals(dbCoupon.getShopId(), Constant.PLATFORM_SHOP_ID) &&
                !Objects.equals(couponDTO.getPutonStatus(), dbCoupon.getPutonStatus()) &&
                Objects.equals(dbCoupon.getPutonStatus(), PutOnStatus.PUT_ON.value());
        if (cancelBinding) {
            ServerResponseEntity<Void> responseEntity = userFeignClient.cancelBindingCoupons(Collections.singletonList(couponDTO.getCouponId()));
            if (!responseEntity.isSuccess()) {
                throw new LuckException(responseEntity.getMsg());
            }
        }
        if (!Objects.equals(couponDTO.getPutonStatus(), PutOnStatus.PUT_ON.value())) {
            // 删除关联弹窗
            ServerResponseEntity<Void> response = popupFeignClient.deleteLink(dbCoupon.getCouponId(), dbCoupon.getShopId(), JumpType.COUPON.value(), null);
            if (!response.isSuccess()) {
                throw new LuckException(response.getMsg());
            }
        }
        // 清除关联商品缓存
        List<Long> spuIds = couponDTO.getSpuIds();
        if (!Objects.equals(couponDTO.getSuitableProdType(), dbCoupon.getSuitableProdType())) {
            spuIds.clear();
        } else if (Objects.equals(couponDTO.getSuitableProdType(), SuitableProdType.ASSIGN_SPU.value())) {
            spuIds = CollUtil.addAllIfNotContains(dbCoupon.getSpuIds(), couponDTO.getSpuIds());
        }
        removeSpuCache(couponDTO.getShopId(), spuIds);
        // 优惠券缓存落库
        couponStockManager.couponStockPlacement(Collections.singletonList(couponDTO.getCouponId()), false);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void deleteById(Long couponId) {
        CouponVO dbCoupon = getCouponAndCouponProdsByCouponId(couponId);
        if (!Objects.equals(dbCoupon.getShopId(), AuthUserContext.get().getTenantId()) && !Objects.equals(Constant.PLATFORM_SHOP_ID, AuthUserContext.get().getTenantId())) {
            throw new LuckException(ResponseEnum.UNAUTHORIZED);
        }
        Coupon coupon = new Coupon();
        coupon.setCouponId(couponId);
        coupon.setStatus(StatusEnum.DELETE.value());
        couponMapper.update(coupon);
        removeSpuCache(dbCoupon.getShopId(), dbCoupon.getSpuIds());
        if (Objects.equals(dbCoupon.getShopId(), Constant.PLATFORM_SHOP_ID)) {
            ServerResponseEntity<Void> responseEntity = userFeignClient.cancelBindingCoupons(Collections.singletonList(dbCoupon.getCouponId()));
            if (!responseEntity.isSuccess()) {
                throw new LuckException(responseEntity.getMsg());
            }
        }
        // 删除关联弹窗
        ServerResponseEntity<Void> response = popupFeignClient.deleteLink(couponId, dbCoupon.getShopId(), JumpType.COUPON.value(), null);
        if (!response.isSuccess()) {
            throw new LuckException(response.getMsg());
        }
        // 优惠券缓存落库
        couponStockManager.couponStockPlacement(Collections.singletonList(couponId), true);
    }

    @Override
    public CouponVO getCouponAndCouponProdsByCouponId(Long couponId) {
        CouponVO coupon = couponMapper.getCouponAndCouponProdsByCouponId(couponId);
        if (Objects.isNull(coupon) || Objects.equals(coupon.getSuitableProdType(), SuitableProdType.ALL_SPU.value())) {
            return coupon;
        }
        ServerResponseEntity<List<SpuSearchVO>> spuResponse = searchSpuFeignClient.listSpuBySpuIds(coupon.getSpuIds());
        coupon.setSpus(spuResponse.getData());
        return coupon;
    }

    @Override
    public void batchBindCouponByCouponIds(List<Long> couponIds, Long userId) {
        List<MongoCouponUserBO> couponUsers = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<Coupon> coupons = couponMapper.getListByCouponIds(couponIds);
        if (CollUtil.isEmpty(coupons)) {
            return;
        }
        for (Coupon coupon : coupons) {
            Long couponId = coupon.getCouponId();
            Date nowTime = new Date();
            // 当优惠券状态不为投放时
            if (Objects.equals(coupon.getStatus(), StatusEnum.DISABLE.value())
                    || !Objects.equals(coupon.getPutonStatus(), PutOnStatus.PUT_ON.value())
                    || coupon.getStocks() < 1) {
                logger.warn("优惠券：{}无法被领取或者该券领完了!", couponId);
                continue;
            }
            long count = couponUserService.getUserHasCouponCount(couponId, userId);
            if (count >= coupon.getLimitNum()) {
                logger.warn("用户：{}，的优惠券：{}已达个人领取上限，无法继续领取", userId, couponId);
                continue;
            }
            if (Objects.equals(coupon.getStatus(), CouponStatus.NO_OVERDUE.value())
                    && Objects.equals(coupon.getPutonStatus(), PutOnStatus.PUT_ON.value())) {
                // 正在投放的优惠券的库存
                Integer stock = couponStockManager.getStockByCouponId(couponId);
                coupon.setStocks(stock);
            }
            Long couponUserId = segmentManager.getSegmentId(DistributedIdKey.tmerclub_COUPON_USER);
            MongoCouponUserBO couponUser = getCouponUser(userId, coupon, nowTime, couponUserId);
            // 减少优惠券的库存
            MultiDecrementBO multiDecrementBO = new MultiDecrementBO(MultiStockEnum.COUPON, userId, couponUserId, couponId,
                    couponId, 1, coupon.getLimitNum(), null);
            if (couponStockManager.decrStocks(multiDecrementBO)) {
                // 扣减成功
                couponUsers.add(couponUser);
            }
            // 清除缓存
            removeCacheByShopId(coupon.getShopId(), couponId);
        }
        if (CollUtil.isEmpty(couponUsers)) {
            return;
        }
        // 存储领券记录和删除锁定记录
        couponStockManager.saveLogAndDeleteLock(couponUsers);
    }

    private MongoCouponUserBO getCouponUser(Long userId, Coupon coupon, Date nowTime, Long couponUserId) {
        MongoCouponUserBO couponUser = new MongoCouponUserBO();
        couponUser.setCouponUserId(couponUserId);
        couponUser.setUserId(userId);
        couponUser.setShopId(coupon.getShopId());
        couponUser.setStatus(StatusEnum.ENABLE.value());
        couponUser.setCouponId(coupon.getCouponId());
        couponUser.setReceiveTime(nowTime);
        // 生效时间类型为固定时间
        if (Objects.equals(coupon.getValidTimeType(), ValidTimeType.FIXED.value())) {
            couponUser.setUserStartTime(coupon.getStartTime());
            couponUser.setUserEndTime(coupon.getEndTime());
        }
        // 生效时间类型为领取后生效
        if (Objects.equals(coupon.getValidTimeType(), ValidTimeType.RECEIVE.value())) {
            if (coupon.getAfterReceiveDays() == null) {
                coupon.setAfterReceiveDays(0);
            }
            if (coupon.getValidDays() == null) {
                coupon.setValidDays(0);
            }
            couponUser.setUserStartTime(DateUtils.addDays(DateUtil.beginOfDay(nowTime), coupon.getAfterReceiveDays()));
            couponUser.setUserEndTime(DateUtils.addDays(couponUser.getUserStartTime(), coupon.getValidDays()));
        }
        couponUser.setIsDelete(0);
        return couponUser;
    }

    @Override
    public EsPageVO<ProductSearchVO> spuListByCouponId(PageDTO pageDTO, ProductSearchDTO productSearch, Long couponId) {
        CouponServiceImpl couponService = (CouponServiceImpl) AopContext.currentProxy();
        CouponVO coupon = couponService.getCouponAndProdData(couponId);
        if (Objects.isNull(coupon)) {
            throw new LuckException("该优惠券不存在");
        }
        if (Objects.equals(coupon.getStatus(), CouponStatus.NO_OVERDUE.value()) && Objects.equals(coupon.getPutonStatus(), PutOnStatus.PUT_ON.value())) {
            // 正在投放的优惠券的库存
            Integer stock = couponStockManager.getStockByCouponId(coupon.getCouponId());
            coupon.setStocks(stock);
        }
        // 全部商品
        if (Objects.equals(coupon.getSuitableProdType(), SuitableProdType.ALL_SPU.value()) && !Objects.equals(coupon.getShopId(), Constant.PLATFORM_SHOP_ID)) {
            productSearch.setShopId(coupon.getShopId());
            List<Integer> notProdTypes = new ArrayList<>();
            notProdTypes.add(SpuType.GROUP.value());
            notProdTypes.add(SpuType.SECKILL.value());
            productSearch.setMustNotProdTypes(notProdTypes);
        }
        // 指定商品
        else {
            productSearch.setSpuIds(coupon.getSpuIds());
        }
        productSearch.setPageSize(pageDTO.getPageSize());
        productSearch.setPageNum(pageDTO.getPageNum());
        ServerResponseEntity<EsPageVO<ProductSearchVO>> spuPageResponse = searchSpuFeignClient.search(productSearch);
        return spuPageResponse.getData();
    }

    @Override
    public void receive(Long couponId) {
        Coupon coupon = couponMapper.getByCouponId(couponId);
        long currentTime = System.currentTimeMillis();
        // 优惠券已过期、未投放或者超出结束时间，都不能再领取
        boolean cannotReceive = !Objects.equals(coupon.getStatus(), CouponStatus.NO_OVERDUE.value()) ||
                !Objects.equals(coupon.getPutonStatus(), PutOnStatus.PUT_ON.value()) ||
                (Objects.nonNull(coupon.getEndTime()) && currentTime >= coupon.getEndTime().getTime());
        if (cannotReceive) {
            throw new LuckException("优惠券已结束领取");
        }
        Long userId = AuthUserContext.get().getUserId();
        long count = couponUserService.getUserHasCouponCount(coupon.getCouponId(), userId);
        if (count >= coupon.getLimitNum()) {
            throw new LuckException("该优惠券已达个人领取上限，无法继续领取");
        }

        Long couponUserId = segmentManager.getSegmentId(DistributedIdKey.tmerclub_COUPON_USER);
        // 减少优惠券的库存
        MultiDecrementBO multiDecrementBO = new MultiDecrementBO(MultiStockEnum.COUPON, userId, couponUserId, couponId,
                couponId, 1, coupon.getLimitNum(), null);
        multiRedisStockManager.decrStocks(multiDecrementBO);

        // 存储领券记录和删除锁定记录
        MongoCouponUserBO couponUser = getCouponUser(userId, coupon, new Date(), couponUserId);
        couponStockManager.saveLogAndDeleteLock(Collections.singletonList(couponUser));
    }

    @Override
    public PageVO<CouponAppVO> getUserCouponPage(PageDTO page, Long type, Integer status) {
        PageVO<CouponAppVO> couponPage = couponUserService.pageUserCoupon(page, AuthUserContext.get().getUserId(), type, status);
        List<CouponAppVO> couponDtoList = couponPage.getList();
        Set<Long> shopSet = couponDtoList.stream().map(CouponAppVO::getShopId).collect(Collectors.toSet());
        ServerResponseEntity<List<ShopDetailVO>> shopResponse = shopDetailFeignClient.listByShopIds(new ArrayList<>(shopSet));
        Map<Long, String> shopMap = shopResponse.getData().stream().collect(Collectors.toMap(ShopDetailVO::getShopId, ShopDetailVO::getShopName));
        for (CouponAppVO coupon : couponDtoList) {
            if (Objects.equals(coupon.getValidTimeType(), ValidTimeType.RECEIVE.value())) {
                Integer afterReceiveDays = coupon.getAfterReceiveDays();
                if (Objects.isNull(coupon.getAfterReceiveDays())) {
                    afterReceiveDays = 0;
                }
                Integer validDays = coupon.getValidDays();
                if (validDays == null) {
                    validDays = 0;
                }
                coupon.setStartTime(DateUtil.offsetDay(coupon.getReceiveTime(), afterReceiveDays));
                coupon.setEndTime(DateUtil.offsetDay(coupon.getStartTime(), validDays));
            }
            coupon.setShopName(shopMap.get(coupon.getShopId()));
        }
        return couponPage;
    }

    @Override
    public List<CouponAppVO> generalCouponList(Long userId) {
        List<CouponAppVO> couponList = couponMapper.generalCouponList(userId);
        if (Objects.nonNull(userId) && CollUtil.isNotEmpty(couponList)) {
            // 补充领券记录
            couponList = couponUserService.setCouponUserInfo(couponList, userId);
        }
        for (CouponAppVO coupon : couponList) {
            if (Objects.equals(coupon.getStatus(), CouponStatus.NO_OVERDUE.value()) && Objects.equals(coupon.getPutonStatus(), PutOnStatus.PUT_ON.value())) {
                // 正在投放的优惠券的库存
                Integer stock = couponStockManager.getStockByCouponId(coupon.getCouponId());
                coupon.setStocks(stock);
            }
        }
        return couponList;
    }

    @Override
    public PageVO<CouponAppVO> getProdCouponPage(PageDTO pageDTO, Long userId) {
        PageVO<CouponAppVO> couponPage = new PageVO<>();
        List<CouponAppVO> coupons = couponMapper.getProdCouponList(new PageAdapter(pageDTO), userId);
        if (Objects.nonNull(userId)) {
            coupons = couponUserService.setCouponUserInfo(coupons, userId);
        }
        couponPage.setTotal(couponMapper.getProdCouponListCount());
        couponPage.setPages(PageUtil.getPages(couponPage.getTotal(), pageDTO.getPageSize()));
        couponPage.setList(coupons);
        Set<Long> spuUnionShopIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        Set<Long> shopIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        Set<Long> spuIds = new HashSet<>(Constant.INITIAL_CAPACITY);
        for (CouponAppVO couponVO : coupons) {
            // 正在投放的优惠券的库存
            Integer stock = couponStockManager.getStockByCouponId(couponVO.getCouponId());
            couponVO.setStocks(stock);
            shopIds.add(couponVO.getShopId());
            if (Objects.equals(couponVO.getSuitableProdType(), SuitableProdType.ALL_SPU.value())) {
                spuUnionShopIds.add(couponVO.getShopId());
                continue;
            }
            int start = 1;
            for (Long spuId : couponVO.getSpuIds()) {
                if (start > Constant.SIZE_OF_THREE) {
                    break;
                }
                start++;
                spuIds.add(spuId);
            }
        }
        loadSpuData(new ArrayList<>(spuUnionShopIds), new ArrayList<>(spuIds), new ArrayList<>(shopIds), coupons);
        return couponPage;
    }

    @Override
    public List<Coupon> getCouponListByCouponIds(List<Long> couponIds) {
        if (CollUtil.isEmpty(couponIds)) {
            return Collections.emptyList();
        }
        return couponMapper.getCouponListByCouponIds(couponIds, null);
    }

    @Override
    public List<CouponOrderVO> getCouponListByUserIdAndShopId(Long userId, Long shopId) {
        List<CouponOrderVO> list = couponUserService.getCouponListByUserIdAndShopId(userId, shopId);
        if (CollUtil.isEmpty(list)) {
            return new ArrayList<>();
        }
        Set<Long> couponIds = list.stream().map(CouponOrderVO::getCouponId).collect(Collectors.toSet());
        Map<Long, List<CouponOrderVO>> couponMap = list.stream().collect(Collectors.groupingBy(CouponOrderVO::getCouponId));
        List<CouponOrderVO> resList = couponMapper.getCouponListByUserIdAndShopId(new ArrayList<>(couponIds));
        for (CouponOrderVO couponOrderVO : resList) {
            List<CouponOrderVO> couponOrderList = couponMap.get(couponOrderVO.getCouponId());
            for (CouponOrderVO couponUserInfo : couponOrderList) {
                couponOrderVO.setStartTime(couponUserInfo.getStartTime());
                couponOrderVO.setEndTime(couponUserInfo.getEndTime());
                couponOrderVO.setCouponUserId(couponUserInfo.getCouponUserId());
            }
        }
        return resList;
    }

    @Override
    public void removeCacheByShopId(Long shopId, Long couponId) {
        List<String> keys = new ArrayList<>(Constant.INITIAL_CAPACITY);
        if (Objects.nonNull(shopId)) {
            keys.add(CacheNames.COUPON_LIST_BY_SHOP_KEY + CacheNames.UNION + shopId);
        }
        if (Objects.nonNull(couponId)) {
            keys.add(CacheNames.COUPON_AND_SPU_DATA + CacheNames.UNION + couponId);
        }
        RedisUtil.deleteBatch(keys);
    }

    @Override
    public PageVO<CouponVO> adminPage(PageDTO pageDTO, CouponDTO couponDTO) {
        if (StringUtils.isNotBlank(couponDTO.getShopName())) {
            // 获取店铺id
            List<Long> data = shopDetailFeignClient.getShopIdsByLikeShopName(couponDTO.getShopName()).getData();
            couponDTO.setShopIds(data);
        }
        PageVO<CouponVO> page = PageUtil.doPage(pageDTO, () -> couponMapper.adminList(couponDTO));
        Set<Long> shopSet = page.getList().stream().map(CouponVO::getShopId).collect(Collectors.toSet());
        ServerResponseEntity<List<ShopDetailVO>> shopResponse = shopDetailFeignClient.listByShopIds(new ArrayList<>(shopSet));
        Map<Long, String> shopMap = shopResponse.getData().stream().collect(Collectors.toMap(ShopDetailVO::getShopId, ShopDetailVO::getShopName));
        for (CouponVO coupon : page.getList()) {
            if (Objects.equals(coupon.getStatus(), CouponStatus.NO_OVERDUE.value()) && Objects.equals(coupon.getPutonStatus(), PutOnStatus.PUT_ON.value())) {
                // 正在投放的优惠券的库存
                Integer stock = couponStockManager.getStockByCouponId(coupon.getCouponId());
                coupon.setStocks(stock);
            }
            coupon.setShopName(shopMap.get(coupon.getShopId()));
        }
        return page;
    }

    @Override
    public void changeCouponStatusAndPutOnStatus(Long couponId, Integer putOnStatus, Integer status) {
        couponMapper.changeCouponStatusAndPutOnStatus(couponId, putOnStatus, status);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void offline(OfflineHandleEventDTO offlineHandleEventDto) {
        OfflineHandleEventDTO offlineHandleEvent = new OfflineHandleEventDTO();
        offlineHandleEvent.setHandleId(offlineHandleEventDto.getHandleId());
        offlineHandleEvent.setHandleType(OfflineHandleEventType.COUPON.getValue());
        offlineHandleEvent.setOfflineReason(offlineHandleEventDto.getOfflineReason());
        ServerResponseEntity<Void> responseEntity = offlineHandleEventFeignClient.save(offlineHandleEvent);
        if (!Objects.equals(responseEntity.getCode(), ResponseEnum.OK.value())) {
            throw new LuckException(responseEntity.getMsg());
        }
        // 更新活动状态为下线状态
        changeCouponStatusAndPutOnStatus(offlineHandleEvent.getHandleId(), PutOnStatus.OFFLINE.value(), null);
        // 删除关联弹窗
        ServerResponseEntity<Void> response = popupFeignClient.deleteLink(offlineHandleEvent.getHandleId(), offlineHandleEventDto.getShopId(), JumpType.COUPON.value(), null);
        if (!response.isSuccess()) {
            throw new LuckException(response.getMsg());
        }
    }

    @Override
    public OfflineHandleEventVO getOfflineHandleEvent(Long couponId) {
        ServerResponseEntity<OfflineHandleEventVO> offlineHandleEventResponse =
                offlineHandleEventFeignClient.getProcessingEventByHandleTypeAndHandleId(OfflineHandleEventType.COUPON.getValue(), couponId, null);
        return offlineHandleEventResponse.getData();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void audit(OfflineHandleEventDTO offlineHandleEventDto, Coupon coupon) {
        ServerResponseEntity<Void> responseEntity = offlineHandleEventFeignClient.auditOfflineEvent(offlineHandleEventDto);
        if (!Objects.equals(responseEntity.getCode(), ResponseEnum.OK.value())) {
            throw new LuckException(responseEntity.getMsg());
        }
        // 审核通过
        if (Objects.equals(offlineHandleEventDto.getStatus(), OfflineHandleEventStatus.AGREE_BY_PLATFORM.getValue())) {
            changeCouponStatusAndPutOnStatus(offlineHandleEventDto.getHandleId(), PutOnStatus.WAIT_PUT_ON.value(), null);
        }
        // 审核不通过
        else if (Objects.equals(offlineHandleEventDto.getStatus(), OfflineHandleEventStatus.DISAGREE_BY_PLATFORM.getValue())) {
            changeCouponStatusAndPutOnStatus(offlineHandleEventDto.getHandleId(), PutOnStatus.OFFLINE.value(), null);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void auditApply(OfflineHandleEventDTO offlineHandleEventDto) {
        // 更新事件状态
        ServerResponseEntity<Void> responseEntity = offlineHandleEventFeignClient.updateToApply(offlineHandleEventDto);
        if (!Objects.equals(responseEntity.getCode(), ResponseEnum.OK.value())) {
            throw new LuckException(responseEntity.getMsg());
        }
        // 更新优惠券为待审核状态
        changeCouponStatusAndPutOnStatus(offlineHandleEventDto.getHandleId(), PutOnStatus.WAIT_AUDIT.value(), null);
    }

    @Override
    public List<Coupon> getCouponListByCouponIdsAndPutOnStatus(List<Long> couponIds, Integer putOnStatus) {
        return couponMapper.getCouponListByCouponIds(couponIds, putOnStatus);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void cancelPut() {
        List<Coupon> coupons = couponMapper.cancelPutCouponIds();
        List<Long> couponIds = coupons.stream().map(Coupon::getCouponId).collect(Collectors.toList());
        List<Long> platformCouponIds = coupons.stream().filter(coupon -> Objects.equals(coupon.getShopId(), Constant.PLATFORM_SHOP_ID)).map(Coupon::getCouponId).collect(Collectors.toList());
        ServerResponseEntity<Void> responseEntity = userFeignClient.cancelBindingCoupons(platformCouponIds);
        if (!responseEntity.isSuccess()) {
            throw new LuckException(responseEntity.getMsg());
        }
        couponMapper.cancelPut();
        // 删除关联弹窗
        List<CouponDataVO> couponDataVOList = BeanUtil.mapAsList(coupons, CouponDataVO.class);
        ServerResponseEntity<Void> response = popupFeignClient.deleteLinkBatch(couponDataVOList);
        if (!response.isSuccess()) {
            throw new LuckException(response.getMsg());
        }
        // 优惠券缓存落库
        couponStockManager.couponStockPlacement(couponIds, true);
    }

    @Override
    public void putonCoupon() {
        couponMapper.putonCoupon();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void handleSpuOffline(List<Long> spuIds, List<Long> shopIds) {
        List<CouponVO> couponList = couponMapper.listCouponBySpuIds(spuIds, shopIds);
        if (CollUtil.isEmpty(couponList)) {
            return;
        }
        List<Long> couponIds = new ArrayList<>(Constant.INITIAL_CAPACITY);
        Set<Long> shopSet = new HashSet<>(Constant.INITIAL_CAPACITY);
        List<Long> offlineList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<CouponVO> offlineCouponList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        for (CouponVO couponVO : couponList) {
            couponIds.add(couponVO.getCouponId());
            shopSet.add(couponVO.getShopId());
            // 活动不是启动状态，或 适用商品类型为：全部商品，则不进行后面的处理
            if (!Objects.equals(couponVO.getPutonStatus(), PutOnStatus.PUT_ON.value())
                    || Objects.equals(SuitableProdType.ALL_SPU.value(), couponVO.getSuitableProdType())) {
                continue;
            }
            couponVO.getSpuIds().removeAll(spuIds);
            // 商品列表为空，则把活动添加到下线列表中
            if (CollUtil.isEmpty(couponVO.getSpuIds())) {
                offlineCouponList.add(couponVO);
                offlineList.add(couponVO.getCouponId());
            }
        }
        // 删除商品关联信息
        couponSpuService.deleteBySpuIds(spuIds);
        // 商品列表为空的活动修改为暂不投放
        if (CollUtil.isNotEmpty(offlineList)) {
            couponMapper.batchOfflineByDiscountIdsAndStatus(offlineList);
            // 删除关联弹窗
            List<CouponDataVO> couponDataVOList = BeanUtil.mapAsList(offlineCouponList, CouponDataVO.class);
            ServerResponseEntity<Void> response = popupFeignClient.deleteLinkBatch(couponDataVOList);
            if (!response.isSuccess()) {
                throw new LuckException(response.getMsg());
            }
        }
        // 清除缓存
        batchRemoveCacheByShopId(couponIds, shopSet);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleShopOffline(List<Long> shopIds) {
        if (CollUtil.isEmpty(shopIds)) {
            return;
        }
        List<Long> couponIds = couponMapper.listIdByShopIds(shopIds);
        if (CollUtil.isEmpty(couponIds)) {
            return;
        }
        // 删除商品关联信息
        couponSpuService.deleteBatchByCouponIds(couponIds);
        // 活动下线(设为过期，取消投放状态)
        couponMapper.batchOfflineByCouponIds(couponIds);
        // 库存落库
        couponStockManager.couponStockPlacement(couponIds, true);
        // 清除缓存
        batchRemoveCacheByShopId(couponIds, new HashSet<>(shopIds));
    }

    private static void batchRemoveCacheByShopId(List<Long> couponIds, Set<Long> shopIds) {
        if (CollUtil.isEmpty(couponIds) || CollUtil.isEmpty(shopIds)) {
            return;
        }
        List<String> keys = new ArrayList<>(Constant.INITIAL_CAPACITY);
        for (Long couponId : couponIds) {
            keys.add(CacheNames.COUPON_AND_SPU_DATA + CacheNames.UNION + couponId);

        }
        for (Long shopId : shopIds) {
            keys.add(CacheNames.COUPON_LIST_BY_SHOP_KEY + CacheNames.UNION + shopId);
        }
        RedisUtil.deleteBatch(keys);
    }

    /**
     * 根据店铺、spuId，获取优惠券中的spu列表
     *
     * @param spuUnionShopIds
     * @param spuIds
     * @param shopIds
     * @param coupons
     */
    private void loadSpuData(List<Long> spuUnionShopIds, List<Long> spuIds, List<Long> shopIds, List<CouponAppVO> coupons) {
        Map<Long, List<SpuSearchVO>> spuUnionShopMap = null;
        Map<Long, SpuSearchVO> spuMap = new HashMap<>(spuIds.size());
        if (CollUtil.isNotEmpty(spuUnionShopIds)) {
            ProductSearchLimitDTO productSearchLimitDTO = new ProductSearchLimitDTO(Constant.SIZE_OF_THREE);
            productSearchLimitDTO.setShopIds(spuUnionShopIds);
            ServerResponseEntity<List<SpuSearchVO>> spuResponse = searchSpuFeignClient.limitSpuList(productSearchLimitDTO);
            spuUnionShopMap = spuResponse.getData().stream().collect(Collectors.groupingBy(SpuSearchVO::getShopId));
        }
        if (CollUtil.isNotEmpty(spuIds)) {
            ServerResponseEntity<List<SpuSearchVO>> spuResponse = searchSpuFeignClient.listSpuBySpuIds(spuIds);
            spuMap.putAll(spuResponse.getData().stream().collect(Collectors.toMap(SpuSearchVO::getSpuId, spu -> spu)));
        }
        ServerResponseEntity<List<ShopDetailVO>> shopResponse = shopDetailFeignClient.listByShopIds(shopIds);
        Map<Long, ShopDetailVO> shopMap = shopResponse.getData().stream().collect(Collectors.toMap(ShopDetailVO::getShopId, shop -> shop));

        for (CouponAppVO coupon : coupons) {
            if (Objects.equals(coupon.getSuitableProdType(), SuitableProdType.ALL_SPU.value())) {
                if (spuUnionShopMap != null) {
                    coupon.setSpus(spuUnionShopMap.get(coupon.getShopId()));
                }
            } else {
                coupon.setSpus(new ArrayList<>(Constant.SIZE_OF_THREE));
                int index = 1;
                for (Long spuId : coupon.getSpuIds()) {
                    if (index > Constant.SIZE_OF_THREE) {
                        break;
                    }
                    index++;
                    SpuSearchVO spuSearchVO = spuMap.get(spuId);
                    if (Objects.nonNull(spuSearchVO)) {
                        coupon.getSpus().add(spuSearchVO);
                    }
                }
            }
            ShopDetailVO shopDetailVO = shopMap.get(coupon.getShopId());
            if (Objects.nonNull(shopDetailVO)) {
                coupon.setShopName(shopDetailVO.getShopName());
                coupon.setShopLogo(shopDetailVO.getShopLogo());
            }
        }
    }

    /**
     * 校验优惠券的数据
     */
    private static void checkCoupon(CouponDTO couponDTO) {
        if (Objects.equals(SuitableProdType.ASSIGN_SPU.value(), couponDTO.getSuitableProdType()) && CollUtil.isEmpty(couponDTO.getSpuIds())) {
            throw new LuckException("请选择商品");
        }
        if (couponDTO.getLimitNum() <= 0) {
            throw new LuckException("优惠券限领数量需大于0");
        }

        if (Objects.isNull(couponDTO.getCashCondition()) || couponDTO.getCashCondition() <= 0L) {
            throw new LuckException("使用条件不能为空，且大于0的整数");
        }
        if (Objects.equals(couponDTO.getCouponType(), CouponType.C2M.value())) {
            if (Objects.isNull(couponDTO.getReduceAmount()) || couponDTO.getReduceAmount() <= 0L) {
                throw new LuckException("减免金额不能为空，且大于0的整数");
            }
            if (couponDTO.getCashCondition() <= couponDTO.getReduceAmount()) {
                throw new LuckException("减免金额必须小于使用条件");
            }
        } else if (Objects.equals(couponDTO.getCouponType(), CouponType.C2D.value())) {
            if (Objects.isNull(couponDTO.getCouponDiscount()) || couponDTO.getCouponDiscount() < CouponConstant.MIN_COUPON_DISCOUNT
                    || couponDTO.getCouponDiscount() > CouponConstant.MAX_COUPON_DISCOUNT) {
                throw new LuckException("折扣额度不能为空，且大于等于" + CouponConstant.MIN_COUPON_DISCOUNT + "小于等于" + CouponConstant.MAX_COUPON_DISCOUNT);
            }
        }

        if (Objects.equals(couponDTO.getPutonStatus(), PutOnStatus.AUTO_LAUNCH.value()) && Objects.isNull(couponDTO.getLaunchTime())) {
            throw new LuckException("优惠券投放时间不能为空");
        }
        // 校验投放时间
        Date nowTime = new Date();
        List<Integer> unCheckPutOnStatus = Arrays.asList(
                PutOnStatus.PUT_ON.value(),
                PutOnStatus.WAIT_PUT_ON.value(),
                PutOnStatus.OFFLINE.value()
        );
        if (Objects.nonNull(couponDTO.getLaunchTime())
                && couponDTO.getLaunchTime().getTime() < nowTime.getTime()
                && !unCheckPutOnStatus.contains(couponDTO.getPutonStatus())) {
            throw new LuckException("投放时间需大于当前时间");
        }

        boolean dateIsNull = Objects.isNull(couponDTO.getAfterReceiveDays()) || Objects.isNull(couponDTO.getValidDays());
        if (Objects.equals(couponDTO.getValidTimeType(), ValidTimeType.RECEIVE.value()) && dateIsNull) {
            throw new LuckException("请输入时间范围");
        } else if (Objects.equals(couponDTO.getValidTimeType(), ValidTimeType.FIXED.value())) {
            if (Objects.isNull(couponDTO.getStartTime()) || Objects.isNull(couponDTO.getEndTime())) {
                throw new LuckException("请输入时间范围");
            }
            if (couponDTO.getStartTime().getTime() > couponDTO.getEndTime().getTime()) {
                throw new LuckException("开始时间需大于结束时间");
            }
        }
    }

    /**
     * 清除优惠券关联的商品缓存
     */
    private void removeSpuCache(Long shopId, List<Long> spuIds) {
        spuFeignClient.removeSpuActivityCache(shopId, spuIds);
    }
}
