package org.jeecg.modules.plan.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.AllArgsConstructor;
import org.jeecg.modules.device.entity.BuDevice;
import org.jeecg.modules.device.mapper.BuDeviceMapper;
import org.jeecg.modules.plan.entity.Plan;
import org.jeecg.modules.plan.mapper.PlanMapper;
import org.jeecg.modules.plan.modal.PlanBo;
import org.jeecg.modules.plan.modal.DevicePlanVo;
import org.jeecg.modules.plan.service.IPlanService;
import org.jeecg.modules.regular.entity.Regular;
import org.jeecg.modules.regular.mapper.RegularMapper;
import org.jeecg.modules.system.service.ISysDictItemService;
import org.jeecg.modules.workOrder.entity.WorkOrder;
import org.jeecg.modules.workOrder.mapper.WorkOrderMapper;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static java.time.LocalDate.now;

/**
 * @Description: 检修计划
 * @Author: jeecg-boot
 * @Date:   2025-01-16
 * @Version: V1.0
 */
@AllArgsConstructor
@Service
public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements IPlanService {

    private BuDeviceMapper deviceMapper;
    private RegularMapper regularMapper;
    private WorkOrderMapper workOrderMapper;
    private ISysDictItemService dictItemService;
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(Plan plan){
        if(baseMapper.selectCount(new LambdaQueryWrapper<Plan>().eq(Plan::getDeviceId, plan.getDeviceId()).eq(Plan::getRapairRange, plan.getRapairRange()).eq(Plan::getCalDay,plan.getCalDay()))>0){
            throw new RuntimeException("当天存在相同计划不允许新增！");
        }
        plan.setCalWeek(plan.getCalDay().getDayOfWeek().getValue());
        int count = Math.toIntExact(baseMapper.selectCount(new LambdaQueryWrapper<Plan>().eq(Plan::getDeviceId, plan.getDeviceId()).eq(Plan::getRapairRange, plan.getRapairRange())));
        plan.setOrderNum(++count);
        return super.save(plan);
    }

    @Override
    public List<Plan> queryList(LambdaQueryWrapper<Plan> queryWrapper){
        List<Plan> pageList = super.list(queryWrapper.orderByAsc(Plan::getCalDay));
        if (ObjectUtil.isEmpty(pageList)) {
            return pageList;
        }
        Set<String> planIds = pageList.stream().map(Plan::getId).collect(Collectors.toSet());
        List<WorkOrder> workOrders = workOrderMapper.selectList(new LambdaQueryWrapper<WorkOrder>()
                .in(WorkOrder::getPlanId, planIds).eq(WorkOrder::getDeleteFlag,0)
                .gt(WorkOrder::getCreateTime, DateUtil.offsetMonth(new Date(),-1)));
        Map<String,WorkOrder> workOrderMap = workOrders.stream().collect(Collectors.toMap(WorkOrder::getPlanId, w -> w));
        pageList.forEach(plan -> {
            plan.setWorkOrder(workOrderMap.get(plan.getId()));
        });
        return pageList;
    }

    @Override
    public List<DevicePlanVo> queryRegularPlan() {

        Map<String, Long> plans = baseMapper.queryRegularPlan().stream()
                .collect(Collectors.toMap(
                        map -> String.valueOf(map.get("device_id")),
                        map -> map.get("num")
                ));
        List<BuDevice> deviceList = deviceMapper.selectList(new LambdaQueryWrapper<>());
        // 获取检修规程类型字典
        Map<String,String> regulerDictMap = dictItemService.selectItemsByDictCode("repair_range");

        return deviceList.stream().map(device -> {
            DevicePlanVo devicePlanVo = new DevicePlanVo();
            devicePlanVo.setDeviceId(device.getId());
            devicePlanVo.setDeviceName(device.getDeviceName()+"("+device.getDeviceCode()+")");
            devicePlanVo.setReliPlanNum(plans.getOrDefault(device.getId(), 0L).intValue());
            // 查询计划，获取下一条和当天的计划
            List<Plan> planList = baseMapper.selectList(new LambdaQueryWrapper<Plan>()
                    .eq(Plan::getDeviceId, device.getId())
                    .ge(Plan::getCalDay,LocalDate.now())
                    .orderByAsc(Plan::getCalDay)
                    .last("limit 2"));
            for (Plan plan : planList) {
                if (plan.getCalDay().isEqual(LocalDate.now())) {
                    devicePlanVo.setNowPlan(regulerDictMap.get(plan.getRapairRange().toString())+"("+plan.getCalDay().format(formatter)+")");
                } else {
                    devicePlanVo.setNextPlan(regulerDictMap.get(plan.getRapairRange().toString())+"("+plan.getCalDay().format(formatter)+")");
                }
            }
            return devicePlanVo;
        }).collect(Collectors.toList());
    }

    /**
     * 计划生成方法
     *
     * @param bo 计划生成要求
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean generatePlan(PlanBo bo) throws Exception {
        // 需要生成计划的设备
        List<BuDevice> deviceList = deviceMapper.selectList(new LambdaQueryWrapper<BuDevice>()
                .eq(bo.getDeviceTypeId() != null, BuDevice::getDeviceTypeId,bo.getDeviceTypeId()));
        // 获取检修规程类型字典
        Map<String,String> regulerDictMap = dictItemService.selectItemsByDictCode("repair_range");
        // 默认结束日期为下一年的12月31日
        LocalDate endDate = ObjectUtil.defaultIfNull(bo.getEndDate(), LocalDate.of(now().getYear() + 1, 12, 31));
        int dayNum = (int) ChronoUnit.DAYS.between(LocalDate.now(), endDate);

        // 处理每一个设备
        for(BuDevice device : deviceList) {
            // 查询设备的需要生成计划的规程
            List<Regular> deviceRegularList = regularMapper.selectList(new LambdaQueryWrapper<Regular>()
                    .eq(Regular::getDeviceTypeId, device.getDeviceTypeId())
                    .eq(bo.getRapairRange() != null, Regular::getRapairRange, bo.getRapairRange())
                    .orderByDesc(Regular::getRapairRange));

            Set<LocalDate> planRegulars = new HashSet<>();
            for(Regular regularVo : deviceRegularList) {
                // 查询生成后续计划的起点
                // 如果指定了从某个日期开始生成，则将当天之后的第一个检修计划作为起点，重新生成后续计划
                // 如果没有指定日期，则将已有计划的最后一个计划作为后续计划的起点，生成后续计划
                LambdaQueryWrapper<Plan> queryWrapper = new LambdaQueryWrapper<Plan>()
                        .eq(Plan::getDeviceId, device.getId())
                        .eq(Plan::getRapairRange, regularVo.getRapairRange())
                        .ge(bo.getCalDay() != null, Plan::getCalDay, bo.getCalDay())
                        .orderByAsc(bo.getCalDay() != null, Plan::getCalDay)
                        .orderByDesc(bo.getCalDay() == null, Plan::getCalDay);
                Plan devicePlan = baseMapper.selectOne(queryWrapper.last("limit 1"));

                if(devicePlan == null && dayNum<regularVo.getMaintenanceCycle()+regularVo.getBias()) {
                    // 如果指定的结束日期与当前日期的间隔小于当前计划周期，则跳过
                    continue;
                }
                // 检查是否找到有效的计划
                if(bo.getCalDay() != null) {
                    if(devicePlan == null) {
                        throw new Exception(String.format("设备[%s]，在[%s]后没有可参考的[%s]计划，请确认后重新生成！",
                                device.getDeviceName(),
                                bo.getCalDay().format(formatter),
                                regulerDictMap.get(regularVo.getRapairRange().toString())));
                    }
                    baseMapper.delete(queryWrapper);
                }

                // 批量创建新计划
                List<Plan> devicePlanList = new ArrayList<>();
                // 检修计划年次序
                int nextOrder = devicePlan == null || devicePlan.getOrderNum()==null ? 0 : devicePlan.getOrderNum();
                // 初始检修作业日期
                LocalDate planDate = devicePlan != null ? devicePlan.getCalDay() : now();
                // 根据[结束日期]按时间顺序生成检修计划
                while (!planDate.isAfter(endDate)) {
                    nextOrder = nextOrder % 12 + 1;
                    // 如果日期已存在则跳过
                    if (planRegulars.add(planDate)) {
                        // 添加成功说明当天没有安排更高的检修计划，生成新的计划
                        LocalDate workDate = planDate.plusDays(regularVo.getMaintenanceCycle()-1);
                        int workDays = 0;//以安排计划的天数(在单个检修任务中计算)
                        while (workDays < regularVo.getDuration()) {
                            // 跳过周末
                            if (workDate.getDayOfWeek().getValue() <= 5) {
                                Plan plan = new Plan(device.getId(), regularVo.getRapairRange(),
                                        workDate,// 检修作业日期
                                        workDate.getDayOfWeek().getValue(),// 获取数字形式(1-7，周一是1，周日是7)
                                        nextOrder);
                                devicePlanList.add(plan);
                                workDays++;// 如果成功创建检修计划对象则已安排的计划天数加一
                            }
                            // 无论当天是否安排计划，日期加一天
                            workDate = workDate.plusDays(1);
                        }
                    }
                    // 无论是否安排计划，跳转到下一个检修作业日期
                    planDate = planDate.plusDays(regularVo.getMaintenanceCycle());
                }

                // 批量插入
                if (!devicePlanList.isEmpty()) {
                    saveBatch(devicePlanList);
                }
            }
        }
        return true;
    }

    /**
     * 在表格中拖拽移动计划Plan(源计划id,目标单元格deviceId,目标单元格calDay)
     * @param plan
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void movePlan(Plan plan) {

        // 1. 查询源计划
        Plan sourcePlan = baseMapper.selectById(plan.getId());
        if (sourcePlan == null) {
            throw new RuntimeException("源计划不存在，ID=" + plan.getId());
        }

        // 2. 查询目标单元格是否已有计划
        Plan targetPlan = baseMapper.selectOne(new LambdaQueryWrapper<Plan>()
                .eq(Plan::getDeviceId, plan.getDeviceId())
                .eq(Plan::getCalDay, plan.getCalDay()));

        if (targetPlan != null) {
            // 3. 目标单元格有计划 → 交换 deviceId 和 calDay
            String tempDeviceId = sourcePlan.getDeviceId();
            LocalDate tempCalDay = sourcePlan.getCalDay();

            // 更新源计划为目标单元格
            sourcePlan.setDeviceId(targetPlan.getDeviceId());
            sourcePlan.setCalDay(targetPlan.getCalDay());
            baseMapper.updateById(sourcePlan);

            // 更新目标计划为源单元格
            targetPlan.setDeviceId(tempDeviceId);
            targetPlan.setCalDay(tempCalDay);
            baseMapper.updateById(targetPlan);

        } else {
            // 4. 目标单元格无计划 → 直接更新源计划
            sourcePlan.setDeviceId(plan.getDeviceId());
            sourcePlan.setCalDay(plan.getCalDay());
            baseMapper.updateById(sourcePlan);
        }
    }

}
