package com.liquidnet.service.platform.service.impl.candy;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liquidnet.common.cache.redis.util.AbstractRedisUtil;
import com.liquidnet.common.cache.redis.util.RedisDataSourceUtil;
import com.liquidnet.common.exception.LiquidnetServiceException;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.adam.constant.AdamRedisConst;
import com.liquidnet.service.adam.dto.AdamUserInfoDto;
import com.liquidnet.service.adam.dto.vo.AdamUserMemberVo;
import com.liquidnet.service.adam.entity.AdamUserMember;
import com.liquidnet.service.adam.mapper.AdamUserMapper;
import com.liquidnet.service.candy.constant.CandyRedisConst;
import com.liquidnet.service.candy.dto.*;
import com.liquidnet.service.candy.entity.*;
import com.liquidnet.service.candy.mapper.CandyCouponMapper;
import com.liquidnet.service.goblin.constant.GoblinRedisConst;
import com.liquidnet.service.goblin.dto.vo.GoblinOrderSkuVo;
import com.liquidnet.service.platform.service.impl.adam.PlatformAdamUserMemberService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class PlatformCandyCouponService extends ServiceImpl<CandyCouponMapper, CandyCoupon> {
    @Autowired
    private PlatformCandyCouponRuleService platformCandyCouponRuleService;
    @Autowired
    private PlatformCandyCouponCodeService platformCandyCouponCodeService;
    @Autowired
    private PlatformCandyUserCouponService platformCandyUserCouponService;
    @Autowired
    private PlatformCandyUserCouponAssocService platformCandyUserCouponAssocService;
    @Autowired
    private PlatformCandyCommonCouponService platformCandyCommonCouponService;
    @Autowired
    private PlatformAdamUserMemberService platformAdamUserMemberService;
    @Autowired
    private RedisDataSourceUtil redisDataSourceUtil;
    @Autowired
    private AdamUserMapper adamUserMapper;

    private void issueCouponProcessing(final CandyMgtCoupon mgtCoupon, final CandyCoupon coupon) {
        List<CandyCouponRuleDto> couponRuleDtoList = platformCandyCouponRuleService.listForCouponRuleDto(coupon.getCouponId());

        // 后台创建券时，同步至REDIS，这里不再同步
//        CandyCouponInfoDto couponInfoDto = new CandyCouponInfoDto();
//        BeanUtils.copyProperties(coupon, couponInfoDto);
//        couponInfoDto.setUseRules(couponRuleDtoList);
//
//        String couponInfoDtoKey = CandyRedisConst.BASIC_COUPON_INFO.concat(couponInfoDto.getCouponId());
//        redisDataSourceUtil.getRedisCandyUtil().set(couponInfoDtoKey, couponInfoDto);

        switch (coupon.getBindType()) {// 领取方式[0-用户输入兑换｜1-发放至用户｜2-积分商品｜3-发放需领取｜4-会员专属]
            case 0:
                this.processingCouponCode(mgtCoupon, coupon);
                break;
            case 1:
//                switch (mgtCoupon.getEventType()) {// 发放类型[1-会员｜2-手机号|10-全体用户]
//                    case 1:
//                        this.processForAllMember(mgtCoupon, coupon, couponRuleDtoList);
//                        break;
//                    case 2:
//                        this.processForUserCoupon(mgtCoupon, coupon, couponRuleDtoList);
//                        break;
//                    case 10:
//                        this.processForCommonCoupon(mgtCoupon, coupon, couponRuleDtoList);
//                        break;
//                    default:
//                        log.warn("发放券任务:无法处理，无效的发放类型[mcouponId:{},eventType:{}]", mgtCoupon.getMcouponId(), mgtCoupon.getEventType());
//                        break;
//                }
//                break;
            case 3:// 新增券领取类型（3-发放需领取）处理逻辑同（1-发放至用户｜2-积分商品）
            case 4:// 新增券领取类型（4-会员专属）处理逻辑同（1-发放至用户｜2-积分商品）
            case 2:// TODO: 2021/11/9 PM上线前临时加的券领取类型（2-积分商品），这里`1-发放至用户｜2-积分商品`做同逻辑处理
                switch (mgtCoupon.getEventType()) {// 发放类型[1-会员｜2-手机号|10-全体用户]
                    case 1:
                        this.processForAllMember(mgtCoupon, coupon, couponRuleDtoList);
                        break;
                    case 2:
                        this.processForUserCoupon(mgtCoupon, coupon, couponRuleDtoList);
                        break;
                    case 10:
                        this.processForCommonCoupon(mgtCoupon, coupon, couponRuleDtoList);
                        break;
                    default:
                        log.warn("发放券任务:无法处理，无效的发放类型[mcouponId:{},eventType:{}]", mgtCoupon.getMcouponId(), mgtCoupon.getEventType());
                        break;
                }
                break;
            default:
                log.warn("发放券任务:无法处理，无效的领取方式[mcouponId:{},bindType:{}]", mgtCoupon.getMcouponId(), coupon.getBindType());
                break;
        }
    }

    /**
     * <p>
     * 券码生成处理
     * </p>
     *
     * @param mgtCoupon CandyMgtCoupon
     * @param coupon    CandyCoupon
     */
    private void processingCouponCode(CandyMgtCoupon mgtCoupon, CandyCoupon coupon) {
        List<CandyCouponCode> initCouponCodeList = new ArrayList<>();
        LocalDateTime now = LocalDateTime.now();
        Integer eventAmt = mgtCoupon.getEventAmt();
        Base64.Encoder encoder = Base64.getEncoder();
        for (int i = 0; i < eventAmt; i++) {
            CandyCouponCode couponCode = new CandyCouponCode();

            String lStrEncoder = encoder.encodeToString((System.nanoTime() + RandomStringUtils.randomNumeric(3)).getBytes(StandardCharsets.UTF_8));

            couponCode.setCcode(lStrEncoder.replace("=", ""));
            couponCode.setCouponId(coupon.getCouponId());
            couponCode.setState(0);
            couponCode.setCreatedAt(now);
            couponCode.setRedeemStart(coupon.getRedeemStart());
            couponCode.setRedeemStop(coupon.getRedeemStop());

            initCouponCodeList.add(couponCode);
        }

        if (!initCouponCodeList.isEmpty()) {
            if (platformCandyCouponCodeService.saveBatch(initCouponCodeList)) {
                AbstractRedisUtil redisCandyUtil = redisDataSourceUtil.getRedisCandyUtil();
                initCouponCodeList.forEach(r -> {
                    redisCandyUtil.set(CandyRedisConst.BASIC_COUPON_CODE.concat(r.getCcode()), CandyCouponCodeDto.getNew().copy(r));
                });

                initCouponCodeList.clear();
            } else {
                throw new LiquidnetServiceException("-1", String.format("券发放失败[mcouponId=%s]", mgtCoupon.getMcouponId()));
            }
        }
    }

    /**
     * <p>
     * 发放处理-公有券
     * </p>
     *
     * @param mgtCoupon         CandyMgtCoupon
     * @param coupon            CandyCoupon
     * @param couponRuleDtoList List<CandyCouponRuleDto>
     */
    private void processForCommonCoupon(CandyMgtCoupon mgtCoupon, CandyCoupon coupon, List<CandyCouponRuleDto> couponRuleDtoList) {
        List<CandyCommonCoupon> initCommonCouponList = new ArrayList<>();
        LocalDateTime now = LocalDateTime.now();

        String mcouponId = mgtCoupon.getMcouponId();
        Integer eventAmt = mgtCoupon.getEventAmt();
        log.info("发放券处理中:{}-目标全体用户[发放量:{}]", mcouponId, eventAmt);

        for (int i = 0; i < eventAmt; i++) {
            CandyCommonCoupon commonCoupon = new CandyCommonCoupon();
            commonCoupon.setCcouponId(IDGenerator.nextTimeId2());
            commonCoupon.setMcouponId(mcouponId);
            commonCoupon.setCouponId(coupon.getCouponId());
            commonCoupon.setExclusive(mgtCoupon.getExclusively());
            commonCoupon.setState(1);
            commonCoupon.setRanged(mgtCoupon.getEventType());
            commonCoupon.setOperator(mgtCoupon.getOperator());
            commonCoupon.setCreatedAt(now);

            initCommonCouponList.add(commonCoupon);
        }

        if (!initCommonCouponList.isEmpty()) {
            if (platformCandyCommonCouponService.saveBatch(initCommonCouponList, initCommonCouponList.size())) {
                AbstractRedisUtil redisCandyUtil = redisDataSourceUtil.getRedisCandyUtil();
                initCommonCouponList.forEach(r -> {
                    String ccKey = CandyRedisConst.BASIC_COMMON_COUPON;

                    List<CandyCommonCouponBasicDto> vos = (List<CandyCommonCouponBasicDto>) redisCandyUtil.get(ccKey);
                    if (CollectionUtils.isEmpty(vos)) {
                        vos = new ArrayList<>();
                    }
                    vos.add(CandyCommonCouponBasicDto.getNew().copy(r, coupon, couponRuleDtoList));

                    redisCandyUtil.set(ccKey, vos);
                });
            } else {
                throw new LiquidnetServiceException("-1", String.format("券发放失败[mcouponId=%s]", mcouponId));
            }
        }
        log.info("发放券处理中:{}-目标全体用户[发放量:{},实际发放:{}]", mcouponId, eventAmt, initCommonCouponList.size());
    }

    /**
     * <p>
     * 发放处理-全体会员
     * </p>
     *
     * @param mgtCoupon         CandyMgtCoupon
     * @param coupon            CandyCoupon
     * @param couponRuleDtoList List<CandyCouponRuleDto>
     */
    private void processForAllMember(CandyMgtCoupon mgtCoupon, CandyCoupon coupon, List<CandyCouponRuleDto> couponRuleDtoList) {
        List<CandyUserCoupon> initUserCouponList = new ArrayList<>();
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime nowEndTime = now.withHour(23).withMinute(59).withSecond(58);

        LambdaQueryWrapper<AdamUserMember> queryWrapper = Wrappers.lambdaQuery(AdamUserMember.class).eq(AdamUserMember::getState, 1)
                .gt(AdamUserMember::getExpiryAt, nowEndTime);
        int userMemberCount = platformAdamUserMemberService.count(queryWrapper), num = 0, pSize = 1000;
        int totalCount = userMemberCount, eventAmt = mgtCoupon.getEventAmt();
        log.info("发放券处理中:{}-目标全体会员[总数:{},发放量:{}]", mgtCoupon.getMcouponId(), userMemberCount, eventAmt);

        String mcouponId = mgtCoupon.getMcouponId();
        String operator = mgtCoupon.getOperator();
        String couponId = coupon.getCouponId();
        LocalDateTime couponEffectAt = coupon.getEffectAt();
        LocalDateTime couponExpireAt = coupon.getExpireAt();

        // 会员权益券失效时间duedAt需要同步会员到期时间expiryAt
        Integer exclusively = mgtCoupon.getExclusively();
        boolean isMemberRightsCoupon = exclusively == 1;
        queryWrapper.select(AdamUserMember::getUid, AdamUserMember::getExpiryAt).orderByAsc(AdamUserMember::getMid);
        while (totalCount > 0) {
            initUserCouponList.clear();

            LambdaQueryWrapper<AdamUserMember> userMemberLambdaQueryWrapper = queryWrapper.clone();
            String lastLimitSql = "LIMIT " + (num * pSize) + "," + pSize;
            userMemberLambdaQueryWrapper.last(lastLimitSql);
            List<AdamUserMember> userMemberList = platformAdamUserMemberService.list(userMemberLambdaQueryWrapper);
            int userMemberListSize = CollectionUtils.isEmpty(userMemberList) ? -1 : userMemberList.size();

            for (int j = 0; j < eventAmt; j++) {
                for (int i = 0; i < userMemberListSize; i++) {
                    AdamUserMember userMember = userMemberList.get(i);

                    CandyUserCoupon userCoupon = new CandyUserCoupon();
                    userCoupon.setUcouponId(IDGenerator.get32UUID());
                    userCoupon.setMcouponId(mcouponId);
                    userCoupon.setUid(userMember.getUid());
                    userCoupon.setCouponId(couponId);
                    userCoupon.setExclusive(exclusively);
                    userCoupon.setState(1);
//                    userCoupon.setBindAt(couponEffectAt);
//                    userCoupon.setDuedAt(couponExpireAt);
                    userCoupon.setBindAt(null == couponEffectAt ? now : couponEffectAt);
                    if (isMemberRightsCoupon) {
                        userCoupon.setDuedAt(userMember.getExpiryAt());
                    } else if (null != couponExpireAt && couponExpireAt.isAfter(now)) {
                        userCoupon.setDuedAt(couponExpireAt);
                    } else {
                        log.warn("发放券处理中:{}-目标全体会员，该券生效日期或失效日期无效[couponId:{},effectAt:{},expireAt:{}]", mcouponId, couponId, couponEffectAt, couponExpireAt);
                        continue;
                    }
                    userCoupon.setOperator(operator);
                    userCoupon.setCreatedAt(now);

                    initUserCouponList.add(userCoupon);
                }
            }

            if (!initUserCouponList.isEmpty()) {
                if (platformCandyUserCouponService.saveBatch(initUserCouponList)) {
                    AbstractRedisUtil redisCandyUtil = redisDataSourceUtil.getRedisCandyUtil();
                    initUserCouponList.forEach(r -> {
                        String uckey = CandyRedisConst.BASIC_USER_COUPON.concat(r.getUid());

                        List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisCandyUtil.get(uckey);

                        if (CollectionUtils.isEmpty(vos)) {
                            vos = new ArrayList<>();
                        }
                        vos.add(CandyUserCouponBasicDto.getNew().copy(r, coupon, couponRuleDtoList));

                        redisCandyUtil.set(uckey, vos);
                    });
                } else {
                    throw new LiquidnetServiceException("-1", "券发放失败");
                }
            }

            num++;
            totalCount -= pSize;
            log.info("发放券处理中:{}-目标全体会员[总数:{},发放量:{},单次处理:{},剩余:{}]", mcouponId, userMemberCount, eventAmt, lastLimitSql, totalCount);
        }
    }

    /**
     * <p>
     * 发放处理-私有券（指定手机号）
     * </p>
     *
     * @param mgtCoupon         CandyMgtCoupon
     * @param coupon            CandyCoupon
     * @param couponRuleDtoList List<CandyCouponRuleDto>
     */
    private void processForUserCoupon(CandyMgtCoupon mgtCoupon, CandyCoupon coupon, List<CandyCouponRuleDto> couponRuleDtoList) {
        LocalDateTime now = LocalDateTime.now();

        String eventLimit = mgtCoupon.getEventLimit();
        String[] eventLimitArr = eventLimit.split(",");
        int eventAmt = mgtCoupon.getEventAmt(), pl = 0, limitSize = eventLimitArr.length;
        log.info("发放券处理中:{}-目标指定手机号[总数:{},发放量:{}]", mgtCoupon.getMcouponId(), limitSize, eventAmt);

        AbstractRedisUtil redisAdamUtil = redisDataSourceUtil.getRedisAdamUtil();
        AbstractRedisUtil redisGoblinUtil = redisDataSourceUtil.getRedisGoblinUtil();

        if (coupon.getBindType() == 2) {// 券领取方式[2-积分商品]，实际发放至用户时计算有效期
            coupon.setEffectAt(now);
            coupon.setExpireAt(coupon.getEffectAt().plusDays(coupon.getValidity()).withHour(23).withMinute(59).withSecond(59));
        }

        String mcouponId = mgtCoupon.getMcouponId();
        String operator = mgtCoupon.getOperator();
        String couponId = coupon.getCouponId();
        LocalDateTime couponEffectAt = coupon.getEffectAt();
        LocalDateTime couponExpireAt = coupon.getExpireAt();

        // 指定手机号发放券中，会员权益券失效时间duedAt需要同步会员到期时间expiryAt
        Integer exclusively = mgtCoupon.getExclusively();
        boolean isMemberRightsCoupon = exclusively == 1;

        List<CandyUserCoupon> initUserCouponList = new ArrayList<>();
        List<CandyUserCouponAssoc> initUserCouponAssocList = new ArrayList<>();
        for (int i = 0; i < eventAmt; i++) {
            for (String r : eventLimitArr) {
                String uid = (String) redisAdamUtil.get(AdamRedisConst.IDENTITY_MOBILE.concat(r));

                if (StringUtils.isBlank(uid)) {
                    List<AdamUserInfoDto> userInfoDtos = adamUserMapper.selectByPhones(r);
                    if (!CollectionUtils.isEmpty(userInfoDtos)) {
                        uid = userInfoDtos.get(0).getUid();
                    }
                }

                if (StringUtils.isNotEmpty(uid)) {
                    CandyUserCoupon userCoupon = new CandyUserCoupon();
                    userCoupon.setUcouponId(IDGenerator.get32UUID());
                    userCoupon.setMcouponId(mcouponId);
                    userCoupon.setUid(uid);
                    userCoupon.setCouponId(couponId);
                    userCoupon.setExclusive(exclusively);
                    userCoupon.setState(1);

//                    userCoupon.setBindAt(couponEffectAt);
                    userCoupon.setBindAt(null == couponEffectAt ? now : couponEffectAt);
                    if (isMemberRightsCoupon) {
                        AdamUserMemberVo vo = (AdamUserMemberVo) redisAdamUtil.get(AdamRedisConst.INFO_USER_MEMBER.concat(uid));
                        if (null != vo && vo.isActive()) {
                            userCoupon.setDuedAt(vo.getExpiryAt());
                        } else {// 非会员（或无效会员）跳过
                            log.warn("发放券处理中:{}-目标指定手机号，该用户非会员或会员已过期[couponId:{},uid:{}]", mcouponId, couponId, uid);
                            continue;
                        }
                    } else if (null != couponExpireAt && couponExpireAt.isAfter(now)) {
                        userCoupon.setDuedAt(couponExpireAt);
                    } else {
                        log.warn("发放券处理中:{}-目标指定手机号，该券生效日期或失效日期无效[couponId:{},effectAt:{},expireAt:{}]", mcouponId, couponId, couponEffectAt, couponExpireAt);
                        continue;
                    }
                    // 判定为券类商品支付后发放券业务&&需要关联实名人时，提取关联人信息绑定到用户券信息中
                    if (Objects.equals(mgtCoupon.getEventDriven(), 0)
                            && Objects.equals(operator, CandyRedisConst.OPERATOR_MGT_COUPON_BY_SKU)
                            && Objects.equals(coupon.getIsTrueName(), 1)) {
                        GoblinOrderSkuVo goblinOrderSkuVo = (GoblinOrderSkuVo) redisGoblinUtil.get(GoblinRedisConst.REDIS_GOBLIN_ORDER_SKU.concat(mgtCoupon.getMcouponId()));
                        if (!Objects.isNull(goblinOrderSkuVo)) {
                            CandyUserCouponAssoc userCouponAssoc = new CandyUserCouponAssoc();
                            userCoupon.setUcouponId(mcouponId);// 券类商品默认一个商品对应一个券，下单只可购买一张
                            userCouponAssoc.setUcouponId(userCoupon.getUcouponId());
                            userCouponAssoc.setIdType(goblinOrderSkuVo.getIdType());
                            userCouponAssoc.setIdName(goblinOrderSkuVo.getIdName());
                            userCouponAssoc.setIdNo(goblinOrderSkuVo.getIdNo());

                            initUserCouponAssocList.add(userCouponAssoc);
                        } else {
                            log.warn("");
                        }
                    }
                    userCoupon.setOperator(operator);
                    userCoupon.setCreatedAt(now);

                    initUserCouponList.add(userCoupon);

                    pl++;
                }
            }
        }

        AbstractRedisUtil redisCandyUtil = redisDataSourceUtil.getRedisCandyUtil();
        if (!initUserCouponList.isEmpty()) {
            if (platformCandyUserCouponService.saveBatch(initUserCouponList)) {
                if (!initUserCouponAssocList.isEmpty()) {// 券类商品默认一个商品对应一个券，下单只可购买一张
                    boolean assocSaveFlg = platformCandyUserCouponAssocService.saveBatch(initUserCouponAssocList);
                    initUserCouponAssocList.forEach(c -> {
                        CandyUserCouponAssocDto assocDto = CandyUserCouponAssocDto.getNew().copy(c);
                        long l = ChronoUnit.SECONDS.between(now, couponExpireAt);
                        boolean assocRdsSetFlg = redisCandyUtil.set(CandyRedisConst.BASIC_USER_COUPON_ASSOC.concat(c.getUcouponId()), assocDto, l);
                        log.info("发放券-券类商品-记录关联人信息:[mcouponId={}, ucouponId={},assocSaveFlg={},assocRdsSetFlg={}]", mcouponId, c.getUcouponId(), assocSaveFlg, assocRdsSetFlg);
                    });
                }
                initUserCouponList.forEach(r -> {
                    String uckey = CandyRedisConst.BASIC_USER_COUPON.concat(r.getUid());

                    List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisCandyUtil.get(uckey);

                    if (CollectionUtils.isEmpty(vos)) {
                        vos = new ArrayList<>();
                    }
                    vos.add(CandyUserCouponBasicDto.getNew().copy(r, coupon, couponRuleDtoList));

                    redisCandyUtil.set(uckey, vos);
                });
            } else {
                throw new LiquidnetServiceException("-1", String.format("券发放失败[mcouponId=%s]", mcouponId));
            }
        }
        log.info("发放券处理中:{}-目标指定手机号[总数:{},发放量:{},实际处理:{}]", mcouponId, limitSize, eventAmt, pl);
    }

    /**
     * <p>
     * 发放代金券
     * </p>
     *
     * @param mgtCoupon CandyMgtCoupon
     * @param coupon    CandyCoupon
     */
    public void issueCashCouponHandler(final CandyMgtCoupon mgtCoupon, final CandyCoupon coupon) {
        this.issueCouponProcessing(mgtCoupon, coupon);
    }

    /**
     * <p>
     * 发放满减券
     * </p>
     *
     * @param mgtCoupon CandyMgtCoupon
     * @param coupon    CandyCoupon
     */
    public void issueOverMinusCouponHandler(CandyMgtCoupon mgtCoupon, CandyCoupon coupon) {
        this.issueCouponProcessing(mgtCoupon, coupon);
    }

    /**
     * <p>
     * 发放兑换券
     * </p>
     *
     * @param mgtCoupon CandyMgtCoupon
     * @param coupon    CandyCoupon
     */
    public void issueExchangeCouponHandler(final CandyMgtCoupon mgtCoupon, final CandyCoupon coupon) {
        this.issueCouponProcessing(mgtCoupon, coupon);
    }

    /**
     * <p>
     * 发放折扣券
     * </p>
     *
     * @param mgtCoupon CandyMgtCoupon
     * @param coupon    CandyCoupon
     */
    public void issueDiscountCouponHandler(final CandyMgtCoupon mgtCoupon, final CandyCoupon coupon) {
        this.issueCouponProcessing(mgtCoupon, coupon);
    }

    /**
     * <p>
     * 发放优先券
     * </p>
     *
     * @param mgtCoupon CandyMgtCoupon
     * @param coupon    CandyCoupon
     */
    public void issuePriorityCouponHandler(final CandyMgtCoupon mgtCoupon, final CandyCoupon coupon) {
        this.issueCouponProcessing(mgtCoupon, coupon);
    }
}
