package com.sckw.transport.service.dashboard;

import com.sckw.core.model.constant.NumberConstant;
import com.sckw.core.model.enums.CarWaybillV1Enum;
import com.sckw.core.model.enums.GatekeeperStatusEnum;
import com.sckw.core.utils.CollectionUtils;
import com.sckw.transport.api.model.vo.CurrentCapacityAnalysisVo;
import com.sckw.transport.model.KwtGatekeeperWaybillOrder;
import com.sckw.transport.model.KwtLogisticsOrderUnit;
import com.sckw.transport.model.KwtWaybillOrder;
import com.sckw.transport.model.KwtWaybillOrderAddress;
import com.sckw.transport.model.KwtWaybillOrderSubtask;
import com.sckw.transport.repository.KwtGatekeeperWaybillOrderRepository;
import com.sckw.transport.repository.KwtLogisticsOrderUnitRepository;
import com.sckw.transport.repository.KwtWaybillOrderAddressRepository;
import com.sckw.transport.repository.KwtWaybillOrderRepository;
import com.sckw.transport.repository.KwtWaybillOrderSubtaskRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 当前运力分析服务：以 {@code kwt_waybill_order_subtask} 为主表口径统计各运单车辆状态
 * <p>
 * 主要统计三类状态：
 * 1. 待入场 (Pending Entry)
 * 2. 装卸作业中 (Loading Operation)
 * 3. 配送中/运输中 (Delivering)
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SubtaskCapacityAnalysisService {

    /**
     * 公开报表固定统计企业范围。
     * <p>
     * 当前接口免登录访问，企业范围按需求固定为图片中的父企业及两个子企业。
     * </p>
     */
    private static final List<Long> REPORT_ENT_IDS = List.of(
            538038314096136193L,
            538039617157337089L,
            538040297439891457L
    );

    private final KwtWaybillOrderSubtaskRepository waybillOrderSubtaskRepository;
    private final KwtWaybillOrderAddressRepository waybillOrderAddressRepository;
    private final KwtGatekeeperWaybillOrderRepository gatekeeperWaybillOrderRepository;
    private final KwtWaybillOrderRepository waybillOrderRepository;
    private final KwtLogisticsOrderUnitRepository logisticsOrderUnitRepository;

    /**
     * 执行当前运力分析
     * <p>
     * 核心流程：
     * 1. 查询有效子任务并聚合出每个运单的最大状态及关键时间点。
     * 2. 加载辅助数据（卸货地址、门岗离场时间、主表更新时间）。
     * 3. 根据业务规则将运单分类为：待入场、装卸中、配送中。
     *
     * @return 当前运力分析结果 VO
     */
    public CurrentCapacityAnalysisVo analyze() {
        CurrentCapacityAnalysisVo vo = new CurrentCapacityAnalysisVo();
        log.info("=== 开始执行当前运力分析 ===");

        // 1. 查询所有有效的子任务（排除已删除和已取消的）
        // 仅查询必要字段以减少内存占用：运单ID、状态、更新时间
        log.debug("步骤1: 查询有效子任务...");
        List<Long> logisticsOrderIds = queryReportLogisticsOrderIds();
        if (CollectionUtils.isEmpty(logisticsOrderIds)) {
            log.warn("公开报表企业范围未查询到物流订单，返回空结果，entIds={}", REPORT_ENT_IDS);
            vo.setPendingEntryCount(0);
            vo.setLoadingOperationCount(0);
            vo.setDeliveringCount(0);
            return vo;
        }
        log.debug("步骤1.0: 公开报表企业范围匹配到 {} 个物流订单", logisticsOrderIds.size());

        // 查询运单子任务列表，仅获取关键状态字段以优化性能
        // 过滤条件：
        // 1. 未删除 (delFlag = 0)
        // 2. 属于当前报表企业范围内的物流订单
        // 3. 状态不为“已取消”
        List<KwtWaybillOrderSubtask> subtasks = waybillOrderSubtaskRepository.queryValidByLogisticsOrderIds(logisticsOrderIds);

        // 若未查询到任何有效子任务，直接返回空统计结果
        if (CollectionUtils.isEmpty(subtasks)) {
            log.warn("未查询到有效子任务，返回空结果");
            vo.setPendingEntryCount(0);
            vo.setLoadingOperationCount(0);
            vo.setDeliveringCount(0);
            return vo;
        }
        log.info("步骤1完成: 查询到 {} 条有效子任务", subtasks.size());

        // 2. 按运单ID聚合子任务信息
        // 对于每个运单，提取最大状态值以及“出场后、卸货前”窗口期的最后更新时间
        log.debug("步骤2: 按运单ID聚合子任务信息...");
        Map<Long, WaybillSubtaskAgg> aggByWaybill = subtasks.stream()
                .filter(st -> st.getWOrderId() != null && st.getStatus() != null)
                .collect(Collectors.groupingBy(
                        KwtWaybillOrderSubtask::getWOrderId,
                        Collectors.reducing(new WaybillSubtaskAgg(), SubtaskCapacityAnalysisService::partialAgg, SubtaskCapacityAnalysisService::mergeAgg)));

        Set<Long> wOrderIds = aggByWaybill.keySet();
        log.info("步骤2完成: 聚合得到 {} 个唯一运单ID", wOrderIds.size());

        // 3. 加载辅助数据
        log.debug("步骤3: 加载辅助数据...");

        // 3.1 加载包含卸货地址的运单ID集合
        Set<Long> hasUnloadWaybillIds = loadUnloadWaybillIds(wOrderIds);
        log.debug("步骤3.1: 加载到 {} 个包含卸货地址的运单", hasUnloadWaybillIds.size());

        // 3.2 加载门岗离场时间
        Map<Long, Date> leaveTimeByWaybill = loadGatekeeperLeaveTime(wOrderIds);
        log.debug("步骤3.2: 加载到 {} 个运单的门岗离场时间", leaveTimeByWaybill.size());

        // 3.3 加载运单主表的更新时间
        Map<Long, Date> waybillUpdate = loadWaybillUpdateTime(wOrderIds);
        log.debug("步骤3.3: 加载到 {} 个运单的主表更新时间", waybillUpdate.size());

        // 计算两小时前的时间点，用于判断无卸货地址运单是否处于活跃运输状态
        Date twoHoursAgo = Date.from(Instant.now().minus(2, ChronoUnit.HOURS));
        log.debug("步骤3.4: 计算活跃运输判定时间点: {}", twoHoursAgo);

        // 4. 分类统计各运单状态
        log.debug("步骤4: 开始分类统计各运单状态...");
        CapacityCounts counts = aggByWaybill.entrySet().stream()
                .map(e -> classify(e.getKey(), e.getValue(), hasUnloadWaybillIds, leaveTimeByWaybill, waybillUpdate, twoHoursAgo))
                .reduce(CapacityCounts.ZERO, CapacityCounts::add);

        log.info("=== 运力分析完成 === - 待入场: {}, 装卸作业中: {}, 配送中: {}",
                counts.pending(), counts.loading(), counts.delivering());

        vo.setPendingEntryCount(counts.pending());
        vo.setLoadingOperationCount(counts.loading());
        vo.setDeliveringCount(counts.delivering());
        return vo;
    }

    /**
     * 部分聚合：将单个子任务转换为聚合对象
     * <p>
     * 逻辑：
     * 1. 记录当前子任务的状态。
     * 2. 如果状态处于“出场后、卸货前”窗口期，记录其更新时间，用于后续判断活跃性。
     *
     * @param st 子任务对象
     * @return 聚合对象
     */
    private static WaybillSubtaskAgg partialAgg(KwtWaybillOrderSubtask st) {
        WaybillSubtaskAgg a = new WaybillSubtaskAgg();
        a.maxStatus = st.getStatus();
        // 如果状态处于“出场后、卸货前”窗口期，记录其更新时间
        if (inPostExitPreUnloadWindow(st.getStatus()) && st.getUpdateTime() != null) {
            a.lastUpdateWhenPostExit = st.getUpdateTime();
        }
        return a;
    }

    /**
     * 合并聚合：合并两个聚合对象
     * <p>
     * 逻辑：
     * 1. 取两个对象中较大的状态值作为最终状态。
     * 2. 取“出场后、卸货前”窗口期的最晚更新时间。
     *
     * @param a 聚合对象A
     * @param b 聚合对象B
     * @return 合并后的聚合对象
     */
    private static WaybillSubtaskAgg mergeAgg(WaybillSubtaskAgg a, WaybillSubtaskAgg b) {
        WaybillSubtaskAgg r = new WaybillSubtaskAgg();
        // 取最大状态值，代表该运单达到的最高进度
        r.maxStatus = Math.max(a.maxStatus, b.maxStatus);
        // 取“出场后、卸货前”窗口期的最晚更新时间
        r.lastUpdateWhenPostExit = Stream.of(a.lastUpdateWhenPostExit, b.lastUpdateWhenPostExit)
                .filter(Objects::nonNull)
                .max(Date::compareTo)
                .orElse(null);
        return r;
    }

    /**
     * 判断状态是否处于“出场后、卸货前”窗口期
     * <p>
     * 定义范围：WAIT_LOADING <= status < COMPLETION_LOADING
     * 此区间通常对应车辆已出场但在途或等待卸货的状态。
     *
     * @param status 状态码
     * @return 是否在窗口期内
     */
    private static boolean inPostExitPreUnloadWindow(int status) {
        return status >= CarWaybillV1Enum.WAIT_LOADING.getCode()
                && status < CarWaybillV1Enum.COMPLETION_LOADING.getCode();
    }

    /**
     * 查询公开报表固定企业范围对应的物流订单ID。
     * <p>
     * 先从 kwt_logistics_order_unit 表按固定企业范围获取物流订单，
     * 再进入后续子任务运力分析逻辑。
     * </p>
     *
     * @return 公开报表企业范围内的物流订单ID
     */
    private List<Long> queryReportLogisticsOrderIds() {
        List<KwtLogisticsOrderUnit> units = logisticsOrderUnitRepository.queryByReportEntIds(REPORT_ENT_IDS);
        if (CollectionUtils.isEmpty(units)) {
            return Collections.emptyList();
        }
        return units.stream()
                .filter(Objects::nonNull)
                .map(KwtLogisticsOrderUnit::getLOrderId)
                .filter(Objects::nonNull)
                .distinct()
                .toList();
    }

    /**
     * 分类单个运单的状态
     * <p>
     * 优先级：
     * 1. 无效/驳回状态 -> 忽略
     * 2. 配送中 (Delivering)
     * 3. 待入场 (Pending Entry)
     * 4. 装卸作业中 (Loading Operation)
     * 5. 其他 -> 忽略
     *
     * @param wid                 运单ID
     * @param agg                 运单子任务聚合信息
     * @param hasUnloadWaybillIds 包含卸货地址的运单ID集合
     * @param leaveTimeByWaybill  门岗离场时间映射
     * @param waybillUpdate       运单主表更新时间映射
     * @param twoHoursAgo         两小时前的时间点
     * @return 分类计数结果
     */
    private static CapacityCounts classify(
            Long wid,
            WaybillSubtaskAgg agg,
            Set<Long> hasUnloadWaybillIds,
            Map<Long, Date> leaveTimeByWaybill,
            Map<Long, Date> waybillUpdate,
            Date twoHoursAgo) {
        
        int ms = agg.maxStatus;
        
        // 无效状态或审核驳回，不计入任何类别
        if (ms <= 0 || Objects.equals(ms, CarWaybillV1Enum.REVIEW_REJECTION.getCode())) {
            log.trace("运单ID: {} 状态无效或已驳回 (status={}), 忽略", wid, ms);
            return CapacityCounts.ZERO;
        }

        boolean hasUnload = hasUnloadWaybillIds.contains(wid);
        
        // 优先判断是否为“配送中”
        if (isDelivering(ms, hasUnload, leaveTimeByWaybill.get(wid), agg.lastUpdateWhenPostExit, waybillUpdate.get(wid), twoHoursAgo)) {
            log.trace("运单ID: {} 分类为: 配送中", wid);
            return CapacityCounts.deliveringOne();
        }
        
        // 判断是否为“待入场”
        if (ms == CarWaybillV1Enum.PENDING_VEHICLE.getCode()) {
            log.trace("运单ID: {} 分类为: 待入场", wid);
            return CapacityCounts.pendingOne();
        }
        
        // 判断是否为“装卸作业中”
        // 包括：拒绝通行、出场完成、称重通行
        List<Integer> statusList = Arrays.asList(
                CarWaybillV1Enum.REFUSE_TRAFFIC.getCode(),
                CarWaybillV1Enum.EXIT_COMPLETED.getCode(),
                CarWaybillV1Enum.EMPTY_WAIT_LEAVE.getCode(),
                CarWaybillV1Enum.WAIT_LEAVE.getCode(),
                CarWaybillV1Enum.UNLOADING.getCode(),
                CarWaybillV1Enum.WAIT_RELEASE.getCode(),
                CarWaybillV1Enum.REPLENISHING.getCode(),
                CarWaybillV1Enum.REPLENISH_FINISH.getCode(),
                CarWaybillV1Enum.RELEASED_NOT_EXITED.getCode()
        );
        if (statusList.contains( ms)) {
            log.trace("运单ID: {} 分类为: 装卸作业中 (status={})", wid, ms);
            return CapacityCounts.loadingOne();
        }
        
        // 其他状态不计入
        log.trace("运单ID: {} 状态未匹配任何分类 (status={}), 忽略", wid, ms);
        return CapacityCounts.ZERO;
    }

    /**
     * 判断运单是否处于“配送中”状态
     * <p>
     * 逻辑：
     * 1. 排除审核驳回和已取消状态
     * 2. 如果有卸货地址：状态在 [WAIT_LOADING, COMPLETION_LOADING) 之间视为配送中
     * 3. 如果没有卸货地址：
     *    - 状态必须在 [WAIT_LOADING, COMPLETION_LOADING) 之间
     *    - 且参考时间（门岗离场时间 > 子任务更新时间 > 运单更新时间）必须在两小时内
     *
     * @param ms                    最大状态码
     * @param hasUnload             是否有卸货地址
     * @param gateLeave             门岗离场时间
     * @param lastPostExitSubtaskUpd 子任务在出场后卸货前的最后更新时间
     * @param waybillUpdate         运单主表更新时间
     * @param twoHoursAgo           两小时前的时间点
     * @return 是否配送中
     */
    private static boolean isDelivering(int ms, boolean hasUnload, Date gateLeave, Date lastPostExitSubtaskUpd,
                                        Date waybillUpdate, Date twoHoursAgo) {
        // 排除终止状态
        if (Objects.equals(ms, CarWaybillV1Enum.REVIEW_REJECTION.getCode())
                || Objects.equals(ms, CarWaybillV1Enum.CANCELLED.getCode())) {
            return false;
        }

        if (hasUnload) {
            // 有卸货地址：只要状态在等待装货到完成装货之间，即视为在途/配送
            // 因为存在明确的卸货点，状态在此区间即代表正在执行运输任务
            boolean isDelivering = ms >= CarWaybillV1Enum.WAIT_LOADING.getCode()
                    && ms < CarWaybillV1Enum.COMPLETION_LOADING.getCode();
            if (isDelivering) {
                log.trace("运单有卸货地址且状态在配送区间, 判定为配送中");
            }
            return isDelivering;
        }

        // 无卸货地址：必须满足状态区间且时间在两小时内
        // 防止历史数据或长时间停滞的数据被误判为配送中
        if (ms < CarWaybillV1Enum.WAIT_LOADING.getCode()
                || ms >= CarWaybillV1Enum.COMPLETION_LOADING.getCode()) {
            return false;
        }

        // 确定参考时间：优先使用门岗离场时间，其次子任务更新时间，最后运单更新时间
        Date ref = Optional.ofNullable(gateLeave)
                .or(() -> Optional.ofNullable(lastPostExitSubtaskUpd))
                .or(() -> Optional.ofNullable(waybillUpdate))
                .orElse(null);
        
        // 参考时间存在且未超过两小时
        boolean isActive = ref != null && !ref.before(twoHoursAgo);
        if (isActive) {
            log.trace("运单无卸货地址，但状态在配送区间且最近活跃 (ref={}), 判定为配送中", ref);
        } else {
            log.trace("运单无卸货地址，虽状态在配送区间但不活跃 (ref={}), 不判定为配送中", ref);
        }
        return isActive;
    }

    /**
     * 加载包含卸货地址的运单ID集合
     *
     * @param wOrderIds 运单ID集合
     * @return 包含卸货地址的运单ID集合
     */
    private Set<Long> loadUnloadWaybillIds(Set<Long> wOrderIds) {
        if (CollectionUtils.isEmpty(wOrderIds)) {
            return Set.of();
        }
        log.debug("正在查询 {} 个运单的地址信息以筛选卸货地址...", wOrderIds.size());
        // addressType = 2 表示卸货地址
        Set<Long> result = waybillOrderAddressRepository.queryBywOrderIds(new ArrayList<>(wOrderIds)).stream()
                .filter(a -> Objects.equals(NumberConstant.TWO, a.getAddressType()))
                .map(KwtWaybillOrderAddress::getWOrderId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        log.debug("筛选出 {} 个包含卸货地址的运单", result.size());
        return result;
    }

    /**
     * 加载门岗离场时间
     *
     * @param wOrderIds 运单ID集合
     * @return 运单ID -> 离场时间的映射
     */
    private Map<Long, Date> loadGatekeeperLeaveTime(Set<Long> wOrderIds) {
        if (CollectionUtils.isEmpty(wOrderIds)) {
            return Map.of();
        }
        log.debug("正在查询 {} 个运单的门岗记录...", wOrderIds.size());
        List<KwtGatekeeperWaybillOrder> rows = gatekeeperWaybillOrderRepository.queryGatekeeperWaybillOrderByWOrderIds(new ArrayList<>(wOrderIds));
        if (CollectionUtils.isEmpty(rows)) {
            log.debug("未查询到任何门岗记录");
            return Map.of();
        }
        
        // 按运单ID分组，并提取每个运单最新的已离场记录的时间
        Map<Long, Date> result = rows.stream()
                .collect(Collectors.groupingBy(KwtGatekeeperWaybillOrder::getWOrderId))
                .entrySet().stream()
                .map(SubtaskCapacityAnalysisService::latestExitedLeaveEntry)
                .flatMap(Optional::stream)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        
        log.debug("解析出 {} 个运单的有效离场时间", result.size());
        return result;
    }

    /**
     * 获取单个运单最新的已离场记录的离场时间
     *
     * @param e 运单ID及其门岗记录列表
     * @return 运单ID -> 离场时间的可选映射
     */
    private static Optional<Map.Entry<Long, Date>> latestExitedLeaveEntry(Map.Entry<Long, List<KwtGatekeeperWaybillOrder>> e) {
        Long wid = e.getKey();
        return e.getValue().stream()
                // 取ID最大的记录（通常代表最新）
                .max(Comparator.comparing(KwtGatekeeperWaybillOrder::getId, Comparator.nullsLast(Long::compareTo)))
                // 过滤出状态为“已出场”或“空车出场”的记录
                .filter(gk -> GatekeeperStatusEnum.EXITED.getCode().equals(gk.getStatus())
                        || GatekeeperStatusEnum.EMPTY_EXITED.getCode().equals(gk.getStatus()))
                // 取离场时间，若为空则取更新时间
                .map(gk -> Optional.ofNullable(gk.getLeaveTime()).orElse(gk.getUpdateTime()))
                .filter(Objects::nonNull)
                .map(leave -> new AbstractMap.SimpleEntry<>(wid, leave));
    }

    /**
     * 加载运单主表的更新时间
     *
     * @param wOrderIds 运单ID集合
     * @return 运单ID -> 更新时间的映射
     */
    private Map<Long, Date> loadWaybillUpdateTime(Set<Long> wOrderIds) {
        if (CollectionUtils.isEmpty(wOrderIds)) {
            return Map.of();
        }
        log.debug("正在查询 {} 个运单的主表更新时间...", wOrderIds.size());
        Map<Long, Date> result = waybillOrderRepository.listByIds(wOrderIds).stream()
                .filter(wo -> wo.getId() != null && wo.getUpdateTime() != null)
                .collect(Collectors.toMap(KwtWaybillOrder::getId, KwtWaybillOrder::getUpdateTime));
        log.debug("获取到 {} 个运单的主表更新时间", result.size());
        return result;
    }

    /**
     * 容量计数记录
     * <p>
     * 用于累加统计三种状态的运单数量
     */
    private record CapacityCounts(int pending, int loading, int delivering) {
        static final CapacityCounts ZERO = new CapacityCounts(0, 0, 0);

        static CapacityCounts pendingOne() {
            return new CapacityCounts(1, 0, 0);
        }

        static CapacityCounts loadingOne() {
            return new CapacityCounts(0, 1, 0);
        }

        static CapacityCounts deliveringOne() {
            return new CapacityCounts(0, 0, 1);
        }

        CapacityCounts add(CapacityCounts o) {
            return new CapacityCounts(pending + o.pending, loading + o.loading, delivering + o.delivering);
        }
    }

    /**
     * 运单子任务聚合信息
     * <p>
     * 用于在 Stream 归约过程中暂存每个运单的关键状态信息
     */
    private static final class WaybillSubtaskAgg {
        /**
         * 最大状态码
         * <p>
         * 代表该运单下所有子任务中达到的最高业务状态
         */
        private int maxStatus;
        /**
         * 在“出场后、卸货前”窗口期的最后更新时间
         * <p>
         * 用于辅助判断无卸货地址运单的活跃性
         */
        private Date lastUpdateWhenPostExit;
    }
}
