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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.Week;
import cn.hutool.core.util.StrUtil;
import com.tmerclub.cloud.api.auth.bo.UidInfoBO;
import com.tmerclub.cloud.api.user.feign.UserFeignClient;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.leaf.manager.SegmentManager;
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.common.util.IpHelper;
import com.tmerclub.cloud.flow.bo.FlowLogBO;
import com.tmerclub.cloud.flow.bo.FlowSpuUserBO;
import com.tmerclub.cloud.flow.bo.FlowSystemUserBO;
import com.tmerclub.cloud.flow.bo.FlowUserOperationBO;
import com.tmerclub.cloud.flow.constant.*;
import com.tmerclub.cloud.flow.dto.FlowLogDTO;
import com.tmerclub.cloud.flow.service.*;
import com.tmerclub.cloud.flow.util.FlowUtil;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

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

/**
 * 流量分析
 *
 * @author YXF
 * @date 2021-05-21 15:25:19
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@Service
public class FlowServiceImpl implements FlowService {

    private static final Logger log = LoggerFactory.getLogger(FlowServiceImpl.class);

    @Autowired
    private FlowUserMongoService flowUserMongoService;
    @Autowired
    private UserAnalyseMongoService userAnalyseMongoService;
    @Autowired
    private SystemAnalyseMongoService systemAnalyseMongoService;
    @Autowired
    private SystemUserAnalyseMongoService systemUserAnalyseMongoService;
    @Autowired
    private SpuAnalyseMongoService spuAnalyseMongoService;
    @Autowired
    private MongoTemplate mongoTemplate;
    @DubboReference
    private UserFeignClient userFeignClient;
    @Autowired
    private SegmentManager segmentManager;


    @Override
    public void log(FlowLogDTO flowLogDTO) {
        flowLogDTO.setCreateTime(DateUtil.beginOfDay(new Date()));
        UidInfoBO uidInfoBO = AuthUserContext.get();
        long oid = flowLogDTO.getOid() * 10;
        long ip = IpHelper.getIpNumber();
        Date date = new Date();
        Long bid = null;
        // 批量处理
        if (StrUtil.isNotBlank(flowLogDTO.getBid()) && flowLogDTO.getBid().contains(Constant.COMMA)) {
            String[] split = flowLogDTO.getBid().split(Constant.COMMA);
            flowLogDTO.setBid(null);
            List<FlowLogBO> flowLogList = new ArrayList<>();
            for (String s : split) {
                FlowLogBO flowLogBO = BeanUtil.map(flowLogDTO, FlowLogBO.class);
                this.handleFlowLog(uidInfoBO, oid, ip, Long.parseLong(s), date, flowLogBO);
                flowLogList.add(flowLogBO);
            }
            mongoTemplate.insertAll(flowLogList);
            return;
        } else if (StrUtil.isNotBlank(flowLogDTO.getBid())) {
            bid = Long.parseLong(flowLogDTO.getBid());
        }
        FlowLogBO flowLogBO = BeanUtil.map(flowLogDTO, FlowLogBO.class);
        this.handleFlowLog(uidInfoBO, oid, ip, bid, date, flowLogBO);
        mongoTemplate.save(flowLogBO);
    }

    @Override
    public void paySuccessLog(Integer sysType, String orderIds, Long userId) {
        List<FlowLogBO> flowLogs = new ArrayList<>();
        // 根据订单id查进入到支付页面的日志，然后对应步数+1就是支付成功的步数
        boolean isMulti = StrUtil.isNotBlank(orderIds) && orderIds.contains(Constant.COMMA);
        String[] orderIdStrArr = orderIds.split(StrUtil.COMMA);
        if (isMulti) {
            for (String orderId : orderIdStrArr) {
                List<FlowLogBO> multiList = flowUserMongoService.getFlowLogByOrderIds(FlowLogPageEnum.ORDER_CONFIRM.value(), userId, orderId);
                flowLogs.addAll(multiList);
            }
        } else {
            flowLogs = flowUserMongoService.getFlowLogByOrderIds(FlowLogPageEnum.ORDER_CONFIRM.value(), userId, orderIds);
        }
        // 如果没有支付页面的日志，就给他加一条
        if (CollUtil.isEmpty(flowLogs)) {
            log.error("提交订单的流量记录不存在，orderIds:{},userId:{}", orderIds, userId);
            addSubmitOrderLog(sysType, orderIds, userId, flowLogs);
        }
        // 如果有多条记录，保留第一条，剩下的全删了
        else if (!isMulti && flowLogs.size() > 1) {
            List<Long> oids = flowLogs.stream().map(FlowLogBO::getOid).collect(Collectors.toList());
            oids.remove(0);
            mongoTemplate.remove(new Query(Criteria.where("oid").in(oids)), FlowLogBO.class);
        } else if (isMulti) {
            // 多单结算
            mulitSave(sysType, flowLogs, orderIdStrArr);
            return;
        }
        FlowLogBO flowLogBO = getFlowLogBO(sysType, orderIds, flowLogs.get(0));
        mongoTemplate.save(flowLogBO);
    }

    private void mulitSave(Integer sysType, List<FlowLogBO> flowLogs, String[] orderIdStrArr) {
        if (flowLogs.size() > orderIdStrArr.length) {
            Map<Long, List<FlowLogBO>> map = flowLogs.stream().collect(Collectors.groupingBy(FlowLogBO::getBid));
            flowLogs.clear();
            for (Long bid : map.keySet()) {
                List<FlowLogBO> flowLogBOList = map.get(bid);
                flowLogs.add(flowLogBOList.get(0));
            }
        }
        List<FlowLogBO> insertList = new ArrayList<>();
        for (FlowLogBO flowLog : flowLogs) {
            FlowLogBO flowLogBO = getFlowLogBO(sysType, String.valueOf(flowLog.getBid()), flowLog);
            insertList.add(flowLogBO);
        }
        mongoTemplate.insertAll(insertList);
    }

    private static FlowLogBO getFlowLogBO(Integer sysType, String orderIds, FlowLogBO flowLogBO) {
        flowLogBO.setSt(sysType);
        flowLogBO.setPid(FlowLogPageEnum.PAY_SUCCESS.value());
        flowLogBO.setVe(FlowVisitEnum.PAY_SUCCESS.value());
        flowLogBO.setDt(new Date());
        flowLogBO.setS(flowLogBO.getS() + 1);
        // 如果没有支付系统类型，默认给个H5,并且记录下来日志
        if (Objects.isNull(flowLogBO.getSt())) {
            log.error("订单支付成功的用户操作记录没有系统类型,orderIds:{}", orderIds);
            flowLogBO.setSt(SystemTypeEnum.H5.value());
        }
        return flowLogBO;
    }

    private void addSubmitOrderLog(Integer sysType, String orderIds, Long userId, List<FlowLogBO> flowLogs) {
        FlowLogBO flowLogBO = flowUserMongoService.getUserLastLog(userId);
        Long oid = segmentManager.getSegmentId(DistributedIdKey.tmerclub_FLOW_USER_ID);
        String[] orderIdStrArr = orderIds.split(StrUtil.COMMA);
        for (String orderId : orderIdStrArr) {
            if (Objects.isNull(flowLogBO)) {
                flowLogBO = new FlowLogBO();
                setFlowInfo(sysType, orderId, flowLogBO, oid);
                flowLogBO.setS(1);
                flowLogBO.setUid(userId);
                flowLogBO.setIp(IpHelper.getIpNumber());
                mongoTemplate.save(flowLogBO);
            }
            setFlowInfo(sysType, orderId, flowLogBO, oid);
            flowLogBO.setS(flowLogBO.getS() + 1);
            flowLogs.add(flowLogBO);
        }
        mongoTemplate.insertAll(flowLogs);
    }

    private static void setFlowInfo(Integer sysType, String orderIds, FlowLogBO flowLogBO, Long oid) {
        flowLogBO.setOid(oid * 10);
        flowLogBO.setSt(sysType);
        flowLogBO.setPid(FlowLogPageEnum.ORDER_CONFIRM.value());
        flowLogBO.setVe(FlowVisitEnum.CONFIRM_ORDER.value());
        flowLogBO.setBid(Long.parseLong(orderIds));
        flowLogBO.setN(0);
        flowLogBO.setDt(new Date());
        // 如果没有支付系统类型，默认给个H5,并且记录下来日志
        if (Objects.isNull(flowLogBO.getSt())) {
            log.error("订单支付成功的用户操作记录没有系统类型,orderIds:{}", orderIds);
            flowLogBO.setSt(SystemTypeEnum.H5.value());
        }
    }

    private void handleFlowLog(UidInfoBO uidInfoBO, long oid, long ip, Long bid, Date date, FlowLogBO flowLogBO) {
        // 登陆后，插入用户id
        if (Objects.nonNull(uidInfoBO) && Objects.nonNull(uidInfoBO.getUserId())) {
            flowLogBO.setUid(uidInfoBO.getUserId());
        }
        // 跟用户id做个区分，oid是13为，uid是12位
        flowLogBO.setOid(oid);
        flowLogBO.setBid(bid);
        flowLogBO.setIp(ip);
        flowLogBO.setDt(date);
    }

    @Override
    public void statisticalFlowUserLog() {
        // 先获取用户操作记录中最早的日期，然后根据日期天统计，一直统计到当前日期

        Date beginDate = this.getBeginDate();
        log.info("统计流量分析数据开始，开始时间：{}", beginDate);
        long today = DateUtil.beginOfDay(new Date()).getTime();

        // 时间循环开始 while
        while (beginDate.getTime() < today) {
            int dateType = FlowDateTypeEnum.DAY.value();
            Date endDate = DateUtil.offsetDay(beginDate, 1);
            Date startTime = beginDate;
            log.info("统计流量分析数据开始，统计时间：{}", startTime);
            // ====================== 从flow_user_log中统计当天的数据 ======================
            this.statisticsBaseData(dateType, endDate, startTime);

            // ====================== 根据上一步统计的数据整合为流量分析的数据 ======================
            // 日 ---- 统计当天商城用户的操作数据
            log.info("统计流量分析数据开始-统计当天商城用户的操作数据");
            this.statisticsDayOfData(dateType, startTime, endDate);

            // 周 ---- 统计该日期所在周的数据
            if (Objects.equals(Week.MONDAY, DateUtil.dayOfWeekEnum(startTime))) {
                log.info("统计流量分析数据开始-统计该日期所在周的数据");
                startTime = DateUtil.beginOfWeek(beginDate);
                this.statisticalDataBasedOnDateType(FlowDateTypeEnum.WEEK.value(), startTime, endDate);
            }

            // 月 ---- 统计该日期所在月的数据
            if (Objects.equals(DateUtil.dayOfMonth(startTime), 1)) {
                log.info("统计流量分析数据开始-统计该日期所在月的数据");
                startTime = DateUtil.beginOfMonth(beginDate);
                this.statisticalDataBasedOnDateType(FlowDateTypeEnum.MONTH.value(), startTime, endDate);
                // 客户留存
                userAnalyseMongoService.statisticsAndSaveUserRetained();
            }
            // 统计完当天的数据后，删除当天的用户操作记录 flow_user_log
//            mongoTemplate.remove(Query.query(Criteria.where("dt").gte(beginDate).lt(endDate)), FlowLogBO.class);
            // 日期加1天继续循环
            beginDate = DateUtil.offsetDay(beginDate, 1);
        }

        Date endDate = DateUtil.beginOfDay(new Date());
        // ====================== 统计近7天的数据 ======================
        beginDate = DateUtil.offsetDay(endDate, -FlowDateTypeEnum.NEARLY_WEEK.getNum());
        log.info("统计流量分析数据，近7天数据统计的开始时间：{}， 结束时间： {}", beginDate, endDate);
        this.statisticalDataBasedOnDateType(FlowDateTypeEnum.NEARLY_WEEK.value(), beginDate, endDate);

        // ====================== 统计近30天的数据 ======================
        beginDate = DateUtil.offsetDay(endDate, -FlowDateTypeEnum.NEARLY_MONTH.getNum());
        log.info("统计流量分析数据，近30天数据统计的开始时间：{}， 结束时间： {}", beginDate, endDate);
        this.statisticalDataBasedOnDateType(FlowDateTypeEnum.NEARLY_MONTH.value(), beginDate, endDate);

        // 统计当月客户留存数据
        userAnalyseMongoService.statisticsAndSaveUserRetainedOfCurrent();

        beginDate = DateUtil.offsetDay(endDate, -70);
        // 删除flow_system_user中70天前的数据
        mongoTemplate.remove(new Query(Criteria.where("_id").lte(FlowUtil.timeToId(beginDate, FlowSystemUserBO.ID_LENGTH))), FlowSystemUserBO.class);

        // 删除flow_spu_user中70天前的数据
        mongoTemplate.remove(new Query(Criteria.where("_id").lte(FlowUtil.timeToId(beginDate))), FlowSpuUserBO.class);

        // 删除flow_user_operation中200天前的数据
        beginDate = DateUtil.offsetDay(endDate, -240);
        mongoTemplate.remove(new Query(Criteria.where("_id").lte(FlowUtil.timeToId(beginDate))), FlowUserOperationBO.class);
    }

    private Date getBeginDate() {
        // 获取流量统计的最初时间
        Date flowLogTime = flowUserMongoService.getBeginDate();

        // 获取用户数据统计的最新时间
        Date userLatestTime = userAnalyseMongoService.getLatestTime();

        // 如果为空就代表还没有统计过用户数据，所以查询第一个用户的注册时间，作为统计的开始时间
        if (Objects.isNull(userLatestTime)) {
            // 获取第一个用户的注册时间
            ServerResponseEntity<Date> userResponseEntity = userFeignClient.getInitialRegistrationTime();
            if (!userResponseEntity.isSuccess()) {
                throw new LuckException(userResponseEntity.getMsg());
            }
            userLatestTime = userResponseEntity.getData();
        }

        // 如果用户数据统计的最新时间小于流量统计的最小时间，就以用户统计时间为准
        if (Objects.nonNull(userLatestTime) && userLatestTime.getTime() < flowLogTime.getTime()) {
            flowLogTime = userLatestTime;
        }

        return DateUtil.beginOfDay(flowLogTime);
    }

    private void statisticsBaseData(int dateType, Date endDate, Date startTime) {
        // 统计系统数据 -flow_system_user  --flow_user_log
        systemUserAnalyseMongoService.statisticsAndSaveSystemUserOfDay(dateType, startTime, endDate);

        // 统计用户操作数据 -flow_user_operation --flow_user_log
        // 先统计每个用户的操作记录
        userAnalyseMongoService.statisticsAndSaveUserOfDay(startTime, endDate);

        // 统计商品数据 -flow_spu_user --flow_user_log
        spuAnalyseMongoService.statisticsAndSaveSpuUserOfDay(startTime, endDate);
    }

    private void statisticsDayOfData(Integer dateType, Date beginDate, Date endDate) {

        // 统计系统数据 - system_analyse --flow_user_log
        log.info("统当天商城用户的操作数据-统计系统数据");
        systemAnalyseMongoService.statisticsAndSaveSystemOfDay(beginDate, endDate);

        // 访问深度  -flow_user_footprint --flow_user_operation
        log.info("统当天商城用户的操作数据-访问深度");
        userAnalyseMongoService.statisticsAndSaveUserFootprintOfDay(dateType, beginDate, endDate);

        // 访问地域 -flow_regional --flow_user_operation、flow_spu_user
        log.info("统当天商城用户的操作数据-访问地域");
        userAnalyseMongoService.statisticsAndSaveRegionalOfTime(dateType, beginDate, endDate);

        // 会员概括 -flow_user --flow_user_operation
        log.info("统当天商城用户的操作数据-会员概括");
        userAnalyseMongoService.statisticsAndSaveUserOfTime(dateType, beginDate, endDate);

        // 商品概况 -flow_spu --flow_user_log
        log.info("统当天商城用户的操作数据-商品概况");
        spuAnalyseMongoService.statisticsAndSaveSpuOfDay(dateType, beginDate, endDate);
    }

    private void  statisticalDataBasedOnDateType(int dateType, Date beginDate, Date endDate){
        // 统计系统数据 - flow_system --flow_system、flow_system_user
        log.info("统计指定范围内的数据-系统数据");
        systemAnalyseMongoService.statisticsAndSaveSystemOfTime(dateType, beginDate, endDate);

        // 访问深度  -flow_user_footprint --flow_user_operation
        log.info("统计指定范围内的数据-访问深度");
        userAnalyseMongoService.statisticsAndSaveUserFootprintOfTime(dateType, beginDate, endDate);

        // 访问地域 -flow_regional --flow_user_operation、flow_spu_user
        log.info("统计指定范围内的数据-访问地域");
        userAnalyseMongoService.statisticsAndSaveRegionalOfTime(dateType, beginDate, endDate);

        // 会员概括 -flow_user --flow_user_operation
        log.info("统计指定范围内的数据-会员概括");
        userAnalyseMongoService.statisticsAndSaveUserOfTime(dateType, beginDate, endDate);

        // 商品概况 -flow_spu --flow_spu、flow_spu_user
        log.info("统计指定范围内的数据-商品概况");
        spuAnalyseMongoService.statisticsAndSaveSpuOfTime(dateType, beginDate, endDate);
    }
}
