package cn.stylefeng.workflow.instance.modular.stat.business.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.stylefeng.roses.kernel.db.api.pojo.entity.BaseEntity;
import cn.stylefeng.workflow.api.entity.instance.RuntimeInstance;
import cn.stylefeng.workflow.api.entity.task.RuTask;
import cn.stylefeng.workflow.instance.modular.manage.service.RuntimeInstanceService;
import cn.stylefeng.workflow.instance.modular.stat.business.enums.StatBizTypeEnum;
import cn.stylefeng.workflow.instance.modular.stat.business.factory.CombineResultFactory;
import cn.stylefeng.workflow.instance.modular.stat.business.mapper.StatMapper;
import cn.stylefeng.workflow.instance.modular.stat.business.pojo.ResultXyData;
import cn.stylefeng.workflow.instance.modular.stat.business.pojo.ScopeStatResult;
import cn.stylefeng.workflow.instance.modular.stat.business.pojo.StatResult;
import cn.stylefeng.workflow.instance.modular.stat.business.pojo.XyData;
import cn.stylefeng.workflow.instance.modular.stat.business.service.InstanceStatService;
import cn.stylefeng.workflow.instance.modular.task.service.RuTaskService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class InstanceStatServiceImpl implements InstanceStatService {

    @Resource
    private RuntimeInstanceService runtimeInstanceService;

    @Resource
    private RuTaskService ruTaskService;

    @Resource
    private StatMapper statMapper;

    @Override
    public StatResult getRecentStat(Integer days) {

        StatResult statResult = new StatResult();

        this.getDateRangeString(days, statResult);

        List<Long> instanceIdList = this.setAvgTimeHours(statResult);

        this.setApprovalCount(statResult, instanceIdList);

        return statResult;
    }

    @Override
    public List<ScopeStatResult> getStatScopeInfo(Integer days) {

        List<ScopeStatResult> scopeStatResults = new ArrayList<>();

        Date currentDate = new Date();
        DateTime latestDays = DateUtil.offsetDay(currentDate, -days);
        DateTime beforeDays = DateUtil.offsetDay(latestDays, -days);

        List<XyData> latestAvgHours = statMapper.findAvgFlowCompleteHour(latestDays, currentDate);

        List<XyData> beforeAvgHours = statMapper.findAvgFlowCompleteHour(beforeDays, latestDays);

        List<ResultXyData> avgHoursResult = CombineResultFactory.combineResult(latestDays, currentDate, latestAvgHours, beforeAvgHours, days);
        scopeStatResults.add(new ScopeStatResult(StatBizTypeEnum.AVG_HOURS.getCode(), avgHoursResult));

        List<XyData> latestApprovalCount = statMapper.findFlowApprovalCount(latestDays, currentDate);

        List<XyData> beforeApprovalCount = statMapper.findFlowApprovalCount(beforeDays, latestDays);

        List<ResultXyData> approvalCountResult = CombineResultFactory.combineResult(latestDays, currentDate, latestApprovalCount, beforeApprovalCount, days);
        scopeStatResults.add(new ScopeStatResult(StatBizTypeEnum.APPROVE_NUMBER.getCode(), approvalCountResult));

        List<XyData> latestApprovalUserCount = statMapper.findFlowApprovalUserCount(latestDays, currentDate);

        List<XyData> beforeApprovalUserCount = statMapper.findFlowApprovalUserCount(beforeDays, latestDays);

        List<ResultXyData> approvalUserCountResult = CombineResultFactory.combineResult(latestDays, currentDate, latestApprovalUserCount, beforeApprovalUserCount, days);
        scopeStatResults.add(new ScopeStatResult(StatBizTypeEnum.APPROVE_USERS.getCode(), approvalUserCountResult));

        return scopeStatResults;
    }

    private void getDateRangeString(Integer days, StatResult statResult) {

        Date currentDate = new Date();

        DateTime beforeDate = DateUtil.offsetDay(currentDate, -days);

        statResult.setStatBeginDate(beforeDate);
        statResult.setStatEndDate(currentDate);
    }

    private List<Long> setAvgTimeHours(StatResult statResult) {

        LambdaQueryWrapper<RuntimeInstance> runtimeInstanceLambdaQueryWrapper = new LambdaQueryWrapper<>();

        runtimeInstanceLambdaQueryWrapper.between(BaseEntity::getCreateTime, statResult.getStatBeginDate(), statResult.getStatEndDate());

        runtimeInstanceLambdaQueryWrapper.isNotNull(RuntimeInstance::getCompletionTime);

        runtimeInstanceLambdaQueryWrapper.select(RuntimeInstance::getInstanceId, RuntimeInstance::getCreateTime, RuntimeInstance::getCompletionTime);

        List<RuntimeInstance> list = runtimeInstanceService.list(runtimeInstanceLambdaQueryWrapper);

        if (ObjectUtil.isEmpty(list)) {
            statResult.setAvgTimeHours(new BigDecimal("0"));
            return new ArrayList<>();
        }

        BigDecimal totalMinutes = new BigDecimal("0");
        for (RuntimeInstance runtimeInstance : list) {
            Date createTime = runtimeInstance.getCreateTime();
            Date completionTime = runtimeInstance.getCompletionTime();
            long between = DateUtil.between(createTime, completionTime, DateUnit.MINUTE);
            totalMinutes = totalMinutes.add(new BigDecimal(between));
        }

        BigDecimal avgTimeHours = totalMinutes.divide(new BigDecimal(list.size() * 60), 2, RoundingMode.HALF_UP);
        statResult.setAvgTimeHours(avgTimeHours);

        return list.stream().map(RuntimeInstance::getInstanceId).collect(Collectors.toList());
    }

    private void setApprovalCount(StatResult statResult, List<Long> instanceIdList) {

        if (ObjectUtil.isEmpty(instanceIdList)) {
            statResult.setApprovalCount(0);
            return;
        }

        LambdaQueryWrapper<RuTask> ruTaskLambdaQueryWrapper = new LambdaQueryWrapper<>();
        ruTaskLambdaQueryWrapper.in(RuTask::getInstanceId, instanceIdList);
        ruTaskLambdaQueryWrapper.select(RuTask::getApprover);
        List<RuTask> list = this.ruTaskService.list(ruTaskLambdaQueryWrapper);
        if (ObjectUtil.isEmpty(list)) {
            statResult.setApprovalCount(0);
            return;
        }

        HashSet<Long> approveUserList = new HashSet<>();
        for (RuTask ruTask : list) {
            approveUserList.add(ruTask.getApprover());
        }

        statResult.setApprovalCount(list.size());

        statResult.setApprovalUserCount(approveUserList.size());
    }

}
