package com.sckw.transport.service;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sckw.core.common.enums.enums.DictTypeEnum;
import com.sckw.core.common.enums.enums.ErrorCodeEnum;
import com.sckw.core.exception.BusinessPlatfromException;
import com.sckw.core.web.constant.HttpStatus;
import com.sckw.core.web.response.BaseResult;
import com.sckw.core.web.response.HttpResult;
import com.sckw.fleet.api.feign.FleetTruckFeignService;
import com.sckw.order.api.feign.TradeOrderApi;
import com.sckw.order.api.model.OrderPara;
import com.sckw.order.api.model.TradeOrderVo;
import com.sckw.product.api.dubbo.GoodsInfoService;
import com.sckw.product.api.model.KwpGoods;
import com.sckw.system.api.RemoteSystemService;
import com.sckw.transport.model.*;
import com.sckw.transport.model.enuma.WeighbridgeTypeEnum;
import com.sckw.transport.model.param.TradeOrderTransportQueryReq;
import com.sckw.transport.model.param.WaybillTransportQueryReq;
import com.sckw.transport.model.vo.TradeOrderTransportInfoResp;
import com.sckw.transport.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 贸易订单运输信息查询服务。
 *
 * @author system
 * @date 2026-05-08
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TradeOrderTransportInfoService {

    private static final int LOAD_TICKET_TYPE = 1;
    private static final int UNLOAD_ADDRESS_TYPE = 2;
    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";

    private final TradeOrderApi tradeOrderApi;
    private final FleetTruckFeignService fleetTruckFeignService;
    private final KwtLogisticsOrderRepository logisticsOrderRepository;
    private final KwtWaybillOrderRepository waybillOrderRepository;
    private final KwtWaybillOrderSubtaskRepository waybillOrderSubtaskRepository;
    private final KwtWaybillOrderTicketRepository waybillOrderTicketRepository;
    private final KwtLogisticsOrderGoodsRepository logisticsOrderGoodsRepository;
    private final KwtLogisticsOrderAddressRepository logisticsOrderAddressRepository;
    private final KwtGatekeeperWaybillOrderRepository kwtGatekeeperWaybillOrderRepository;
    private final  KwtWaybillOrderWeighbridgeRepository waybillOrderWeighbridgeRepository;

    @DubboReference(version = "1.0.0", group = "design", check = false)
    private GoodsInfoService goodsInfoService;
    @DubboReference(version = "1.0.0", group = "design", check = false)
    private RemoteSystemService remoteSystemService;

    /**
     * 根据贸易订单号查询运输相关信息。
     * <p>
     * 该方法通过贸易订单号串联查询物流订单、运单及关联的运输任务详情，组装成完整的运输信息响应对象。
     * 主要流程：
     * 1. 校验入参及贸易订单号有效性。
     * 2. 查询贸易订单基础信息（用于补充买卖双方企业名称等）。
     * 3. 根据贸易订单号查询关联的物流订单列表，若不存在则抛出异常。
     * 4. 提取物流订单ID，批量查询关联的运单（运输任务）列表，若不存在则抛出异常。
     * 5. 组装并返回包含任务、货物、车辆、司机等信息的响应对象。
     * </p>
     *
     * @param req 查询参数，包含贸易订单号
     * @return 贸易订单运输信息响应对象
     * @throws IllegalArgumentException 当参数为空、贸易订单号为空、未查询到物流订单或未查询到运单时抛出
     */
    public TradeOrderTransportInfoResp queryByTradeOrderNo(TradeOrderTransportQueryReq req) {
        // 1. 参数校验
        if (Objects.isNull(req)) {
            log.warn("查询贸易订单运输信息失败：查询参数为空");
            throw new IllegalArgumentException("查询参数不能为空");
        }
        String tradeOrderNo = StringUtils.trimToEmpty(req.getTradeOrderNo());
        if (StringUtils.isBlank(tradeOrderNo)) {
            log.warn("查询贸易订单运输信息失败：贸易订单号为空");
            throw new IllegalArgumentException("贸易订单号不能为空");
        }
        log.info("查询贸易订单运输信息开始，tradeOrderNo:{}", tradeOrderNo);

        // 2. 查询贸易订单基础信息
        TradeOrderVo tradeOrder = queryTradeOrder(tradeOrderNo);
        
        // 3. 查询关联的物流订单列表
        List<KwtLogisticsOrder> logisticsOrders = queryLogisticsOrders(tradeOrderNo, tradeOrder);
        if (CollectionUtils.isEmpty(logisticsOrders)) {
            log.warn("查询贸易订单运输信息失败：未查询到关联的物流订单，tradeOrderNo:{}", tradeOrderNo);
            throw new IllegalArgumentException("未查询到贸易订单关联的物流订单");
        }
        log.debug("查询到关联物流订单数量:{}", logisticsOrders.size());

        // 4. 提取物流订单ID并查询关联的运单（运输任务）列表
        List<Long> logisticsOrderIds = logisticsOrders.stream()
                .map(KwtLogisticsOrder::getId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        
        List<KwtWaybillOrder> waybillOrders = queryWaybillOrders(logisticsOrderIds);
        if (CollectionUtils.isEmpty(waybillOrders)) {
            log.warn("查询贸易订单运输信息失败：未查询到关联的运输任务，tradeOrderNo:{}, logisticsOrderIds:{}", 
                    tradeOrderNo, logisticsOrderIds);
            throw new IllegalArgumentException("未查询到贸易订单关联的运输任务");
        }
        log.debug("查询到关联运单数量:{}", waybillOrders.size());

        // 5. 组装响应数据
        TradeOrderTransportInfoResp resp = buildTransportInfo(logisticsOrders, waybillOrders, tradeOrder);
        resp.setTradeOrderNo(tradeOrderNo);
        
        log.info("查询贸易订单运输信息完成，tradeOrderNo:{}, taskSize:{}", tradeOrderNo, 
                Objects.nonNull(resp.getTasks()) ? resp.getTasks().size() : 0);
        return resp;
    }

    /**
     * 查询贸易订单基础信息。
     *
     * @param tradeOrderNo 贸易订单号
     * @return 贸易订单基础信息
     */
    /**
     * 根据运单号查询运输相关信息。
     *
     * @param req 查询参数
     * @return 运单运输信息
     */
    /**
     * 根据运单号查询运输相关信息。
     * <p>
     * 该方法通过运单号反向追溯关联的物流订单及贸易订单，组装完整的运输链路信息。
     * 主要流程：
     * 1. 校验入参及运单号有效性。
     * 2. 查询运单基础信息，校验是否存在及是否关联物流订单。
     * 3. 根据运单关联的物流订单ID，查询物流订单详情。
     * 4. 若物流订单中存在贸易订单号，则进一步查询贸易订单详细信息（如买卖双方）。
     * 5. 组装并返回包含任务、货物、车辆、司机等信息的响应对象。
     * </p>
     *
     * @param req 查询参数，包含运单号
     * @return 贸易订单运输信息响应对象（复用同一响应结构）
     * @throws IllegalArgumentException 当参数为空、运单不存在、未关联物流订单或物流订单不存在时抛出
     */
    public TradeOrderTransportInfoResp queryByWaybillNo(WaybillTransportQueryReq req) {
        log.info("根据运单号查询运输信息开始，请求参数:{}", JSON.toJSONString( req));
        // 1. 参数校验
        if (Objects.isNull(req)) {
            log.warn("根据运单号查询运输信息失败：查询参数为空");
            throw new IllegalArgumentException("查询参数不能为空");
        }
        String waybillNo = StringUtils.trimToEmpty(req.getWaybillNo());
        if (StringUtils.isBlank(waybillNo)) {
            log.warn("根据运单号查询运输信息失败：运单号为空");
            throw new IllegalArgumentException("运单号不能为空");
        }
        log.info("根据运单号查询运输信息开始，waybillNo:{}", waybillNo);

        // 2. 查询运单信息
        KwtWaybillOrder waybillOrder = waybillOrderRepository.queryByWayOrderNo(waybillNo);
        if (Objects.isNull(waybillOrder)) {
            log.warn("根据运单号查询运输信息失败：未查询到运单信息，waybillNo:{}", waybillNo);
            throw new IllegalArgumentException("未查询到运单信息");
        }
        
        // 3. 校验运单是否关联物流订单
        if (Objects.isNull(waybillOrder.getLOrderId())) {
            log.warn("根据运单号查询运输信息失败：运单未关联物流订单，waybillNo:{}, waybillId:{}", waybillNo, waybillOrder.getId());
            throw new IllegalArgumentException("运单未关联物流订单");
        }

        // 4. 查询关联的物流订单
        KwtLogisticsOrder logisticsOrder = logisticsOrderRepository.queryByLogisticsOrderId(waybillOrder.getLOrderId());
        if (Objects.isNull(logisticsOrder)) {
            log.error("根据运单号查询运输信息失败：未查询到运单关联的物流订单，waybillNo:{}, logisticsOrderId:{}", 
                    waybillNo, waybillOrder.getLOrderId());
            throw new IllegalArgumentException("未查询到运单关联的物流订单");
        }
        log.debug("查询到关联物流订单，logisticsOrderId:{}, tradeOrderNo:{}", logisticsOrder.getId(), logisticsOrder.getTOrderNo());

        // 5. 查询贸易订单信息（可选，用于补充买卖双方企业名称等）
        TradeOrderVo tradeOrder = null;
        if (StringUtils.isNotBlank(logisticsOrder.getTOrderNo())) {
            try {
                tradeOrder = queryTradeOrder(logisticsOrder.getTOrderNo());
                if (Objects.nonNull(tradeOrder)) {
                    log.debug("查询到关联贸易订单，tradeOrderNo:{}", tradeOrder.getTOrderNo());
                } else {
                    log.warn("物流订单关联了贸易订单号但未查询到详细信息，tradeOrderNo:{}", logisticsOrder.getTOrderNo());
                }
            } catch (Exception e) {
                log.error("查询贸易订单异常，但不影响主流程，tradeOrderNo:{}", logisticsOrder.getTOrderNo(), e);
            }
        }

        // 6. 组装响应数据
        TradeOrderTransportInfoResp resp = buildTransportInfo(
                Collections.singletonList(logisticsOrder),
                Collections.singletonList(waybillOrder),
                tradeOrder
        );

        // 7. 查询运单过磅信息
        KwtWaybillOrderWeighbridge waybillOrderWeighbridge = queryWeighbridge(waybillOrder);
        Long weighbridgeId = waybillOrderWeighbridge.getWeighbridgeId();
        resp.setWeighbridgeId(weighbridgeId);
        
        log.info("根据运单号查询运输信息完成，waybillNo:{}, taskSize:{}", waybillNo, 
                Objects.nonNull(resp.getTasks()) ? resp.getTasks().size() : 0);
        return resp;
    }

    /**
     * 查询过磅信息
     * @param waybillOrder
     * @return
     */
    private KwtWaybillOrderWeighbridge queryWeighbridge(KwtWaybillOrder waybillOrder) {
        List<KwtWaybillOrderWeighbridge> weighbridges = waybillOrderWeighbridgeRepository
                .queryWaybillOrderWeighbridgeByWOrderId(waybillOrder.getId(), WeighbridgeTypeEnum.LOADING.getCode());
        if (CollectionUtils.isEmpty(weighbridges)) {
            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "未查询到地磅信息！");
        }
        return weighbridges.get(0);
    }


    /**
     * 查询贸易订单详细信息。
     * <p>
     * 通过 Feign 调用订单服务接口，根据贸易订单号获取对应的贸易订单数据。
     * 若返回多个订单，优先匹配订单号完全一致的记录；若无完全匹配，则返回列表中的第一个订单作为兜底。
     * </p>
     *
     * @param tradeOrderNo 贸易订单号
     * @return 贸易订单视图对象，若未查询到或发生异常则返回 null
     */
    private TradeOrderVo queryTradeOrder(String tradeOrderNo) {
        log.debug("开始查询贸易订单详情，tradeOrderNo:{}", tradeOrderNo);
        try {
            // 构建查询参数
            OrderPara para = new OrderPara();
            para.setOrderNo(tradeOrderNo);
            
            // 调用远程订单服务接口
            List<TradeOrderVo> tradeOrders = tradeOrderApi.trade(para);
            
            if (CollectionUtils.isEmpty(tradeOrders)) {
                log.warn("远程服务返回的贸易订单列表为空，tradeOrderNo:{}", tradeOrderNo);
                return null;
            }

            // 优先精确匹配订单号
            Optional<TradeOrderVo> exactMatch = tradeOrders.stream()
                    .filter(item -> tradeOrderNo.equals(item.getTOrderNo()))
                    .findFirst();
            
            if (exactMatch.isPresent()) {
                log.debug("精确匹配到贸易订单，tradeOrderNo:{}", tradeOrderNo);
                return exactMatch.get();
            }

            // 若无精确匹配，取列表第一个作为兜底（兼容历史数据或特殊场景）
            TradeOrderVo fallbackOrder = tradeOrders.get(0);
            log.warn("未精确匹配到贸易订单号，使用返回列表中的第一个订单作为兜底，requestNo:{}, actualOrderNo:{}", 
                    tradeOrderNo, fallbackOrder.getTOrderNo());
            return fallbackOrder;
            
        } catch (Exception e) {
            log.error("Feign 调用订单服务查询贸易订单异常，tradeOrderNo:{}, errorMessage:{}", 
                    tradeOrderNo, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 查询物流订单，优先使用本地贸易订单号精确匹配。
     *
     * @param tradeOrderNo 贸易订单号
     * @param tradeOrder   远程贸易订单
     * @return 物流订单列表
     */
    /**
     * 查询物流订单列表。
     * <p>
     * 查询策略采用“优先单号匹配，降级ID匹配”的方式：
     * 1. 首先尝试通过贸易订单号（tradeOrderNo）在本地数据库查询物流订单。
     * 2. 如果查询结果为空，且提供了有效的贸易订单对象及其ID（tOrderId），则降级使用贸易订单ID进行查询。
     * 3. 这种设计兼容了不同数据来源或历史数据中订单号不一致但ID关联的场景。
     * </p>
     *
     * @param tradeOrderNo 贸易订单号
     * @param tradeOrder   贸易订单详细信息，用于获取贸易订单ID作为兜底查询条件
     * @return 物流订单列表，若未查询到则返回空列表
     */
    private List<KwtLogisticsOrder> queryLogisticsOrders(String tradeOrderNo, TradeOrderVo tradeOrder) {
        log.debug("开始查询物流订单，tradeOrderNo:{}", tradeOrderNo);
        
        // 第一步：优先通过贸易订单号查询
        List<KwtLogisticsOrder> logisticsOrders = logisticsOrderRepository.queryByTradeOrderNo(tradeOrderNo);
        
        // 如果查询到了结果，或者无法进行降级查询（贸易订单为空或缺少ID），则直接返回当前结果
        if (CollectionUtils.isNotEmpty(logisticsOrders)) {
            log.debug("通过贸易订单号查询到物流订单，count:{}", logisticsOrders.size());
            return logisticsOrders;
        }
        
        if (Objects.isNull(tradeOrder) || Objects.isNull(tradeOrder.getTOrderId())) {
            log.warn("通过贸易订单号未查询到物流订单，且无法通过贸易订单ID降级查询（tradeOrder为空或tOrderId为空）");
            return Collections.emptyList();
        }

        // 第二步：降级通过贸易订单ID查询
        log.info("通过贸易订单号未查询到物流订单，尝试通过贸易订单ID降级查询，tOrderId:{}", tradeOrder.getTOrderId());
        List<KwtLogisticsOrder> fallbackOrders = logisticsOrderRepository.queryByTradeOrderId(tradeOrder.getTOrderId());
        
        if (CollectionUtils.isNotEmpty(fallbackOrders)) {
            log.info("通过贸易订单ID成功查询到物流订单，count:{}", fallbackOrders.size());
        } else {
            log.warn("通过贸易订单ID也未查询到物流订单，tOrderId:{}", tradeOrder.getTOrderId());
        }
        
        return fallbackOrders;
    }

    /**
     * 查询运单列表。
     *
     * @param logisticsOrderIds 物流订单ID列表
     * @return 运单列表
     */
    private List<KwtWaybillOrder> queryWaybillOrders(List<Long> logisticsOrderIds) {
        if (CollectionUtils.isEmpty(logisticsOrderIds)) {
            return Collections.emptyList();
        }
        return waybillOrderRepository.list(Wrappers.<KwtWaybillOrder>lambdaQuery()
                .eq(KwtWaybillOrder::getDelFlag, 0)
                .in(KwtWaybillOrder::getLOrderId, logisticsOrderIds)
                .orderByDesc(KwtWaybillOrder::getTaskEndTime)
                .orderByDesc(KwtWaybillOrder::getUpdateTime)
                .orderByDesc(KwtWaybillOrder::getId));
    }

    /**
     * 填充贸易订单企业信息。
     *
     * @param resp           响应对象
     * @param tradeOrder     远程贸易订单
     * @param logisticsOrder 本地物流订单
     */
    private void fillTradeOrderInfo(TradeOrderTransportInfoResp resp, TradeOrderVo tradeOrder, KwtLogisticsOrder logisticsOrder) {
        resp.setSupplierName(Objects.nonNull(tradeOrder) ? tradeOrder.getSellEntName() : null);
        resp.setCustomerName(Objects.nonNull(tradeOrder) ? tradeOrder.getBuyEntName() : null);
        if (StringUtils.isBlank(resp.getTradeOrderNo()) && Objects.nonNull(logisticsOrder)) {
            resp.setTradeOrderNo(logisticsOrder.getTOrderNo());
        }
    }

    /**
     * 组装任务信息列表。
     *
     * @param logisticsOrders 物流订单列表
     * @param waybillOrders   运单列表
     * @return 任务信息列表
     */
    /**
     * 组装运输信息响应。
     *
     * @param logisticsOrders 物流订单列表
     * @param waybillOrders   运单列表
     * @param tradeOrder      贸易订单信息
     * @return 运输信息响应
     */
    private TradeOrderTransportInfoResp buildTransportInfo(List<KwtLogisticsOrder> logisticsOrders,
                                                           List<KwtWaybillOrder> waybillOrders,
                                                           TradeOrderVo tradeOrder) {
        TradeOrderTransportInfoResp resp = new TradeOrderTransportInfoResp();
        KwtLogisticsOrder firstLogisticsOrder = CollectionUtils.isEmpty(logisticsOrders) ? null : logisticsOrders.get(0);
        fillTradeOrderInfo(resp, tradeOrder, firstLogisticsOrder);
        resp.setTasks(buildTaskInfoList(logisticsOrders, waybillOrders));
        return resp;
    }

    /**
     * 组装运输任务信息列表。
     * <p>
     * 该方法负责将物流订单和运单数据转换为前端展示的任务信息列表。
     * 主要步骤包括：
     * 1. 构建物流订单映射，以便快速通过ID获取订单详情。
     * 2. 提取有效的物流订单ID和运单ID列表。
     * 3. 批量查询并构建子任务、装货单、货物信息、卸货地址的映射关系，优化查询性能。
     * 4. 遍历运单列表，结合上述映射数据组装每个任务的详细信息。
     * </p>
     *
     * @param logisticsOrders 物流订单列表
     * @param waybillOrders   运单列表
     * @return 运输任务信息列表
     */
    private List<TradeOrderTransportInfoResp.TaskInfo> buildTaskInfoList(List<KwtLogisticsOrder> logisticsOrders,
                                                                         List<KwtWaybillOrder> waybillOrders) {
        log.debug("开始组装运输任务信息列表，logisticsOrderCount:{}, waybillOrderCount:{}", 
                CollectionUtils.isEmpty(logisticsOrders) ? 0 : logisticsOrders.size(),
                CollectionUtils.isEmpty(waybillOrders) ? 0 : waybillOrders.size());

        // 1. 构建物流订单ID到对象的映射，处理可能的重复ID（保留第一个）
        Map<Long, KwtLogisticsOrder> logisticsOrderMap = logisticsOrders.stream()
                .filter(item -> Objects.nonNull(item.getId()))
                .collect(Collectors.toMap(KwtLogisticsOrder::getId, Function.identity(), (a, b) -> a));
        
        // 2. 提取有效的ID列表用于后续批量查询
        List<Long> logisticsOrderIds = logisticsOrders.stream()
                .map(KwtLogisticsOrder::getId)
                .filter(Objects::nonNull)
                .toList();
        List<Long> waybillOrderIds = waybillOrders.stream()
                .map(KwtWaybillOrder::getId)
                .filter(Objects::nonNull)
                .toList();

        if (CollectionUtils.isEmpty(waybillOrderIds)) {
            log.warn("运单ID列表为空，无法组装任务信息");
            return Collections.emptyList();
        }

        // 3. 批量查询关联数据并构建映射，以优化数据库交互次数
        
        // 查询运单子任务，若有多个子任务则选择最新更新的记录
        Map<Long, KwtWaybillOrderSubtask> subtaskMap = waybillOrderSubtaskRepository.queryByWOrderIds(waybillOrderIds).stream()
                .collect(Collectors.toMap(KwtWaybillOrderSubtask::getWOrderId, Function.identity(), this::chooseLatestSubtask));
        log.debug("查询到运单子任务数量:{}", subtaskMap.size());

        // 查询装货单（类型为LOAD_TICKET_TYPE），若有多个则选择最新更新的记录
        Map<Long, KwtWaybillOrderTicket> loadTicketMap = waybillOrderTicketRepository.queryByWOrderIdsAndType(waybillOrderIds, LOAD_TICKET_TYPE).stream()
                .collect(Collectors.toMap(KwtWaybillOrderTicket::getWOrderId, Function.identity(), this::chooseLatestTicket));
        log.debug("查询到装货单数量:{}", loadTicketMap.size());

        // 查询物流订单货物信息
        Map<Long, KwtLogisticsOrderGoods> goodsMap = logisticsOrderGoodsRepository.queryByLogOrderIds(logisticsOrderIds).stream()
                .collect(Collectors.toMap(KwtLogisticsOrderGoods::getLOrderId, Function.identity(), (a, b) -> a));
        log.debug("查询到货物信息数量:{}", goodsMap.size());

        // 查询卸货地址（类型为UNLOAD_ADDRESS_TYPE）
        Map<Long, KwtLogisticsOrderAddress> unloadAddressMap = logisticsOrderAddressRepository.queryByLogOrderIds(logisticsOrderIds).stream()
                .filter(item -> Objects.equals(UNLOAD_ADDRESS_TYPE, item.getAddressType()))
                .collect(Collectors.toMap(KwtLogisticsOrderAddress::getLOrderId, Function.identity(), (a, b) -> a));
        log.debug("查询到卸货地址数量:{}", unloadAddressMap.size());
        //查询字典
        Map<String, Map<String, String>> dictValueAndDictResDtoMap = remoteSystemService.queryDictByType(Arrays.asList(DictTypeEnum.PRODUCT_NAME_TYPE.getType(), DictTypeEnum.GOODS_SPEC.getType()));

        // 用于缓存车辆轴数信息，避免重复Feign调用
        Map<String, String> truckAxleCache = new LinkedHashMap<>();

        // 4. 遍历运单，组装每个任务的详细信息
        List<TradeOrderTransportInfoResp.TaskInfo> taskInfoList = waybillOrders.stream()
                .map(waybillOrder -> buildTaskInfo(waybillOrder,
                        logisticsOrderMap.get(waybillOrder.getLOrderId()),
                        subtaskMap.get(waybillOrder.getId()),
                        loadTicketMap.get(waybillOrder.getId()),
                        goodsMap.get(waybillOrder.getLOrderId()),
                        unloadAddressMap.get(waybillOrder.getLOrderId()),
                        truckAxleCache,
                        dictValueAndDictResDtoMap))
                .collect(Collectors.toList());
        
        log.info("运输任务信息列表组装完成，最终任务数量:{}", taskInfoList.size());
        return taskInfoList;
    }

    /**
     * 组装单车任务信息。
     */
    /**
     * 组装单车任务详细信息。
     * <p>
     * 该方法将运单、物流订单、子任务、装货单、货物信息及卸货地址等分散的数据源，
     * 整合为一个完整的任务信息对象（TaskInfo），用于前端展示单个运输任务的详细状态。
     * </p>
     *
     * @param waybillOrder   运单主表信息，包含任务号、司机、车辆及基础时间信息
     * @param logisticsOrder 关联的物流订单信息，用于补充货物基础单位及重量兜底
     * @param subtask        运单子任务信息，包含实际装卸时间、委托量等执行细节
     * @param loadTicket     装货单信息，包含皮重、毛重、净重及打印/上传时间
     * @param goods          物流订单货物明细，包含货物名称、规格等静态属性
     * @param unloadAddress  卸货地址信息，用于获取目的地城市名称
     * @param truckAxleCache 车辆轴数缓存Map，Key为车牌号，Value为轴数，避免重复远程调用
     * @return 组装好的单车任务信息对象
     */
    private TradeOrderTransportInfoResp.TaskInfo buildTaskInfo(KwtWaybillOrder waybillOrder,
                                                               KwtLogisticsOrder logisticsOrder,
                                                               KwtWaybillOrderSubtask subtask,
                                                               KwtWaybillOrderTicket loadTicket,
                                                               KwtLogisticsOrderGoods goods,
                                                               KwtLogisticsOrderAddress unloadAddress,
                                                               Map<String, String> truckAxleCache,
                                                               Map<String, Map<String, String>> dictValueAndDictResDtoMap) {
        log.debug("开始组装单车任务信息，waybillNo:{}", Objects.nonNull(waybillOrder) ? waybillOrder.getWOrderNo() : null);
        //查询商品数据

        KwpGoods goodsById = goodsInfoService.getGoodsById(goods.getGoodsId());
        TradeOrderTransportInfoResp.TaskInfo taskInfo = new TradeOrderTransportInfoResp.TaskInfo();
        
        // 1. 设置任务编号
        if (Objects.nonNull(waybillOrder)) {
            taskInfo.setTaskNo(waybillOrder.getWOrderNo());
            //查询门卫
            KwtGatekeeperWaybillOrder kwtGatekeeperWaybillOrder = kwtGatekeeperWaybillOrderRepository.queryGatekeeperWaybillOrderByWOrderId(waybillOrder.getId(), 1);
            String weigherName = Optional.ofNullable(kwtGatekeeperWaybillOrder)
                    .map(KwtGatekeeperWaybillOrder::getGatekeeperName)
                    .orElse("");
            taskInfo.setWeigherName(weigherName);

            log.debug("设置任务编号: {}", waybillOrder.getWOrderNo());
        }

        // 2. 设置接单时间：优先使用任务开始时间，若为空则降级使用创建时间
        Date acceptTime = firstNotNull(
                Objects.nonNull(waybillOrder) ? waybillOrder.getTaskStartTime() : null, 
                Objects.nonNull(waybillOrder) ? waybillOrder.getCreateTime() : null
        );
        taskInfo.setAcceptTime(formatDate(acceptTime));
        log.debug("设置接单时间: {}", formatDate(acceptTime));

        // 3. 设置完成时间：优先使用任务结束时间，若为空则尝试从子任务中获取卸货时间
        Date finishTime = firstNotNull(
                Objects.nonNull(waybillOrder) ? waybillOrder.getTaskEndTime() : null, 
                Objects.nonNull(subtask) ? subtask.getUnloadTime() : null
        );
        taskInfo.setFinishTime(formatDate(finishTime));
        log.debug("设置完成时间: {}", formatDate(finishTime));

        // 4. 组装司机信息（姓名、电话、脱敏身份证号）
        taskInfo.setDriverInfo(buildDriverInfo(waybillOrder));

        // 5. 组装货物及重量信息（名称、规格、单位、各类重量数据）
        taskInfo.setGoodsInfo(buildGoodsInfo(logisticsOrder, subtask, loadTicket, goods,goodsById,dictValueAndDictResDtoMap));

        // 6. 组装车辆信息（车牌号、轴数-含缓存逻辑）
        taskInfo.setTruckInfo(buildTruckInfo(waybillOrder, truckAxleCache));

        // 7. 设置目的地城市名称
        String destination = Objects.nonNull(unloadAddress) ? unloadAddress.getCityName() : null;
        taskInfo.setDestination(destination);
        log.debug("设置目的地: {}", destination);

        // 8. 组装打印信息（主要基于装货单上传时间）
        taskInfo.setPrintInfo(buildPrintInfo(loadTicket));

        log.debug("单车任务信息组装完成，waybillNo:{}", Objects.nonNull(waybillOrder) ? waybillOrder.getWOrderNo() : null);
        return taskInfo;
    }

    /**
     * 组装司机信息。
     */
    /**
     * 组装司机信息。
     * <p>
     * 从运单对象中提取司机姓名、电话及身份证号，并对身份证号进行脱敏处理以保护隐私。
     * </p>
     *
     * @param waybillOrder 运单信息，包含司机相关字段
     * @return 组装后的司机信息对象
     */
    private TradeOrderTransportInfoResp.DriverInfo buildDriverInfo(KwtWaybillOrder waybillOrder) {
        log.debug("开始组装司机信息，waybillNo:{}", Objects.nonNull(waybillOrder) ? waybillOrder.getWOrderNo() : null);
        
        TradeOrderTransportInfoResp.DriverInfo driverInfo = new TradeOrderTransportInfoResp.DriverInfo();
        
        if (Objects.nonNull(waybillOrder)) {
            // 设置司机姓名
            driverInfo.setName(waybillOrder.getDriverName());
            // 设置司机电话
            driverInfo.setPhone(waybillOrder.getDriverPhone());
            // 设置脱敏后的身份证号
            String maskedIdCard = maskIdCard(waybillOrder.getDriverIdcard());
            driverInfo.setIdCard(maskedIdCard);
            
            log.debug("司机信息组装完成，name:{}, phone:{}, idCardMasked:{}", 
                    waybillOrder.getDriverName(), 
                    waybillOrder.getDriverPhone(), 
                    maskedIdCard);
        } else {
            log.warn("运单对象为空，无法组装司机信息");
        }
        
        return driverInfo;
    }

    /**
     * 组装货物及重量信息。
     */
    /**
     * 组装货物及重量信息。
     * <p>
     * 该方法负责从物流订单、运单子任务、装货单及货物明细中提取并整合货物相关信息。
     * 主要包含货物名称、规格、单位、委托量以及皮重、毛重、净重等关键指标。
     * </p>
     * <p>
     * 数据取值优先级策略：
     * 1. 基础属性（名称、规格）：优先取自货物明细表 (KwtLogisticsOrderGoods)。
     * 2. 单位与委托量：优先取自运单子任务 (KwtWaybillOrderSubtask)，若缺失则降级使用物流订单 (KwtLogisticsOrder) 中的数据。
     * 3. 重量信息：
     *    - 皮重/毛重：直接取自装货单 (KwtWaybillOrderTicket)。
     *    - 净重：采用多级兜底策略，优先级依次为：
     *      a. 装货单记录的净重 (loadTicket.getAmount())
     *      b. 计算得出的净重 (毛重 - 皮重)
     *      c. 子任务记录的装载量 (subtask.getLoadAmount())
     * </p>
     *
     * @param logisticsOrder 物流订单信息，用于获取基础单位和重量兜底
     * @param subtask        运单子任务信息，包含实际执行层面的单位、委托量及装载量
     * @param loadTicket     装货单信息，包含称重相关的皮重、毛重及净重数据
     * @param goods          货物明细信息，包含货物名称、规格等静态属性
     * @return 组装后的货物信息对象
     */
    private TradeOrderTransportInfoResp.GoodsInfo buildGoodsInfo(KwtLogisticsOrder logisticsOrder,
                                                                 KwtWaybillOrderSubtask subtask,
                                                                 KwtWaybillOrderTicket loadTicket,
                                                                 KwtLogisticsOrderGoods goods,
                                                                 KwpGoods goodsById,
                                                                 Map<String, Map<String, String>> dictValueAndDictResDtoMap) {
        log.debug("开始组装货物及重量信息，logisticsOrderId:{}, waybillSubtaskId:{}, loadTicketId:{}, goodsId:{}", 
                Objects.nonNull(logisticsOrder) ? logisticsOrder.getId() : null,
                Objects.nonNull(subtask) ? subtask.getId() : null,
                Objects.nonNull(loadTicket) ? loadTicket.getId() : null,
                Objects.nonNull(goods) ? goods.getId() : null);

        TradeOrderTransportInfoResp.GoodsInfo goodsInfo = new TradeOrderTransportInfoResp.GoodsInfo();
        Map<String, String> goodsTypeMap = dictValueAndDictResDtoMap.getOrDefault(DictTypeEnum.PRODUCT_NAME_TYPE.getType(), new HashMap<>());
        Map<String, String> goodsSpecMap = dictValueAndDictResDtoMap.getOrDefault(DictTypeEnum.GOODS_SPEC.getType(), new HashMap<>());
        // 1. 设置货物基础属性：名称和规格
        if (Objects.nonNull(goods)) {
            goodsInfo.setMaterialName(goods.getGoodsName());
            goodsInfo.setSpecification(Objects.nonNull(goodsById)? goodsSpecMap.getOrDefault(goodsById.getSpec(), ""):"");
            log.debug("设置货物名称: {}, 规格: {}", goods.getGoodsName(), goods.getRemark());
        } else {
            log.warn("货物明细为空，无法设置货物名称和规格");
        }

        // 2. 设置计量单位：优先使用子任务中的单位，若无则使用物流订单中的单位
        String unit = firstNotBlank(
                Objects.nonNull(subtask) ? subtask.getUnit() : null, 
                Objects.nonNull(logisticsOrder) ? logisticsOrder.getUnit() : null
        );
        goodsInfo.setUnit(unit);
        log.debug("设置货物单位: {}", unit);

        // 3. 设置委托任务量：优先使用子任务中的委托量，若无则使用物流订单中的总量
        BigDecimal taskAmount = firstNotNull(
                Objects.nonNull(subtask) ? subtask.getEntrustAmount() : null, 
                Objects.nonNull(logisticsOrder) ? logisticsOrder.getAmount() : null
        );
        goodsInfo.setTaskAmount(taskAmount);
        log.debug("设置委托任务量: {}", taskAmount);

        // 4. 设置皮重和毛重：直接从装货单获取
        if (Objects.nonNull(loadTicket)) {
            goodsInfo.setTareWeight(loadTicket.getTareAmount());
            goodsInfo.setGrossWeight(loadTicket.getGrossAmount());
            log.debug("设置皮重: {}, 毛重: {}", loadTicket.getTareAmount(), loadTicket.getGrossAmount());
        } else {
            log.warn("装货单信息为空，无法设置皮重和毛重");
        }

        // 5. 计算并设置净重：采用多级兜底策略
        // 优先级：装货单净重 > (毛重 - 皮重) > 子任务装载量
        BigDecimal netWeight = firstNotNull(
                Objects.nonNull(loadTicket) ? loadTicket.getAmount() : null,
                subtract(goodsInfo.getGrossWeight(), goodsInfo.getTareWeight()),
                Objects.nonNull(subtask) ? subtask.getLoadAmount() : null
        );
        goodsInfo.setNetWeight(netWeight);
        log.debug("设置净重: {} (来源优先级: 装货单记录 > 计算值 > 子任务记录)", netWeight);

        log.debug("货物及重量信息组装完成，materialName:{}, netWeight:{}", 
                goodsInfo.getMaterialName(), goodsInfo.getNetWeight());
        return goodsInfo;
    }

    /**
     * 组装车辆信息。
     */
    /**
     * 组装车辆信息。
     * <p>
     * 该方法负责从运单对象中提取车辆相关信息，并查询车辆轴数。
     * 为了减少远程 Feign 调用的次数，使用了传入的缓存 Map (truckAxleCache) 来存储已查询过的车牌号对应的轴数。
     * </p>
     *
     * @param waybillOrder   运单信息，包含车牌号等基础数据
     * @param truckAxleCache 车辆轴数缓存 Map，Key 为车牌号，Value 为轴数
     * @return 组装后的车辆信息对象
     */
    private TradeOrderTransportInfoResp.TruckInfo buildTruckInfo(KwtWaybillOrder waybillOrder, Map<String, String> truckAxleCache) {
        log.debug("开始组装车辆信息，waybillNo:{}", Objects.nonNull(waybillOrder) ? waybillOrder.getWOrderNo() : null);
        
        TradeOrderTransportInfoResp.TruckInfo truckInfo = new TradeOrderTransportInfoResp.TruckInfo();
        
        if (Objects.nonNull(waybillOrder)) {
            // 设置车牌号
            String truckNo = waybillOrder.getTruckNo();
            truckInfo.setTruckNo(truckNo);
            log.debug("设置车牌号: {}", truckNo);

            // 获取车辆轴数：优先从缓存中获取，若不存在则调用 queryTruckAxle 查询并放入缓存
            // 使用 StringUtils.defaultString 防止 key 为 null 导致 Map 操作异常
            String cacheKey = StringUtils.defaultString(truckNo);
            String truckAxle = truckAxleCache.computeIfAbsent(cacheKey, this::queryTruckAxle);
            truckInfo.setTruckAxle(truckAxle);
            log.debug("设置车辆轴数: {} (来源: {})", truckAxle, truckAxleCache.containsKey(cacheKey) && truckAxle != null ? "缓存/新查询" : "查询失败/null");
        } else {
            log.warn("运单对象为空，无法组装车辆信息");
        }
        
        log.debug("车辆信息组装完成，truckNo:{}, truckAxle:{}", 
                Objects.nonNull(truckInfo.getTruckNo()) ? truckInfo.getTruckNo() : "N/A", 
                truckInfo.getTruckAxle());
        return truckInfo;
    }

    /**
     * 通过车辆 Feign 查询车辆轴数。
     */
    /**
     * 通过车辆 Feign 查询车辆轴数。
     * <p>
     * 该方法调用远程车队服务接口，根据车牌号获取车辆的轴数信息。
     * 主要用于补充运输任务中的车辆详细属性，以支持后续的运费计算或合规性校验。
     * </p>
     *
     * @param truckNo 车牌号
     * @return 车辆轴数（字符串格式），若查询失败、无数据或发生异常则返回 null
     */
    // sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/TradeOrderTransportInfoService.java

    private String queryTruckAxle(String truckNo) {
        if (StringUtils.isBlank(truckNo)) {
            log.debug("查询车辆轴数跳过：车牌号为空");
            return null;
        }

        log.debug("开始查询车辆轴数，truckNo:{}", truckNo);
        try {
            BaseResult<Map<String, Object>> result = fleetTruckFeignService.truckDetail(Map.of("truckNo", truckNo));
            if (Objects.isNull(result) || !Objects.equals(result.getCode(),HttpStatus.SUCCESS_CODE)|| Objects.isNull(result.getData())) {
                log.warn("查询车辆轴数失败：远程服务返回异常或数据为空，truckNo:{}, resultCode:{}, dataType:{}",
                        truckNo,
                        Objects.nonNull(result) ? result.getCode() : "N/A",
                        Objects.nonNull(result) && Objects.nonNull(result.getData())
                                ? result.getData().getClass().getSimpleName()
                                : "N/A");
                return null;
            }

            Object carAxis = result.getData().get("carAxisName");
            if (Objects.nonNull(carAxis)) {
                String axleStr = String.valueOf(carAxis);
                log.debug("成功查询到车辆轴数，truckNo:{}, axle:{}", truckNo, axleStr);
                return axleStr;
            }

            log.debug("远程服务返回数据中未包含轴数信息，truckNo:{}", truckNo);
            return null;
        } catch (Exception e) {
            log.error("Feign 调用查询车辆轴数发生异常，truckNo:{}, errorMessage:{}", truckNo, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 组装打印信息，当前运输库无独立打印记录时使用装货单上传时间兜底。
     */
    /**
     * 组装打印信息。
     * <p>
     * 该方法负责构建任务相关的打印信息对象。
     * 由于当前运输库中可能没有独立的打印记录表，因此采用“装货单上传时间”作为打印时间的兜底数据。
     * </p>
     *
     * @param loadTicket 装货单信息，包含上传时间等关键字段
     * @return 组装后的打印信息对象，若装货单为空则返回仅包含空字段的对象
     */
    private TradeOrderTransportInfoResp.PrintInfo buildPrintInfo(KwtWaybillOrderTicket loadTicket) {
        log.debug("开始组装打印信息，loadTicketId:{}", Objects.nonNull(loadTicket) ? loadTicket.getId() : null);
        
        TradeOrderTransportInfoResp.PrintInfo printInfo = new TradeOrderTransportInfoResp.PrintInfo();
        
        if (Objects.nonNull(loadTicket)) {
            // 使用装货单的上传时间作为打印时间
            Date uploadingTime = loadTicket.getUploadingTime();
            String printTimeStr = formatDate(uploadingTime);
            printInfo.setPrintTime(printTimeStr);
            
            log.debug("设置打印时间（源自装货单上传时间）: {}", printTimeStr);
        } else {
            log.warn("装货单信息为空，无法设置打印时间");
        }
        
        log.debug("打印信息组装完成");
        return printInfo;
    }

    /**
     * 选择最新的运单子任务。
     * <p>
     * 当存在多个子任务时，根据更新时间（UpdateTime）进行比较，保留最新更新的记录。
     * 若更新时间为空，则视为较小值（排在后面），确保非空时间优先被选中。
     * </p>
     *
     * @param source 源子任务
     * @param target 目标子任务
     * @return 更新时间较晚的子任务；若两者均为 null 或更新时间相同，返回 source
     */
    private KwtWaybillOrderSubtask chooseLatestSubtask(KwtWaybillOrderSubtask source, KwtWaybillOrderSubtask target) {
        log.debug("比较运单子任务更新时间，sourceId:{}, sourceUpdateTime:{}, targetId:{}, targetUpdateTime:{}", 
                Objects.nonNull(source) ? source.getId() : null,
                Objects.nonNull(source) ? source.getUpdateTime() : null,
                Objects.nonNull(target) ? target.getId() : null,
                Objects.nonNull(target) ? target.getUpdateTime() : null);

        KwtWaybillOrderSubtask latest = Comparator.nullsLast(Comparator.comparing(KwtWaybillOrderSubtask::getUpdateTime, Comparator.nullsLast(Date::compareTo)))
                .compare(source, target) >= 0 ? source : target;
        
        log.debug("选择最新的运单子任务，selectedId:{}, selectedUpdateTime:{}", 
                Objects.nonNull(latest) ? latest.getId() : null,
                Objects.nonNull(latest) ? latest.getUpdateTime() : null);
                
        return latest;
    }

    /**
     * 选择最新的装货单。
     * <p>
     * 当存在多个装货单时，根据更新时间（UpdateTime）进行比较，保留最新更新的记录。
     * 若更新时间为空，则视为较小值（排在后面），确保非空时间优先被选中。
     * </p>
     *
     * @param source 源装货单
     * @param target 目标装货单
     * @return 更新时间较晚的装货单；若两者均为 null 或更新时间相同，返回 source
     */
    private KwtWaybillOrderTicket chooseLatestTicket(KwtWaybillOrderTicket source, KwtWaybillOrderTicket target) {
        log.debug("比较装货单更新时间，sourceId:{}, sourceUpdateTime:{}, targetId:{}, targetUpdateTime:{}", 
                Objects.nonNull(source) ? source.getId() : null,
                Objects.nonNull(source) ? source.getUpdateTime() : null,
                Objects.nonNull(target) ? target.getId() : null,
                Objects.nonNull(target) ? target.getUpdateTime() : null);

        KwtWaybillOrderTicket latest = Comparator.nullsLast(Comparator.comparing(KwtWaybillOrderTicket::getUpdateTime, Comparator.nullsLast(Date::compareTo)))
                .compare(source, target) >= 0 ? source : target;
        
        log.debug("选择最新的装货单，selectedId:{}, selectedUpdateTime:{}", 
                Objects.nonNull(latest) ? latest.getId() : null,
                Objects.nonNull(latest) ? latest.getUpdateTime() : null);
                
        return latest;
    }

    public static String maskIdCard(String idCard) {
        if (StringUtils.isBlank(idCard) || idCard.length() < 8) {
            return idCard;
        }
        return idCard.substring(0, 5) + "****" + idCard.substring(idCard.length() - 4);
    }

    @SafeVarargs
    public static <T> T firstNotNull(T... values) {
        if (Objects.isNull(values)) {
            return null;
        }
        for (T value : values) {
            if (Objects.nonNull(value)) {
                return value;
            }
        }
        return null;
    }

    public static BigDecimal subtract(BigDecimal source, BigDecimal target) {
        if (Objects.isNull(source) || Objects.isNull(target)) {
            return null;
        }
        return source.subtract(target);
    }

    private String firstNotBlank(String source, String target) {
        return StringUtils.isNotBlank(source) ? source : target;
    }

    private String formatDate(Date date) {
        if (Objects.isNull(date)) {
            return null;
        }
        return new SimpleDateFormat(DATE_TIME_PATTERN).format(date);
    }
}
