package cn.iocoder.yudao.module.pms.service.iotmainworkorderbom;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.iotdevicerunlog.vo.IotDeviceRunLogRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbom.IotMainWorkOrderBomDO;
import cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorderbom.IotMainWorkOrderBomMapper;
import cn.iocoder.yudao.module.pms.enums.ErrorCodeConstant;
import cn.iocoder.yudao.module.pms.service.IotDeviceService;
import cn.iocoder.yudao.module.pms.service.iotdevicebom.IotDeviceBomService;
import cn.iocoder.yudao.module.pms.service.iotdevicerunlog.IotDeviceRunLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Stream;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;

/**
 * PMS 保养计划明细BOM Service 实现类
 *
 * @author ruiqi
 */
@Service
@Validated
public class IotMainWorkOrderBomServiceImpl implements IotMainWorkOrderBomService {

    @Resource
    private IotMainWorkOrderBomMapper iotMainWorkOrderBomMapper;
    @Autowired
    private IotDeviceRunLogService iotDeviceRunLogService;
    @Autowired
    private IotDeviceBomService iotDeviceBomService;
    @Resource
    private IotDeviceService iotDeviceService;

    @Override
    public Long createIotMainWorkOrderBom(IotMainWorkOrderBomSaveReqVO createReqVO) {
        // 插入
        IotMainWorkOrderBomDO iotMainWorkOrderBom = BeanUtils.toBean(createReqVO, IotMainWorkOrderBomDO.class);
        iotMainWorkOrderBomMapper.insert(iotMainWorkOrderBom);
        // 返回
        return iotMainWorkOrderBom.getId();
    }

    @Override
    public void updateIotMainWorkOrderBom(IotMainWorkOrderBomSaveReqVO updateReqVO) {
        // 校验存在
        validateIotMainWorkOrderBomExists(updateReqVO.getId());
        // 更新
        IotMainWorkOrderBomDO updateObj = BeanUtils.toBean(updateReqVO, IotMainWorkOrderBomDO.class);
        iotMainWorkOrderBomMapper.updateById(updateObj);
    }

    @Override
    public void deleteIotMainWorkOrderBom(Long id) {
        // 校验存在
        validateIotMainWorkOrderBomExists(id);
        // 删除
        iotMainWorkOrderBomMapper.deleteById(id);
    }

    private void validateIotMainWorkOrderBomExists(Long id) {
        if (iotMainWorkOrderBomMapper.selectById(id) == null) {
            throw exception(ErrorCodeConstant.IOT_MAIN_WORK_ORDER_BOM_NOT_EXISTS);
        }
    }

    @Override
    public IotMainWorkOrderBomDO getIotMainWorkOrderBom(Long id) {
        return iotMainWorkOrderBomMapper.selectById(id);
    }

    @Override
    public PageResult<IotMainWorkOrderBomDO> getIotMainWorkOrderBomPage(IotMainWorkOrderBomPageReqVO pageReqVO) {
        return iotMainWorkOrderBomMapper.selectPage(pageReqVO);
    }

    @Override
    public List<IotMainWorkOrderBomDO> getIotMainWorkOrderBomList(IotMainWorkOrderBomPageReqVO pageReqVO) {
        List<IotMainWorkOrderBomDO> workOrderBomS = iotMainWorkOrderBomMapper.selectList(pageReqVO);
        // 查询所有BOM节点id 查询出每个节点所有的上级节点 替换节点名称
        List<Long> bomNodeIds = new ArrayList<>();
        if (CollUtil.isNotEmpty(workOrderBomS)) {
            workOrderBomS.forEach(bom -> {
                bomNodeIds.add(bom.getBomNodeId());
            });
            // 查询 bomNodeIds 集合中每个bom节点的所有上级节点
            Map<Long, String> bomFullPaths = iotDeviceBomService.buildBomFullPaths(bomNodeIds);
            workOrderBomS.forEach(bom -> {
                if (bomFullPaths.containsKey(bom.getBomNodeId())) {
                    if (StrUtil.isNotBlank(bomFullPaths.get(bom.getBomNodeId()))) {
                        bom.setName(bomFullPaths.get(bom.getBomNodeId()));
                    }
                }
            });
        }
        return workOrderBomS;
    }

    @Override
    public List<IotMainWorkOrderBomDO> maintenanceCumulativeValue(IotMainWorkOrderBomPageReqVO pageReqVO) {
        List<IotMainWorkOrderBomDO> workOrderBomS = iotMainWorkOrderBomMapper.selectList(pageReqVO);
        return workOrderBomS;
    }

    @Override
    public Map<Long, String> mainWorkOrderNearestDistance(IotMainWorkOrderBomPageReqVO pageReqVO) {
        List<IotMainWorkOrderBomDO> workOrderBomS = getIotMainWorkOrderBomList(pageReqVO);
        Map<Long, String> resultMap = new HashMap<>();
        // 查找所有保养工单明细 继承自 保养计划明细 的 code累计运行时长属性名称 type累计运行公里数属性名称
        // 运行记录模板中 多种 累计时长 公里数 属性名称集合
        Set<String> boundedMultiAttrNames = new HashSet<>();
        // 遍历工单明细 找出每个工单中 触发保养时间最近的 时间/里程/自然日
        if (CollUtil.isNotEmpty(workOrderBomS)) {
            Set<Long> deviceIds = new HashSet<>();
            Set<Long> deviceCategoryIds = new HashSet<>();
            Map<Long, Long> deviceCategoryPair = new HashMap<>();
            workOrderBomS.forEach(bom -> {
                deviceIds.add(bom.getDeviceId());
                if (StrUtil.isNotBlank(bom.getCode())) {
                    boundedMultiAttrNames.add(bom.getCode());
                }
                if (StrUtil.isNotBlank(bom.getType())) {
                    boundedMultiAttrNames.add(bom.getType());
                }
            });
            // 组装bom关联的设备信息
            Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(workOrderBomS,
                    bom -> Stream.of(bom.getDeviceId())));
            if (CollUtil.isNotEmpty(deviceMap)) {
                deviceMap.forEach((k,v) -> {
                    deviceCategoryIds.add(v.getAssetClass());
                    deviceCategoryPair.put(k, v.getAssetClass());
                });
            }
            // 查询 保养工单保养明细中已经绑定的 多种累计时长 公里数 属性运行记录值集合
            List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
            if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
                multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames, null);
            }
            // key(设备id-累计时长属性名称)    value时长属性累计时长数值
            Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
            if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
                multipleAccumulatedData.forEach(data -> {
                    String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
                    tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
                });
            }

            // 查询当前所有设备的 累计运行里程 累计运行时间
            Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMapAlone(new ArrayList<>(deviceIds),
                    new ArrayList<>(deviceIds), deviceCategoryPair, null);
            Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
            workOrderBomS.forEach(bom -> {
                BigDecimal runningTimeDistance = null;
                BigDecimal runningKiloDistance = null;
                BigDecimal naturalDateDistance = null;
                // 运行记录模板中维护的属性 填报的累计值
                BigDecimal totalRunTime = BigDecimal.ZERO;
                BigDecimal totalMileage = BigDecimal.ZERO;
                if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                    // 正常的累计时长 公里数
                    totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
                    totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
                } else {
                    // 保养项绑定的 累计时长 类型的属性值
                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
                    if (tempTotalRunDataPair.containsKey(uniqueKey)) {
                        totalRunTime = tempTotalRunDataPair.get(uniqueKey);
                    }
                    // 保养项绑定的 累计公里数类型的属性值
                    String uniqueKeyType = StrUtil.join("-", bom.getDeviceId(), bom.getType());
                    if (tempTotalRunDataPair.containsKey(uniqueKeyType)) {
                        totalMileage = tempTotalRunDataPair.get(uniqueKeyType);
                    }
                }
                // 计算每个保养项 每个保养规则下的 距离保养时间
                if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule()) {
                    // 运行时间保养规则
                    // if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        // BigDecimal totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
                        BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
                        BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
                        BigDecimal timePeriodLead = bom.getTimePeriodLead();        // 运行时间周期提前量
                        BigDecimal delayDuration = bom.getDelayDuration();          // 推迟运行时长H
                        // runningTimeDistance = (lastRunningTime.add(runningTimePeriod).subtract(timePeriodLead)).subtract(totalRunTime);
                        runningTimeDistance = (runningTimePeriod.subtract(totalRunTime.subtract(lastRunningTime))).add(delayDuration);
                    // }
                }
                if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule()) {
                    // 运行里程保养规则
                    // 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
                    // if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
                        // BigDecimal totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
                        BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
                        BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
                        BigDecimal kiloCycleLead = bom.getKiloCycleLead();                // 运行里程周期提前量
                        BigDecimal delayKilometers = bom.getDelayKilometers();            // 推迟运行公里数KM
                        // runningKiloDistance = (lastRunningKilo.add(runningKiloPeriod).subtract(kiloCycleLead)).subtract(totalMileage);
                        runningKiloDistance = (runningKiloPeriod.subtract(totalMileage.subtract(lastRunningKilo))).add(delayKilometers);
                    // }
                }
                if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 0 == bom.getNaturalDateRule()) {
                    // 自然日期保养规则
                    LocalDateTime lastNaturalDate = bom.getLastNaturalDate();      // 上次保养自然日期
                    BigDecimal naturalDatePeriod = bom.getNextNaturalDate();       // 自然日周期
                    BigDecimal natualDateLead = bom.getNaturalDatePeriodLead();    // 自然日周期提前量
                    BigDecimal delayNaturalDate = bom.getDelayNaturalDate();       // 推迟自然日期D
                    if (ObjUtil.isNotEmpty(lastNaturalDate) && ObjUtil.isNotEmpty(naturalDatePeriod) && ObjUtil.isNotEmpty(natualDateLead)) {
                        // 计算有效天数 = 自然日周期 - 提前量 + 推迟自然日期D
                        long days = (naturalDatePeriod).add(delayNaturalDate).longValue();     // 转为长整型天数
                        // 计算目标日期：上次保养日期 + 有效天数（注意：LocalDateTime加天数会自动处理日期进位）
                        LocalDateTime targetDate = lastNaturalDate.plusDays(days);
                        // 获取当前日期时间
                        LocalDateTime now = LocalDateTime.now();
                        // 计算日期差值（以天为单位）
                        naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(now, targetDate));
                    }
                }
                // 找出距离0最近的数，作为工单的最近保养距离数据
                Object closet = chooseNearestDistance(runningTimeDistance, runningKiloDistance, naturalDateDistance);
                Map<String, Object> tempDistance = new HashMap<>();
                if (closet == runningTimeDistance) {
                    tempDistance.put("H", closet);
                } else if (closet == runningKiloDistance) {
                    tempDistance.put("KM", closet);
                } else if (closet == naturalDateDistance) {
                    tempDistance.put("D", closet);
                }
                if (orderDistancePair.containsKey(bom.getWorkOrderId())) {
                    List<Map<String, Object>> tempDistances = orderDistancePair.get(bom.getWorkOrderId());
                    tempDistances.add(tempDistance);
                    orderDistancePair.put(bom.getWorkOrderId(), tempDistances);
                } else {
                    List<Map<String, Object>> tempDistances = new ArrayList<>();
                    tempDistances.add(tempDistance);
                    orderDistancePair.put(bom.getWorkOrderId(), tempDistances);
                }
            });
            // 以保养工单id 为维度 统计每个保养工单明细中 距离最近的保养数据
            resultMap = findClosestToZero(orderDistancePair);
        }
        return resultMap;
    }

    /**
     * 找出每个工单对应的集合中 距离时间最短的 数据
     * @param orderDistancePair
     */
    private Map<Long, String> findClosestToZero(Map<Long, List<Map<String, Object>>> orderDistancePair){
        Map<Long, String> result = new HashMap<>();
        for (Map.Entry<Long, List<Map<String, Object>>> entry : orderDistancePair.entrySet()) {
            Long workOrderId = entry.getKey();
            List<Map<String, Object>> mapList = entry.getValue();
            // 存储所有有效值（原始值、单位、转换值）
            List<Triple<BigDecimal, String, BigDecimal>> allValues = new ArrayList<>();
            // 收集所有值并进行单位转换
            for (Map<String, Object> map : mapList) {
                for (Map.Entry<String, Object> valueEntry : map.entrySet()) {
                    if (valueEntry.getValue() instanceof BigDecimal) {
                        BigDecimal value = (BigDecimal) valueEntry.getValue();
                        String unit = valueEntry.getKey();
                        BigDecimal convertedValue;
                        // 小时转换为天
                        if ("H".equals(unit)) {
                            convertedValue = value.divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
                        } else {
                            convertedValue = value;
                        }
                        allValues.add(new Triple<>(value, unit, convertedValue));
                    }
                }
            }
            // 如果没有有效值，跳过
            if (allValues.isEmpty()) {
                continue;
            }
            // 找到转换后值最小的值
            Triple<BigDecimal, String, BigDecimal> minTriple = allValues.get(0);
            for (Triple<BigDecimal, String, BigDecimal> triple : allValues) {
                if (triple.getThird().compareTo(minTriple.getThird()) < 0) {
                    minTriple = triple;
                }
            }
            // 格式化为字符串: "原始值 单位"
            result.put(workOrderId, minTriple.getFirst() + " " + minTriple.getSecond());
        }
        return result;
    }

    /**
     * 将Map转换为String格式：value1 key1; value2 key2
     */
    private String convertMapToString(Map<String, Object> map) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (sb.length() > 0) {
                sb.append(";");  // 添加分隔符
            }
            String key = entry.getKey();
            Object value = entry.getValue();

            // 处理null值
            String valueStr = (value != null) ? value.toString() : "null";

            // 转义特殊字符（如有需要）
            sb.append(valueStr).append(" ").append(key);
        }
        return sb.toString();
    }

    /**
     * 计算单个Map中所有BigDecimal值的最小绝对值
     * @param map
     * @return
     */
    private BigDecimal calculateMinAbsolute(Map<String, Object> map) {
        BigDecimal minAbs = null;

        for (Object value : map.values()) {
            if (!(value instanceof BigDecimal)) continue;

            BigDecimal decimalValue = (BigDecimal) value;
            BigDecimal absValue = decimalValue.abs();

            if (minAbs == null || absValue.compareTo(minAbs) < 0) {
                minAbs = absValue;
            }
        }
        return minAbs; // 可能为null（无BigDecimal值）
    }

    private BigDecimal chooseNearestDistance(BigDecimal runningTimeDistance, BigDecimal runningKiloDistance, BigDecimal naturalDateDistance){
        // 存储当前找到的最小绝对值的变量
        // 存储所有非空值及其转换值（小时->天）
        List<Triple<BigDecimal, String, BigDecimal>> values = new ArrayList<>();
        if (runningTimeDistance != null) {
            // 转换为天数用于比较，但保留原始值
            BigDecimal converted = runningTimeDistance.divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
            values.add(new Triple<>(runningTimeDistance, "H", converted));
        }
        if (runningKiloDistance != null) {
            // 公里值不转换
            values.add(new Triple<>(runningKiloDistance, "KM", runningKiloDistance));
        }
        if (naturalDateDistance != null) {
            // 自然日值不转换
            values.add(new Triple<>(naturalDateDistance, "D", naturalDateDistance));
        }
        // 如果没有有效值，返回null
        if (values.isEmpty()) {
            return null;
        }
        // 找到转换后值最小的原始值
        Triple<BigDecimal, String, BigDecimal> minTriple = values.get(0);
        for (Triple<BigDecimal, String, BigDecimal> triple : values) {
            if (triple.getThird().compareTo(minTriple.getThird()) < 0) {
                minTriple = triple;
            }
        }
        return minTriple.getFirst(); // 返回原始值
    }

    @Override
    public void batchAddOrderBOMs(List<IotMainWorkOrderBomDO> workOrderBOMs) {
        iotMainWorkOrderBomMapper.insertBatch(workOrderBOMs);
    }

    private static class Triple<A, B, C> {
        private final A first;
        private final B second;
        private final C third;

        public Triple(A first, B second, C third) {
            this.first = first;
            this.second = second;
            this.third = third;
        }

        public A getFirst() {
            return first;
        }

        public B getSecond() {
            return second;
        }

        public C getThird() {
            return third;
        }
    }

}
