package cn.newfeifan.mall.module.distri.service.integral;

import cn.newfeifan.mall.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.newfeifan.mall.module.distri.controller.admin.consumptionchangelog.vo.ConsumptionChangeLogSaveReqVO;
import cn.newfeifan.mall.module.distri.controller.admin.ptprofit.vo.PtProfitSaveReqVO;
import cn.newfeifan.mall.module.distri.controller.admin.ptprofitlog.vo.PtProfitLogSaveReqVO;
import cn.newfeifan.mall.module.distri.dal.dataobject.ptprofit.PtProfitDO;
import cn.newfeifan.mall.module.distri.dal.dataobject.ptprofitlog.PtProfitLogDO;
import cn.newfeifan.mall.module.distri.dal.mysql.ptprofit.PtProfitMapper;
import cn.newfeifan.mall.module.distri.dal.mysql.ptprofitlog.PtProfitLogMapper;
import cn.newfeifan.mall.module.distri.enums.CaclEnum;
import cn.newfeifan.mall.module.distri.enums.ConsumptionEnum;
import cn.newfeifan.mall.module.distri.service.consumptionchangelog.ConsumptionChangeLogService;
import cn.newfeifan.mall.module.distri.service.ptprofit.PtProfitService;
import cn.newfeifan.mall.module.distri.service.ptprofitlog.PtProfitLogService;
import cn.newfeifan.mall.module.distri.service.sharepath.SharePathService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import org.springframework.validation.annotation.Validated;

import java.time.LocalDateTime;
import java.util.*;

import cn.newfeifan.mall.module.distri.controller.admin.integral.vo.*;
import cn.newfeifan.mall.module.distri.dal.dataobject.integral.IntegralDO;
import cn.newfeifan.mall.framework.common.pojo.PageResult;
import cn.newfeifan.mall.framework.common.util.object.BeanUtils;

import cn.newfeifan.mall.module.distri.dal.mysql.integral.IntegralMapper;

import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.newfeifan.mall.module.distri.constant.DistriConstants.PT_ID;
import static cn.newfeifan.mall.module.distri.enums.CaclEnum.*;
import static cn.newfeifan.mall.module.distri.enums.ErrorCodeConstants.*;

/**
 * 推荐用户积分 Service 实现类
 *
 * @author 非繁人
 */
@Service
@Validated
public class IntegralServiceImpl implements IntegralService {

    @Resource
    private IntegralMapper integralMapper;
    @Resource
    private ConsumptionChangeLogService consumptionChangeLogService;

    @Resource
    private PtProfitLogMapper ptProfitLogMapper;

    @Resource
    @Lazy
    private SharePathService sharePathService;

    @Resource
    private PtProfitLogService ptProfitLogService;

    @Resource
    private PtProfitService ptProfitService;

    @Resource
    private PtProfitMapper ptProfitMapper;

    @Override
    public Long createIntegral(IntegralSaveReqVO createReqVO) {
        // 插入
        IntegralDO integral = BeanUtils.toBean(createReqVO, IntegralDO.class);
        integral.setAccumulatedQuota(0L);
        integral.setAncestorQuota(0L);
        integralMapper.insert(integral);
        // 返回
        return integral.getId();
    }

    @Override
    public void updateIntegral(IntegralSaveReqVO updateReqVO) {
        // 校验存在
        validateIntegralExists(updateReqVO.getId());
        // 更新
        IntegralDO updateObj = BeanUtils.toBean(updateReqVO, IntegralDO.class);
        integralMapper.updateById(updateObj);
    }

    @Override
    public void deleteIntegral(Long id) {
        // 校验存在
        validateIntegralExists(id);
        // 删除
        integralMapper.deleteById(id);
    }

    private void validateIntegralExists(Long id) {
        if (integralMapper.selectById(id) == null) {
            throw exception(INTEGRAL_NOT_EXISTS);
        }
    }

    @Override
    public IntegralDO getIntegral(Long id) {
        return integralMapper.selectOne(new LambdaQueryWrapperX<IntegralDO>().eqIfPresent(IntegralDO::getUserId, id));
    }

    @Override
    public PageResult<IntegralDO> getIntegralPage(IntegralPageReqVO pageReqVO) {
        return integralMapper.selectPage(pageReqVO);
    }

    @Override
    public IntegralDO selectByUser(Long descendant) {
        return integralMapper.selectOne(new LambdaQueryWrapper<IntegralDO>().eq(IntegralDO::getUserId, descendant));
    }

    @Override
    public void saveBatch(List<IntegralSaveReqVO> integralSaveReqVOS) {
        List<IntegralDO> integralDOS = BeanUtils.toBean(integralSaveReqVOS, IntegralDO.class);
        integralDOS.forEach(k -> {
            k.setAccumulatedQuota(0L);
            k.setAncestorQuota(0L);
        });
        integralMapper.insertBatch(integralDOS);
    }

    @Override
    public void updateBatch(List<IntegralSaveReqVO> integralSaveReqVOS) {

        integralSaveReqVOS.forEach(k -> {
            // 校验存在
            validateIntegralExists(k.getId());
            // 更新
            IntegralDO updateObj = BeanUtils.toBean(k, IntegralDO.class);
            integralMapper.updateById(updateObj);
        });

    }

    @Override
    public IntegralDO selectUserParent(Long userId) {

        Long parentId = sharePathService.queryParentBySonUserId(userId);
        return integralMapper.selectOne(new LambdaQueryWrapperX<IntegralDO>().eqIfPresent(IntegralDO::getUserId, parentId));
    }

    @Override
    public List<IntegralDO> selectAllUser() {

        return integralMapper.selectList();

    }

    @Override
    public IntegralDO getIntegralByUserId(Long userId) {
        return integralMapper.selectOne(new LambdaQueryWrapper<IntegralDO>()
                .eq(IntegralDO::getUserId, userId)
        );
    }

    @Override
    public void updateIntegralFreezeHighQuota(Long generateUserId, Long userId, CaclEnum caclEnum, Long totalFreezeHighQuota, Long tradeOrderId, String orderNum, LocalDateTime payTime) {
        try{
            IntegralDO integralDO = integralMapper.selectOne(new LambdaQueryWrapper<IntegralDO>().eq(IntegralDO::getUserId, userId));
            integralDO.setFreezeHighQuota(integralDO.getFreezeHighQuota() + totalFreezeHighQuota);
            integralMapper.updateById(integralDO);

            //记录用户的积分变动日志
            PtProfitLogDO ptProfitLogDO = PtProfitLogDO.builder()
                    .freezeHighQuota(totalFreezeHighQuota)
                    .afterFreezeHighQuota(integralDO.getFreezeHighQuota())
                    .userId(userId)
                    .generateUserId(generateUserId)
                    .profitStatus(caclEnum.getType())
                    .orderId(tradeOrderId)
                    .orderNo(orderNum)
                    .build();
            ptProfitLogDO.setCreateTime(payTime);
            ptProfitLogDO.setUpdateTime(payTime);
            ptProfitLogMapper.insert(ptProfitLogDO);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    @Override
    public void updateIntegralFreezeHighQuota(Long generateUserId, Long userId, CaclEnum caclEnum, Long totalFreezeHighQuota, Long tradeOrderId, String orderNum) {
        IntegralDO integralDO = integralMapper.selectOne(new LambdaQueryWrapper<IntegralDO>().eq(IntegralDO::getUserId, userId));
        integralDO.setFreezeHighQuota(integralDO.getFreezeHighQuota() + totalFreezeHighQuota);
        integralMapper.updateById(integralDO);

        //记录用户的积分变动日志
        PtProfitLogSaveReqVO profitLogSaveReqVO = PtProfitLogSaveReqVO.builder()
                .freezeHighQuota(totalFreezeHighQuota)
                .afterFreezeHighQuota(integralDO.getFreezeHighQuota())
                .userId(userId)
                .generateUserId(generateUserId)
                .profitStatus(caclEnum.getType())
                .orderId(tradeOrderId)
                .orderNo(orderNum)
                .build();
        ptProfitLogService.createPtProfitLog(profitLogSaveReqVO);
    }

    @Override
    public void initializeIntegralHighQuotaTotal() {
        List<IntegralDO> integralDOS = integralMapper.selectList();
        for (IntegralDO integralDO : integralDOS) {
            List<PtProfitLogDO> ptProfitLogDOS = ptProfitLogMapper.selectList(new LambdaQueryWrapper<PtProfitLogDO>()
                    .eq(PtProfitLogDO::getUserId, integralDO.getUserId())
                    .eq(PtProfitLogDO::getProfitStatus, DIRECT_REFERRAL_QUOTA.getType())
            );
            for (PtProfitLogDO ptProfitLogDO : ptProfitLogDOS) {
                integralDO.setHighQuotaTotal(integralDO.getHighQuotaTotal() + ptProfitLogDO.getMaxAvailablePointsAmount());
            }

            integralMapper.updateById(integralDO);
            if(ptProfitLogDOS.size() > 1){
                System.out.println("用户编号:[" + integralDO.getUserId() + "]");
            }
        }
    }

    @Override
    public void updateUserIntegral(Long userId, CaclEnum caclEnum, Long payIntegral, Long tradeOrderId, String orderNum) {
        //退还订单支付过期积分
        IntegralDO integralDO = integralMapper.selectOne(new LambdaQueryWrapper<IntegralDO>().eq(IntegralDO::getUserId, userId));
        integralDO.setCurrentQuota(integralDO.getCurrentQuota() + payIntegral);

        integralMapper.updateById(integralDO);

        ptProfitLogService.addMessage(userId,userId,caclEnum,payIntegral,integralDO.getCurrentQuota(),0L,0L,null,tradeOrderId,orderNum);
    }

    /**
     * add by Ben
     * 修改用户积分
     *
     * @param generateUserId 要修改积分的用户
     * @param userId 要修改积分的用户
     * @param caclEnum 导致积分变化的事件类型枚举
     * @param amount 用户可用积分变化金额，可为负数
     * @param freezeAmount 用户冻结积分变化金额，可为负数
     * @return 返回是否执行成功。执行失败，通常是就是用户积分不足，没积分抵扣
     */
    @Override
    public Boolean updateUserIntegral(Long generateUserId,//导致产生积分变动的用户ID
                                      Long userId, CaclEnum caclEnum, Long amount, Long freezeAmount,
                                      Long tradeOrderId,
                                      String OrderNum
    ) {
        Boolean success = null;

        //修改用户积分钱包的积分
        int effectRowNum = ptProfitMapper.updateUserIntegral(amount,amount,freezeAmount,freezeAmount,userId);

        if (effectRowNum==1) {//如果成功修改积分记录
            success = true;

            //用户修改后的当前可用的钱包余额
            Long afterAmount = null;
            //用户修改后的冻结积分钱包余额
            Long afterFreezeAmount = null;

            //查询用户修改后的积分
            Map<String, Object> map = ptProfitMapper.selectUserIntegral(userId);
            Object currentQuotaObj = map.get("current_quota");
            Object freezeQuotaObj = map.get("freeze_quota");

            afterAmount = (Long)currentQuotaObj;
            afterFreezeAmount = (Long)freezeQuotaObj;

            if(afterAmount<0||afterFreezeAmount<0){
                throw new RuntimeException("用户钱包为负数：用户IO为"+userId+"的用户可用钱包为"+afterAmount+",冻结钱包为"+afterFreezeAmount);
            }

            //记录用户的积分变动日志
            ptProfitLogService.addMessage(generateUserId,//导致产生积分变动的用户ID
                    userId, caclEnum, amount, afterAmount,
                    freezeAmount, afterFreezeAmount,null,tradeOrderId,OrderNum);

        }else if(effectRowNum<1){//如果钱包积分不足，不够抵扣
            success = false;
        } else if (effectRowNum>1) {
            throw new RuntimeException("用户ID为"+userId+"的用户钱包不只一个！");
        }

        return success;
    }

    @Override
    public void calcIntegral() {
        List<PtProfitLogDO> ptProfitLogDOS = ptProfitLogService.calcIntegral();
        for (PtProfitLogDO ptProfitLogDO : ptProfitLogDOS) {
            IntegralDO integralDO = integralMapper.selectOne(new LambdaQueryWrapper<IntegralDO>().eq(IntegralDO::getUserId, ptProfitLogDO.getUserId()));
            //加积分余额
            integralDO.setCurrentQuota(integralDO.getCurrentQuota() + ptProfitLogDO.getAccumulatedQuotaAmount());
            //加累计积分余额
            integralDO.setAccumulatedQuota(integralDO.getAccumulatedQuota() + ptProfitLogDO.getAccumulatedQuotaAmount());
            //减可用峰值
            integralDO.setHighQuota(integralDO.getHighQuota() - ptProfitLogDO.getAccumulatedQuotaAmount());

            integralMapper.updateById(integralDO);

            PtProfitDO ptProfit = ptProfitService.getPtProfit();
            IntegralDO ptIntegralDO = selectByUser(PT_ID);

            //平台用户扣除积分
            ptProfit.setPtGrossAdd(ptProfit.getPtGrossAdd() - ptProfitLogDO.getAccumulatedQuotaAmount());
            ptIntegralDO.setCurrentQuota(ptIntegralDO.getCurrentQuota() - ptProfitLogDO.getAccumulatedQuotaAmount());
            // 平台扣除积分
            ptProfit.setPtTotalAdd(ptProfit.getPtTotalAdd() - ptProfitLogDO.getAccumulatedQuotaAmount());

            integralMapper.updateById(ptIntegralDO);
            ptProfitService.updatePtProfit(PtProfitSaveReqVO.builder().id(ptProfit.getId())
                    .ptAdd(ptProfit.getPtAdd())
                    .ptGrossAdd(ptProfit.getPtGrossAdd())
                    .ptTotalAdd(ptProfit.getPtTotalAdd()).build());

            //这条记录是用户看的
            PtProfitLogSaveReqVO ProfitLog = PtProfitLogSaveReqVO.builder()
                    .amount(ptProfitLogDO.getAccumulatedQuotaAmount())
                    .afterAmount(integralDO.getCurrentQuota())
                    .userId(ptProfitLogDO.getUserId())
                    .generateUserId(ptProfitLogDO.getGenerateUserId())
                    .profitStatus(MANUAL_RETURN_INTEGRAL.getType())
                    .maxAvailablePointsAmount(-ptProfitLogDO.getAccumulatedQuotaAmount())
                    .afterMaxAvailablePointsAmount(integralDO.getHighQuota())
                    .build();
            ptProfitLogMapper.insert(BeanUtils.toBean(ProfitLog, PtProfitLogDO.class));

            //这条是pt看的
            PtProfitLogSaveReqVO ptProfitLog = PtProfitLogSaveReqVO.builder()
                    .amount(ptProfitLogDO.getAccumulatedQuotaAmount())
                    .afterAmount(ptProfit.getPtGrossAdd())
                    .generateUserId(ptProfitLogDO.getGenerateUserId())
                    .profitStatus(MANUAL_RETURN_INTEGRAL.getType())
                    .build();
            ptProfitLogMapper.insert(BeanUtils.toBean(ptProfitLog, PtProfitLogDO.class));
        }
    }

    @Override
    public void updateUserConsumptionPoints(Long userId, ConsumptionEnum consumptionEnum, Long consumptionPoints, Long orderId, String orderNum) {
        IntegralDO integralDO = integralMapper.selectOne(IntegralDO::getUserId, userId);

        // 修改用户消费积分
        integralDO.setConsumptionPoints(integralDO.getConsumptionPoints() + consumptionPoints);
        integralMapper.updateById(integralDO);

        consumptionChangeLogService.createConsumptionChangeLog(
                ConsumptionChangeLogSaveReqVO.builder()
                        .userId(userId)
                        .generateUserId(userId)
                        .consumptionStatus(consumptionEnum.getType())
                        .consumptionPoints(consumptionPoints)
                        .afterConsumptionPoints(integralDO.getConsumptionPoints())
                        .orderId(orderId)
                        .orderNo(orderNum)
                        .build());
    }

}
