package com.ximai.mes.pro.schedule.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ximai.common.exception.ServiceException;
import com.ximai.common.utils.MessageUtils;
import com.ximai.mes.cal.domain.CalPlanWorkunit;
import com.ximai.mes.cal.service.ICalPlanWorkunitService;
import com.ximai.mes.md.service.IMdWorkunitService;
import com.ximai.mes.md.vo.MdWorkunitVo;
import com.ximai.mes.pro.domain.proWorkOrder.ProWorkOrderProcess;
import com.ximai.mes.pro.domain.proWorkOrder.ProWorkorder;
import com.ximai.mes.pro.domain.task.ProTaskWorkunit;
import com.ximai.mes.pro.domain.task.WorkorderScheduleParams;
import com.ximai.mes.pro.schedule.*;
import com.ximai.mes.pro.service.proWorkOrder.IProWorkOrderProcessService;
import com.ximai.mes.pro.service.proWorkOrder.IProWorkorderService;
import com.ximai.mes.pro.service.productionSolution.IProProductionSolutionProcessService;
import com.ximai.mes.pro.service.productionSolution.IProProductionSolutionService;
import com.ximai.mes.pro.service.productionSolution.IProProductionSolutionSpecificationSheetService;
import com.ximai.mes.pro.service.task.IProTaskWorkunitService;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 加载排产业务数据实现
 */
@Component
public class AlgorithmDataSourceThXMImpl implements AlgorithmDataSource {

    @Autowired
    IMdWorkunitService mdWorkunitService;
    @Autowired
    ICalPlanWorkunitService calPlanWorkunitService;
    @Autowired
    IProWorkorderService proWorkorderService;
    @Autowired
    IProWorkOrderProcessService proWorkOrderProcessService;
    @Autowired
    IProTaskWorkunitService proTaskWorkunitService;
    @Autowired
    IProProductionSolutionService proProductionSolutionService;
    @Autowired
    IProProductionSolutionProcessService proProductionSolutionProcessService;
    @Autowired
    IProProductionSolutionSpecificationSheetService proProductionSolutionSpecificationSheetService;

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public List<Resource> getResource(LocalDateTime schedulingStartedDate, List<WorkorderScheduleParams> workorderScheduleParams) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        QueryWrapper<MdWorkunitVo> query = new QueryWrapper<MdWorkunitVo>();
        query.eq("t1.enable_flag", "Y");
        query.isNotNull("t1.workstation_id");
        List<Equipment> algEquipments = new ArrayList<Equipment>();
        List<MdWorkunitVo> workunitList = mdWorkunitService.selectMdWorkunitJoinWorkstation(query);
        //查询作业单元
        for (MdWorkunitVo workUnit : workunitList) {
            Equipment equipment = new Equipment() {{
                setId(workUnit.getWorkunitId());
                setWorkUnitCode(workUnit.getWorkunitCode());
                setWorkUnitName(workUnit.getWorkunitName());
                setWorkCenterId(workUnit.getWorkstationId());
                setWorkCenterCode(workUnit.getWorkstationCode());
                setWorkCenterName(workUnit.getWorkstationName());
                setSerial(workUnit.getSerial());
                if(workUnit.getTransferTime()!=null){
                    setTransferShopTime(workUnit.getTransferTime().intValue());
                }
                if(workUnit.getSolidifyTime()!=null){
                    setCuringTime(workUnit.getSolidifyTime().intValue());
                }
                setIsParallel(false);
            }};
            equipment.initEquipmentAttribute(workUnit);
            equipment.initEquipmentFunction(workUnit);
            algEquipments.add(equipment);
        }
        stopWatch.stop();
        logger.info(String.format("排产设备加载耗时：%s", stopWatch.getTotalTimeSeconds()));
        return algEquipments.stream().map(s -> (Resource) s).collect(Collectors.toList());
    }

    @Override
    public List<Job> getJob(LocalDateTime scheduleStartedDate, List<WorkorderScheduleParams> workorderScheduleParams, List<Resource> resources) {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        List<Job> algJobs = new ArrayList<Job>();
        //查询相关工单，工单编排单号也需一并查询出来
        QueryWrapper<ProWorkorder> queryWrapper = new QueryWrapper<ProWorkorder>();
        queryWrapper.in("workorder_id", workorderScheduleParams.stream().map(s->s.getWorkorderId()).collect(Collectors.toList()));
        List<ProWorkorder> workorderList = proWorkorderService.selectListByQw(queryWrapper);
        Map<Long,WorkorderScheduleParams> workorderScheduleParamsMap= workorderScheduleParams.stream().collect(Collectors.toMap(s->s.getWorkorderId(),s->s));
        List<ScheduleJobGroup> scheduleJobGroupList = new ArrayList<ScheduleJobGroup>();
        for (ProWorkorder workorder : workorderList) {
            ScheduleJobGroup scheduleJobGroup = new ScheduleJobGroup();
            List<ProWorkorder> list = new ArrayList<>();
            list.add(workorder);
            scheduleJobGroup.setWorkorderList(list);
            if(ObjectUtil.isEmpty(workorder.getRequestDate())){
                throw new ServiceException(MessageUtils.message("pro.workOrder.error.not.null.requestDate"));
            }
            LocalDateTime recentDate = list.stream().map(s -> DateUtil.toLocalDateTime(s.getRequestDate())).min(Comparator.comparing(LocalDateTime::toLocalDate)).get();
            WorkorderScheduleParams params = workorderScheduleParamsMap.get(workorder.getWorkorderId());
            scheduleJobGroup.setQuantity(params.getScheduleQuantity());
            scheduleJobGroup.setDemandDate(recentDate);
            scheduleJobGroup.setWorkorderCode(workorder.getWorkorderCode());
            scheduleJobGroup.setScheduleDate(params.getScheduleDate());
            scheduleJobGroupList.add(scheduleJobGroup);
        }
        //按交期近，数量少排序
        scheduleJobGroupList.sort(Comparator.comparing(ScheduleJobGroup::getDemandDate).thenComparing(ScheduleJobGroup::getQuantity));
        Long i = 0l;
        StringBuilder msg = new StringBuilder();
        for (ScheduleJobGroup scheduleJobGroup : scheduleJobGroupList) {
            ProWorkorder firstWorkorder = scheduleJobGroup.getWorkorderList().get(0);
            QueryWrapper<ProWorkOrderProcess> processQuery = new QueryWrapper<ProWorkOrderProcess>();
            processQuery.eq("t1.workorder_id", firstWorkorder.getWorkorderId());
            processQuery.orderByAsc("t1.idx");
            List<ProWorkOrderProcess> productionOrderRoutings = proWorkOrderProcessService.selectListByQw(processQuery);
            if(productionOrderRoutings.size() < 1){
                msg.append(MessageUtils.message("pro.schedule.error.not.exist.process", firstWorkorder.getWorkorderCode()));
                msg.append(System.lineSeparator());
                continue;
            }
            // 工序分组 生成Job及Task
            Job job = new Job(scheduleJobGroup.getWorkorderCode(), Duration.between(scheduleJobGroup.getScheduleDate(), DateUtil.date(scheduleJobGroup.getDemandDate()).toLocalDateTime())) {{
                setFixed(false);
            }};
            JobExtent jobExtent = new JobExtent();
            jobExtent.setWorkorderList(scheduleJobGroup.getWorkorderList());
            job.setJobExtend(jobExtent);
            job.getLmtStartedTime().addOptional(Duration.ZERO);
            job.getLmtEndedTime().addOptional(Duration.between(scheduleJobGroup.getScheduleDate(), DateUtil.date(scheduleJobGroup.getDemandDate()).toLocalDateTime()));
            job.setQuantity(scheduleJobGroup.getQuantity());
            job.setScheduleDate(scheduleJobGroup.getScheduleDate());
            for (ProWorkOrderProcess routingItem : productionOrderRoutings) {
                Task task = new Task(i, job);
                task.setStandardWorkingTime(Duration.ZERO);
                task.setSetupTime(Duration.ZERO);
                task.setProcessId(routingItem.getProcessId());
                task.setUnitPrice(routingItem.getUnitPrice());
                task.setStdWorkingTime(routingItem.getStdWorkingTime());
                task.setStdWorkingTimeUom(routingItem.getStdWorkingTimeUom());
                task.setIdx(routingItem.getIdx());
                task.setProcessName(routingItem.getProcessName());
                task.setOrderQuantity(scheduleJobGroup.getQuantity());
                task.setWorkCenterId(routingItem.getWorkstationId());
                task.setWorkCenterName(routingItem.getWorkstationName());
                if("Y".equalsIgnoreCase(routingItem.getOutsourced())){
                    task.setOutsourced(true);
                }
                //HOUR\SECOND\MINUTE
                Long unitMultiple = 0l;
                if ("HOUR".equalsIgnoreCase(routingItem.getStdWorkingTimeUom())) {
                    unitMultiple = unitMultiple * 60;
                } else if ("SECOND".equalsIgnoreCase(routingItem.getStdWorkingTimeUom())) {
                    unitMultiple = 1000l * 1000 * 1000;
                } else if("MIN".equalsIgnoreCase(routingItem.getStdWorkingTimeUom())){
                    unitMultiple = 1000l * 1000 * 1000 * 60;
                }else {
                    throw new ServiceException(MessageUtils.message("pro.schedule.error.canNot.use.standardTime", routingItem.getStdWorkingTimeUom()));
                }
                if (routingItem.getStdWorkingTime() != null) {
                    //单件标准工时
                    Long standardSd = routingItem.getStdWorkingTime().multiply(BigDecimal.valueOf(unitMultiple)).longValue();
                    if (routingItem.getQuantity() != null) {
                        standardSd = standardSd / routingItem.getQuantity().longValue();
                    }
                    task.setStandardWorkingTime(Duration.ofNanos(standardSd));
                }
                if (routingItem.getLineBreakTime() != null) {
                    task.setSetupTime(Duration.ofNanos(routingItem.getLineBreakTime().multiply(BigDecimal.valueOf(unitMultiple)).longValue()));
                }
                task.getTaskData().put("SeqNo", routingItem.getProcessRef());
                job.getTasks().add(task);
                i++;
            }
            algJobs.add(job);
        }
        if(msg.length() > 0){
            throw new ServiceException(msg.toString());
        }
        stopWatch.stop();
        logger.info(String.format("待排产数据加载耗时：%s", stopWatch.getTotalTimeSeconds()));
        return algJobs;
    }

    @Override
    public List<Calendar> getCalendar(LocalDateTime schedulingStartedDate, List<WorkorderScheduleParams> workorderScheduleParams, List<Resource> resources) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Map<Long, Resource> resourceMap = resources.stream().collect(Collectors.toMap(s -> s.getId(), s -> s));
        List<Calendar> algCalendar = new ArrayList<Calendar>();
        QueryWrapper<CalPlanWorkunit> calPlanWorkunitQuery = new QueryWrapper<CalPlanWorkunit>();
        calPlanWorkunitQuery.eq("rest_flag", 0);
        calPlanWorkunitQuery.gt("start_date", schedulingStartedDate);
        calPlanWorkunitQuery.orderByAsc("start_date");
        //查询可用工作日历
        List<CalPlanWorkunit> list = calPlanWorkunitService.selectListWithSchedule(calPlanWorkunitQuery);
        for (CalPlanWorkunit etpCal : list) {
            if (!resourceMap.containsKey(etpCal.getWorkunitId())) {
                continue;
            }
            Calendar.WorkingCalendar alg_CalendarItem = new Calendar.WorkingCalendar() {{
                setStartedTime(Duration.between(schedulingStartedDate, DateUtil.toLocalDateTime(etpCal.getStartDate())));
                setEndedTime(Duration.between(schedulingStartedDate, DateUtil.toLocalDateTime(etpCal.getEndDate())));
                setResource(resourceMap.get(etpCal.getWorkunitId()));
                setStartedTime2(DateUtil.toLocalDateTime(etpCal.getStartDate()));
                setEndedTime2(DateUtil.toLocalDateTime(etpCal.getEndDate()));
                setShiftType(etpCal.getShiftType());
            }};
            alg_CalendarItem.getCalendarData().put("CalendarID", etpCal.getId());
            algCalendar.add(alg_CalendarItem);
        }
        stopWatch.stop();
        logger.info(String.format("排产日历数据加载耗时：%s", stopWatch.getTotalTimeSeconds()));
        return algCalendar;
    }

    //按编排单号分组的工单数据
    @Data
    public class ScheduleJobGroup {
        List<ProWorkorder> workorderList;
        //最近交期
        LocalDateTime demandDate;
        //排产开始日期
        LocalDateTime scheduleDate;
        //总数量
        BigDecimal quantity;
        //工单单号
        String workorderCode;
    }

}
