package com.yc.ship.module.marketing.service.coupon;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import com.yc.ship.framework.common.enums.CommonStatusEnum;
import com.yc.ship.framework.common.pojo.PageResult;
import com.yc.ship.framework.common.util.collection.CollectionUtils;
import com.yc.ship.framework.common.util.object.BeanUtils;
import com.yc.ship.module.infra.api.file.FileApi;
import com.yc.ship.module.marketing.controller.admin.coupon.vo.CouponPageReqVO;
import com.yc.ship.module.marketing.controller.admin.coupon.vo.CouponSaveReqVO;
import com.yc.ship.module.marketing.controller.app.coupon.vo.AppCouponCanUseReqVo;
import com.yc.ship.module.marketing.controller.app.params.vo.AppParamsSaveVo;
import com.yc.ship.module.marketing.dal.dataobject.coupon.CouponDO;
import com.yc.ship.module.marketing.dal.dataobject.couponCode.CouponCodeDO;
import com.yc.ship.module.marketing.dal.dataobject.couponPorduct.CouponProductDo;
import com.yc.ship.module.marketing.dal.dataobject.couponpackagecode.CouponPackageCodeDO;
import com.yc.ship.module.marketing.dal.mysql.coupon.CouponMapper;
import com.yc.ship.module.marketing.dal.mysql.couponCode.CouponCodeMapper;
import com.yc.ship.module.marketing.dal.mysql.couponpackagecode.CouponPackageCodeMapper;
import com.yc.ship.module.marketing.enums.coupon.*;
import com.yc.ship.module.marketing.service.couponProduct.CouponProductService;
import com.yc.ship.module.marketing.service.params.MarketingParamsService;
import com.yc.ship.module.member.api.user.MemberUserApi;
import com.yc.ship.module.member.api.user.dto.MemberUserRespDTO;
import com.yc.ship.module.system.api.social.SocialClientApi;
import com.yc.ship.module.system.api.social.dto.SocialWxQrcodeReqDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.yc.ship.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.yc.ship.module.marketing.enums.ErrorCodeConstants.*;
import static com.yc.ship.module.member.enums.ErrorCodeConstants.AUTH_NOT_LOGIN;


/**
 * 优惠券 Service 实现类
 *
 * @author lfy
 */
@Service
@Validated
@Slf4j
public class CouponServiceImpl implements CouponService {

    @Resource
    private CouponMapper couponMapper;

    @Resource
    private MemberUserApi memberUserApi;

    @Resource
    private CouponCodeMapper couponCodeMapper;

    //TODO:待完善
//    @Resource
//    private ProductApi productApi;

//    @Resource
//    private ChildProductApi childProductApi;

    @Resource
    private FileApi fileApi;

    @Resource
    private SocialClientApi socialClientApi;

    @Resource
    private MarketingParamsService paramsService;

    @Resource
    private CouponPackageCodeMapper couponPackageCodeMapper;

    @Resource
    private CouponProductService couponProductService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createCoupon(CouponSaveReqVO createReqVO) {
        // 插入主表
        CouponDO coupon = BeanUtils.toBean(createReqVO, CouponDO.class);
        coupon.setAuditState(CouponAuditStateEnum.AUDITING.getType());
        long id = IdUtil.getSnowflakeNextId();
        coupon.setId(id);
        String mpQrCode = createMpQrCode(id, "pages/mine/coupon/index");
        coupon.setQrCode(mpQrCode);
        couponMapper.insert(coupon);
        // 返回
        return coupon.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCoupon(CouponSaveReqVO updateReqVO) {
        // 校验存在
        validateCouponExists(updateReqVO.getId());
        // 更新
        CouponDO updateObj = BeanUtils.toBean(updateReqVO, CouponDO.class);
        updateObj.setAuditState(CouponAuditStateEnum.AUDITING.getType());
        couponMapper.updateById(updateObj);
    }

    @Override
    public void deleteCoupon(Long id) {
        // 校验存在
        validateCouponExists(id);
        // 删除
        couponMapper.deleteById(id);
    }

    private void validateCouponExists(Long id) {
        if (couponMapper.selectById(id) == null) {
            throw exception(COUPON_NOT_EXISTS);
        }
    }

    @Override
    public CouponDO getCoupon(Long id) {
        return couponMapper.selectById(id);
    }

    @Override
    public PageResult<CouponDO> getCouponPage(CouponPageReqVO pageReqVO) {
        return couponMapper.selectPage(pageReqVO);
    }

    @Override
    public List<CouponDO> getCouponList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return ListUtil.empty();
        }
        return couponMapper.selectBatchIds(ids);
    }

    @Override
    public List<CouponDO> getCanReceiveCouponList(Long memberId) {
        if (Objects.isNull(memberId)) {
            throw exception(AUTH_NOT_LOGIN);
        }
        MemberUserRespDTO memberUserRespDTO = memberUserApi.getUser(memberId);
        Long groupId = memberUserRespDTO.getGroupId();
        LocalDateTime createTime = memberUserRespDTO.getCreateTime();
        Boolean isNewUser = createTime.plusDays(-7).isBefore(LocalDateTime.now());
        return couponMapper.selectCanReceiveCouponList(groupId, isNewUser);
    }

    @Override
    public List<CouponDO> getReceivedCouponList(Long memberId) {
        if (Objects.isNull(memberId)) {
            throw exception(AUTH_NOT_LOGIN);
        }
        List<CouponDO> couponList = couponMapper.selectReceivedCouponList(memberId);
        handleEffectiveTime(couponList);
        return couponList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long receiveCoupon(Long memberId, Long couponId) {
        if (Objects.isNull(memberId)) {
            throw exception(AUTH_NOT_LOGIN);
        }
        MemberUserRespDTO memberUserRespDTO = memberUserApi.getUser(memberId);
        Long groupId = memberUserRespDTO.getGroupId();
        LocalDateTime createTime = memberUserRespDTO.getCreateTime();

        //1.检查优惠券状态
        boolean isPackageCode = false; //是否是优惠券包
        Integer surplusTimes = 0; //优惠券包二维码剩余领取次数
        CouponDO couponDO = couponMapper.selectById(couponId);
        if(couponDO == null) { //如果优惠券为null，再去检查是否是券包
            CouponPackageCodeDO couponPackageCode = couponPackageCodeMapper.selectById(couponId);
            if(couponPackageCode == null)  {
                throw exception(COUPON_NOT_EXISTS);
            }else {
                couponDO = couponMapper.selectById(couponPackageCode.getCouponId());
                surplusTimes = couponPackageCode.getSurplusTimes();
                isPackageCode = true;
            }

        }

        if (!CouponAuditStateEnum.AUDIT_PASS.getType().equals(couponDO.getAuditState())) {
            throw exception(COUPON_NOT_AUDIT_PASS);
        }
        if(!CommonStatusEnum.ENABLE.getStatus().equals(couponDO.getState())) {
            throw exception(COUPON_NOT_ENABLE);
        }
        //2.检查派发规则
        if (couponDO.getDeliverUserGroupType() == CouponDeliverUserGroupType.NEW_USER.getType()) {//新会员
            Boolean isNewUser = createTime.plusDays(-7).isBefore(LocalDateTime.now());
            if(!isNewUser) {
                throw exception(COUPON_IS_NEW_USER);
            }
        } else if (couponDO.getDeliverUserGroupType() == CouponDeliverUserGroupType.SOME_USER.getType()){//指定用户组
            String deliverUserGroup = couponDO.getDeliverUserGroup();
            String[] split = deliverUserGroup.split(",");
            if(!Arrays.asList(split).contains(String.valueOf(groupId))) {
                throw exception(COUPON_IN_GROUP);
            }
        }
        //检查派发时间
        if (couponDO.getDeliverStart().isAfter(LocalDateTime.now()) || couponDO.getDeliverEnd().isBefore(LocalDateTime.now())) {
            throw exception(COUPON_NOT_IN_DELIVER_TIME);
        }
        //检查最大派发数量少于已领取数量
        Long receivedCount = couponCodeMapper.selectCountByCouponId(couponId);
        if(couponDO.getNumber()<=receivedCount) {
            throw exception(COUPON_IS_DELIVERED_OVER);
        }
        //检查今日领取数量
        Long todayReceivedCount = couponCodeMapper.selectCountToday(couponId);
        if(couponDO.getSingleDayMax()<=todayReceivedCount) {
            throw exception(COUPON_IS_DELIVERED_DAY_OVER);
        }
        //检查个人领取上限
        Long receivedCountByMember = couponCodeMapper.selectCountByMemberIdAndCouponId(memberId, couponId);
        if(couponDO.getSingleUserMax()<=receivedCountByMember) {
            throw exception(COUPON_IS_DELIVERED_USER_OVER);
        }
        //3.判断是否是优惠券包, 是则判断剩余次数是否为>0
        if(isPackageCode) {
            if (surplusTimes <= 0) {
                throw exception(COUPON_PACKAGE_OVER_TIMES);
            }else {
                surplusTimes = surplusTimes - 1;
                CouponPackageCodeDO couponPackageCodeDO = new CouponPackageCodeDO();
                couponPackageCodeDO.setId(couponId);
                couponPackageCodeDO.setSurplusTimes(surplusTimes);
                couponPackageCodeMapper.updateById(couponPackageCodeDO);
            }
        }
        //4.发放券
        String code = getNewCode();
        CouponCodeDO couponCodeDO = new CouponCodeDO();
        couponCodeDO.setCouponId(couponDO.getId()).setMemberId(memberId).setState(CouponCodeUseStateEnum.UNUSED.getType())
                .setCode(code);
        couponCodeMapper.insert(couponCodeDO);
        return couponCodeDO.getId();
    }

    @Override
    public List<CouponDO> getCanUseCouponList(Long memberId, AppCouponCanUseReqVo reqVo) {
        Long type = null;
//        //小程序子产品
//        ChildProductDTO childProduct = childProductApi.getChildProduct(reqVo.getSpuId());
//        if (childProduct != null) {
//            //小程序的产品
//            CategoryExtraRespDTO extraRespDTO = productApi.getAppProductDetail(childProduct.getCategoryId());
//            if(extraRespDTO != null) {
//                //小程序的产品分类
//                type = extraRespDTO.getType();
//            }
//        }
        List<CouponDO> couponList = couponMapper.selectCanUseCouponList(memberId, reqVo.getTravelDate());
        handleEffectiveTime(couponList);
        Long finalType = type;
        List<CouponDO> list = couponList.stream().filter(couponDO -> {
            BigDecimal basic = couponDO.getBasic();
            Boolean flag = basic.compareTo(reqVo.getOrderAmount()) <= 0;
            //满足门槛金额
            if (flag) {
                if (couponDO.getRelationType().equals(CouponProductTypeEnum.ALL.getType())) {
                    return true;
                } else if (couponDO.getRelationType().equals(CouponProductTypeEnum.CATEGORY.getType())) {
                    if(finalType == null) {
                        return false;
                    }
                    return finalType.equals(couponDO.getRelationId());
                } else if (couponDO.getRelationType().equals(CouponProductTypeEnum.CUSTOM.getType())) {
                    boolean flag1 = couponDO.getRelationChildId().equals(reqVo.getSpuId());
                    if (flag1) {
                        List<String> list1 = Arrays.asList(couponDO.getRelationSkuId().split(","));
                        List<String> list2 = Arrays.asList(reqVo.getSkuIds().split(","));
                        return list1.stream().anyMatch(list2::contains);
                    }
                }
            }
            return false;
        }).collect(Collectors.toList());
        handelQuota(list, reqVo.getOrderAmount());
        return list;
    }

    @Override
    public List<CouponDO> getMyUnUseCouponList(Long loginUserId) {
        List<CouponDO> couponList = couponMapper.selectMyUnUseCouponList(loginUserId);
        handleEffectiveTime(couponList);
        return couponList;
    }

    @Override
    public List<CouponDO> getMyUsedCouponList(Long loginUserId) {
        List<CouponDO> couponList = couponMapper.selectMyUsedCouponList(loginUserId);
        handleEffectiveTime(couponList);
        return couponList;
    }

    @Override
    public List<CouponDO> getMyExpiredCouponList(Long loginUserId) {
        List<CouponDO> couponList = couponMapper.selectMyExpiredCouponList(loginUserId);
        handleEffectiveTime(couponList);
        return couponList;
    }

    @Override
    public CouponDO getByCouponCodeId(Long couponCodeId) {
        return couponMapper.selectByCouponCodeId(couponCodeId);
    }

    @Override
    public List<CouponDO> getCanReceiveCouponListBySpuId(Long memberId, Long spuId) {
        List<CouponDO> list = this.getCanReceiveCouponList(memberId);
        List<Long> couponIds = CollectionUtils.convertList(list, CouponDO::getId);
        List<CouponProductDo> couponProductList = couponProductService.getListByCouponIds(couponIds);
        Map<Long, List<CouponProductDo>> longListMap = CollectionUtils.convertMultiMap(couponProductList, CouponProductDo::getCouponId);
        Long type = null;
//        //小程序子产品
//        ChildProductDTO childProduct = childProductApi.getChildProduct(spuId);
//        if (childProduct != null) {
//            //小程序的产品
//            CategoryExtraRespDTO extraRespDTO = productApi.getAppProductDetail(childProduct.getCategoryId());
//            if(extraRespDTO != null) {
//                //小程序的产品分类
//                type = extraRespDTO.getType();
//            }
//        }
        Long finalType = type;
        List<CouponDO> result = list.stream().filter(couponDO -> {
            List<CouponProductDo> couponProductDoList = longListMap.get(couponDO.getId());
            List<CouponProductDo> filterList = couponProductDoList.stream().filter(couponProductDo -> {
                Integer relationType = couponProductDo.getType();
                if (relationType.equals(CouponProductTypeEnum.ALL.getType())) {
                    return true;
                } else if (relationType.equals(CouponProductTypeEnum.CATEGORY.getType())) {
                    if(finalType == null) {
                        return false;
                    }
                    return finalType.equals(couponProductDo.getRelationId()) || finalType.equals(couponProductDo.getRelationId());
                } else if (relationType.equals(CouponProductTypeEnum.CUSTOM.getType())) {
                    return couponProductDo.getRelationChildId().equals(spuId) || couponProductDo.getRelationChildId().equals(spuId);
                }
                return false;
            }).collect(Collectors.toList());
            if(filterList.size() > 0) {
                return true;
            }else {
                return false;
            }
        }).collect(Collectors.toList());
        return result;
    }

    /**
     * 生成优惠券码
     * @return
     */
    private String getNewCode() {
        String dateStr = DateUtil.format(LocalDateTime.now(), "yyyyMMdd");
        int esix = (int) ((Math.random()*9+1)*100000);
        String newCode = dateStr + esix;
        //验证码值是否存在
        boolean isCheck = true;
        do{
            CouponCodeDO couponCodeDO = couponCodeMapper.selectByCode(newCode);
            if(couponCodeDO==null){
                isCheck = false;
            }else{
                int random = (int)((Math.random()*9+1)*100000);
                newCode = dateStr + random;
            }
        }while (isCheck);
        return newCode;
    }

    /**
     * 处理优惠券的有效期
     * 当有效期类型是相对时间时，将有效期开始时间设置为领取时间，将结束时间设置为领取时间+有效分钟数
     * @param couponDOList
     */
    private void handleEffectiveTime(List<CouponDO> couponDOList) {
        couponDOList.stream().forEach(couponDO -> {
            if(couponDO.getEffectiveType()== CouponEffectiveTypeEnum.RELATIVE_TIME.getType()){
                couponDO.setEffectiveStart(couponDO.getGetTime());
                couponDO.setEffectiveEnd(couponDO.getGetTime().plusMinutes(couponDO.getEffectiveTime()));
            }
        });
    }

    /**
     * 处理优惠金额
     * @param couponDOList
     * @param orderAmount
     */
    private void handelQuota(List<CouponDO> couponDOList, BigDecimal orderAmount) {
        couponDOList.stream().forEach(couponDO -> {
            //TODO: 暂时不考虑多券同用的场景
            //优惠券类型
            Integer type = couponDO.getType();
            //最大减免
            BigDecimal maximum = couponDO.getMaximum();
            //满足金额
            BigDecimal basic = couponDO.getBasic();
            //优惠券额度
            BigDecimal quota = couponDO.getQuota();
            if(type == CouponTypeEnum.COUPON_TYPE_CASH.getType()){
                couponDO.setAmount(quota);
            }else if(type == CouponTypeEnum.COUPON_TYPE_REDUCTION.getType()){
                if(basic == null || basic.compareTo(BigDecimal.ZERO) == 0) {
                    couponDO.setAmount(maximum);
                }else{
                    //判断可以满减多少次
                    BigDecimal count = orderAmount.divide(basic, 2, RoundingMode.DOWN);
                    int i = count.intValue();
                    BigDecimal multiply = quota.multiply(BigDecimal.valueOf(i));
                    if(multiply.compareTo(maximum)>=0) {
                        couponDO.setAmount(maximum);
                    }else {
                        couponDO.setAmount(multiply);
                    }
                }
            }else if(type == CouponTypeEnum.COUPON_TYPE_DISCOUNT.getType()){
                BigDecimal oneHundred = new BigDecimal(100);
                BigDecimal discount = oneHundred.subtract(quota);
                discount = discount.divide(oneHundred, 2, RoundingMode.DOWN);
                BigDecimal multiply = orderAmount.multiply(discount);
                couponDO.setAmount(multiply);
            }
        });
        couponDOList.sort(Comparator.comparing(CouponDO::getAmount).reversed());
    }


    /**
     * 生成线下优惠券小程序码
     * @param id
     * @return
     */
    private String createMpQrCode(Long id, String path) {
        JSONObject json = new JSONObject();
        //c 表示优惠券id
        json.set("c", id);
        AppParamsSaveVo params = new AppParamsSaveVo();
        params.setParams(json.toString());
        Long paramsId = paramsService.createParams(params);
        SocialWxQrcodeReqDTO reqDTO = new SocialWxQrcodeReqDTO();
        reqDTO.setScene("q="+paramsId);
        reqDTO.setPath(path);
        reqDTO.setAutoColor(true);
        reqDTO.setHyaline(false);
        reqDTO.setWidth(480);
        reqDTO.setCheckPath(false);
        byte[] imageBytes = socialClientApi.getWxaQrcode(reqDTO);
        String file = fileApi.createFile(imageBytes);
        return file;
    }

}