package com.ruoyi.app.flashdelivery.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.app.flashdelivery.dto.*;
import com.ruoyi.app.flashdelivery.exception.FlashDeliveryQuoteChangedException;
import com.ruoyi.app.flashdelivery.route.FlashDeliveryRouteService;
import com.ruoyi.app.flashdelivery.route.GeoPoint;
import com.ruoyi.app.flashdelivery.route.RouteDistance;
import com.ruoyi.app.order.RiderDeliveryExclusivityService;
import com.ruoyi.app.order.RiderDeliveryLockService;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.InfoUser;
import com.ruoyi.system.domain.flash.*;
import com.ruoyi.system.mapper.InfoUserMapper;
import com.ruoyi.system.mapper.flash.*;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.URI;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;

import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.*;

/**
 * 闪送业务编排服务。
 *
 * <p>负责报价、订单创建、抢单、履约状态流转、平台介入和审计日志。
 * 订单身份只接受 Controller 从 token 解析后的用户 ID，客户端传入的金额、距离或操作人均不可信。</p>
 */
@Service
public class FlashDeliveryApplicationService {
    private static final List<String> SERVICE_TYPES = List.of("HELP_SEND", "HELP_PICKUP");
    private static final Set<String> DELIVERY_TYPES = Set.of("NORMAL", "URGENT");
    private static final Set<String> PACKAGE_TYPES = Set.of("DOCUMENT", "GIFT", "CLOTHING", "BEAUTY",
            "DAILY_NECESSITIES", "FOOD_INGREDIENTS", "ELECTRONICS", "SMALL_APPLIANCE", "OTHER");
    private static final Set<String> DELIVERY_MODES = Set.of("NOW", "SCHEDULED");
    private static final BigDecimal MAX_TOTAL_WEIGHT_KG = new BigDecimal("20.00");
    private static final DateTimeFormatter PRICING_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
    private static final ZoneId TAIPEI = ZoneId.of("Asia/Taipei");
    private static final int MAX_DISTANCE_METERS = 40_000;
    private static final BigDecimal MAX_PRICING_DISTANCE = new BigDecimal("999999.99");
    private static final long SCHEDULE_SLOT_MILLIS = 30L * 60L * 1000L;
    private static final long MAX_SCHEDULE_DELAY_MILLIS = 3L * 24L * 60L * 60L * 1000L;
    private static final SecureRandom PIN_RANDOM = new SecureRandom();
    private final FlashDeliveryOrderMapper orderMapper;
    private final FlashDeliveryPricingMapper pricingMapper;
    private final FlashDeliveryOrderImageMapper imageMapper;
    private final FlashDeliveryOrderLogMapper logMapper;
    private final InfoUserMapper userMapper;
    private final FlashDeliveryRouteService routeService;
    private final FlashDeliveryPricingCalculator pricingCalculator;
    private final RiderDeliveryLockService riderDeliveryLockService;
    private final RiderDeliveryExclusivityService riderDeliveryExclusivityService;

    public FlashDeliveryApplicationService(FlashDeliveryOrderMapper orderMapper,
                                           FlashDeliveryPricingMapper pricingMapper,
                                           FlashDeliveryOrderImageMapper imageMapper,
                                           FlashDeliveryOrderLogMapper logMapper,
                                           InfoUserMapper userMapper,
                                           FlashDeliveryRouteService routeService,
                                           FlashDeliveryPricingCalculator pricingCalculator,
                                           RiderDeliveryLockService riderDeliveryLockService,
                                           RiderDeliveryExclusivityService riderDeliveryExclusivityService) {
        this.orderMapper = orderMapper;
        this.pricingMapper = pricingMapper;
        this.imageMapper = imageMapper;
        this.logMapper = logMapper;
        this.userMapper = userMapper;
        this.routeService = routeService;
        this.pricingCalculator = pricingCalculator;
        this.riderDeliveryLockService = riderDeliveryLockService;
        this.riderDeliveryExclusivityService = riderDeliveryExclusivityService;
    }

    public FlashDeliveryHomeView home() {
        FlashDeliveryHomeView view = new FlashDeliveryHomeView();
        view.setServiceTypes(SERVICE_TYPES);
        view.setDeliveryTypes(List.of("NORMAL", "URGENT"));
        FlashDeliveryPricing pricing = findPricingAtOrNull(pricingTargetTime(new Date()));
        view.setServices(pricing == null ? List.of() : SERVICE_TYPES.stream()
                .map(serviceType -> serviceView(pricing, serviceType)).toList());
        return view;
    }

    public FlashDeliveryQuoteView quote(FlashDeliveryQuoteRequest request) {
        // 报价只使用服务端路线距离和当前启用的计价配置，避免客户端篡改金额。
        QuoteContext context = calculateQuote(request);
        return quoteView(request, context);
    }

    private FlashDeliveryQuoteView quoteView(FlashDeliveryQuoteRequest request, QuoteContext context) {
        FlashDeliveryQuoteView view = new FlashDeliveryQuoteView();
        view.setServiceType(request.getServiceType());
        view.setDeliveryType(request.getDeliveryType());
        view.setDeliveryMode(normalizeDeliveryMode(request.getDeliveryMode()));
        view.setScheduledPickupStartAt(request.getScheduledPickupStartAt());
        view.setScheduledPickupEndAt(request.getScheduledPickupEndAt());
        view.setPricingId(context.pricing().getId());
        view.setStartTime(context.pricing().getStartTime());
        view.setEndTime(context.pricing().getEndTime());
        view.setDistanceMeters(context.route().distanceMeters());
        view.setDistanceSource(context.route().source());
        view.setEstimatedDurationSeconds(context.route().durationSeconds());
        view.setStartingDistance(context.pricing().getStartingDistance());
        view.setStartingFare(context.pricing().getStartingFare());
        view.setDistance(context.pricing().getDistance());
        view.setFreight(context.pricing().getFreight());
        view.setUrgentRate(context.pricing().getUrgentRate());
        view.setMinimumUrgentFee(context.pricing().getMinimumUrgentFee());
        applyBreakdown(view, context.breakdown());
        view.setCurrency("TWD");
        view.setPricingVersion(context.pricing().getConfigVersion());
        return view;
    }

    @Transactional
    public FlashDeliveryOrderDetailView create(Long userId, FlashDeliveryCreateRequest request) {
        requireUserId(userId);
        if (request == null || !hasText(request.getClientRequestId())
                || request.getClientRequestId().trim().length() > 64) {
            throw fail("flash.delivery.client.request.id.invalid");
        }
        String requestId = request.getClientRequestId().trim();
        // 用户 ID + clientRequestId 构成幂等键；重复请求直接返回第一次创建的订单。
        FlashDeliveryOrder existing = orderMapper.selectByUserRequestId(userId, requestId);
        if (existing != null) return participantDetail(existing, true, false);
        if (request.getUserNote() != null && request.getUserNote().length() > 500) {
            throw fail("flash.delivery.note.too.long");
        }
        QuoteContext quote = calculateQuote(request);
        FlashDeliveryQuoteView latestQuote = quoteView(request, quote);
        if (!matchesQuotedPrice(request, latestQuote)) {
            throw new FlashDeliveryQuoteChangedException(latestQuote);
        }
        Date now = new Date();
        String deliveryMode = normalizeDeliveryMode(request.getDeliveryMode());
        validateSchedule(deliveryMode, request.getScheduledPickupStartAt(), request.getScheduledPickupEndAt(), now);
        boolean pinRequired = request.getPinRequired() == null || request.getPinRequired();
        List<String> senderImages = validateProofUrls(request.getSenderImageUrls(), false);
        FlashDeliveryOrder order = new FlashDeliveryOrder();
        order.setOrderNo("FD" + UUID.randomUUID().toString().replace("-", "").substring(0, 24).toUpperCase(Locale.ROOT));
        order.setClientRequestId(requestId);
        order.setUserId(userId);
        order.setServiceType(request.getServiceType());
        order.setDeliveryType(request.getDeliveryType());
        order.setStatus(WAITING_ACCEPTANCE);
        order.setPackageType(request.getPackageType());
        order.setQuantity(request.getQuantity());
        order.setTotalWeightKg(scaleWeight(request.getTotalWeightKg()));
        order.setSpecification(trimToNull(request.getSpecification()));
        order.setPackageSize(packageSize(request.getTotalWeightKg()));
        order.setDeliveryMode(deliveryMode);
        order.setScheduledPickupStartAt("SCHEDULED".equals(deliveryMode) ? request.getScheduledPickupStartAt() : null);
        order.setScheduledPickupEndAt("SCHEDULED".equals(deliveryMode) ? request.getScheduledPickupEndAt() : null);
        order.setPinRequired(pinRequired);
        order.setDeliveryPinCode(pinRequired ? String.format(Locale.ROOT, "%04d", PIN_RANDOM.nextInt(10_000)) : null);
        copyAddress(request.getPickup(), order, true);
        copyAddress(request.getDelivery(), order, false);
        order.setReceiverUserId(resolveReceiverUserId(order.getDeliveryPhone()));
        order.setDistanceMeters(quote.route().distanceMeters());
        order.setDistanceSource(quote.route().source());
        order.setEstimatedDurationSeconds(quote.route().durationSeconds());
        order.setAmount(quote.breakdown().getAmount());
        order.setCurrency("TWD");
        order.setPricingId(quote.pricing().getId());
        order.setPricingVersion(quote.pricing().getConfigVersion());
        order.setPricingStartTime(quote.pricing().getStartTime());
        order.setPricingEndTime(quote.pricing().getEndTime());
        order.setStartingDistance(quote.pricing().getStartingDistance());
        order.setStartingFare(quote.pricing().getStartingFare());
        order.setDistance(quote.pricing().getDistance());
        order.setFreight(quote.pricing().getFreight());
        // 保存计价快照，后台调价只影响新订单，不追溯修改历史订单金额。
        order.setDistanceFee(quote.breakdown().getDistanceFee());
        order.setBaseDeliveryFee(quote.breakdown().getBaseDeliveryFee());
        order.setUrgentRate(quote.pricing().getUrgentRate());
        order.setMinimumUrgentFee(quote.pricing().getMinimumUrgentFee());
        order.setUrgentFee(quote.breakdown().getUrgentFee());
        order.setTipAmount(quote.breakdown().getTipAmount());
        order.setUserNote(trimToNull(request.getUserNote()));
        order.setVersion(0);
        order.setCreateTime(now);
        order.setUpdateTime(now);
        try {
            orderMapper.insert(order);
        } catch (DataIntegrityViolationException duplicate) {
            // 并发重复请求可能同时通过前置查询，最终由数据库唯一键完成幂等收口。
            FlashDeliveryOrder concurrent = orderMapper.selectByUserRequestId(userId, requestId);
            if (concurrent != null) return participantDetail(concurrent, true, false);
            throw duplicate;
        }
        saveImages(order.getId(), "SENDER", "USER", userId, senderImages, now);
        writeLog(order.getId(), null, WAITING_ACCEPTANCE, "USER", userId, null, now);
        return participantDetail(order, true, false);
    }

    public IPage<FlashDeliveryUserOrderListView> userOrders(Long userId, int pageNum, int pageSize, String role) {
        String selectedRole = hasText(role) ? role.trim().toLowerCase(Locale.ROOT) : "sender";
        if (!"sender".equals(selectedRole) && !"receiver".equals(selectedRole)) {
            throw fail("flash.delivery.tab.invalid");
        }
        String participantColumn = "receiver".equals(selectedRole) ? "receiver_user_id" : "user_id";
        QueryWrapper<FlashDeliveryOrder> query = new QueryWrapper<FlashDeliveryOrder>()
                .eq(participantColumn, userId).orderByDesc("create_time");
        return mapPage(orderMapper.selectPage(page(pageNum, pageSize), query), this::userOrderListView);
    }

    public FlashDeliveryOrderDetailView userDetail(Long userId, Long orderId) {
        FlashDeliveryOrder order = requireOrder(orderId);
        if (!isParticipant(userId, order)) throw fail("flash.delivery.order.not.found");
        return participantDetail(order, true, false);
    }

    @Transactional
    public void userCancel(Long userId, Long orderId, FlashDeliveryReasonRequest request) {
        FlashDeliveryOrder order = requireOrder(orderId);
        if (!Objects.equals(userId, order.getUserId())) throw fail("flash.delivery.order.not.found");
        if (!FlashDeliveryStateMachine.canUserCancel(order.getStatus())) throw fail("flash.delivery.cancel.not.allowed");
        String reason = requiredReason(request);
        Date now = new Date();
        if (orderMapper.cancel(orderId, order.getStatus(), "USER", userId, reason, now) != 1) {
            throw fail("flash.delivery.state.changed");
        }
        writeLog(orderId, order.getStatus(), CANCELLED, "USER", userId, reason, now);
    }

    @Transactional
    public void confirmReceipt(Long userId, Long orderId) {
        FlashDeliveryOrder order = requireOrder(orderId);
        if (!isParticipant(userId, order)) throw fail("flash.delivery.order.not.found");
        Date now = new Date();
        if (orderMapper.transitionByParticipant(orderId, userId, DELIVERED, COMPLETED, now) != 1) {
            throw fail("flash.delivery.complete.not.allowed");
        }
        writeLog(orderId, DELIVERED, COMPLETED, "USER", userId, null, now);
    }

    public FlashDeliveryRiderOrderPageView riderOrders(Long riderId, int pageNum, int pageSize,
                                                        String tab, BigDecimal longitude, BigDecimal latitude) {
        InfoUser rider = requireRider(riderId);
        if (!hasText(tab)) throw fail("flash.delivery.tab.invalid");
        String selectedTab = tab.trim();
        // lambda 无法捕获被重新赋值的参数，统一改用仅 newTask 携带坐标的有效变量。
        BigDecimal effectiveLongitude = "newTask".equals(selectedTab) ? longitude : null;
        BigDecimal effectiveLatitude = "newTask".equals(selectedTab) ? latitude : null;
        if ("newTask".equals(selectedTab)) {
            // 新任务派单按骑手选择的配送类型过滤；本人已接订单页签不受限制。
            if (!rider.supportsDeliveryType("FLASH")) throw fail("flash.delivery.rider.type.not.enabled");
            validateRiderCoordinates(effectiveLongitude, effectiveLatitude);
        }
        QueryWrapper<FlashDeliveryOrder> query = riderOrdersQuery(riderId, selectedTab,
                effectiveLongitude, effectiveLatitude, true);
        IPage<FlashDeliveryOrder> orders = orderMapper.selectPage(page(pageNum, pageSize), query);
        FlashDeliveryRiderOrderPageView result = new FlashDeliveryRiderOrderPageView();
        result.setRecords(orders.getRecords().stream()
                .map(order -> riderOrderListView(order, effectiveLongitude, effectiveLatitude)).toList());
        result.setTotal(orders.getTotal());
        result.setCurrent(orders.getCurrent());
        result.setSize(orders.getSize());
        if ("newTask".equals(selectedTab)) {
            result.setNearbyTaskCount(orders.getTotal());
            QueryWrapper<FlashDeliveryOrder> maximumQuery = riderOrdersQuery(riderId, selectedTab,
                    effectiveLongitude, effectiveLatitude, false)
                    .select("MAX(amount)");
            result.setHighestOrderAmount(firstLong(orderMapper.selectObjs(maximumQuery)));
        }
        return result;
    }

    public FlashDeliveryOrderDetailView riderDetail(Long riderId, Long orderId) {
        InfoUser rider = requireRider(riderId);
        FlashDeliveryOrder order = requireOrder(orderId);
        if (WAITING_ACCEPTANCE.equals(order.getStatus()) && order.getRiderId() == null) {
            // 不兼容闪送的骑手按订单不存在处理，避免泄露待抢任务信息。
            if (!isAvailableAt(order, new Date()) || !rider.supportsDeliveryType("FLASH")) {
                throw fail("flash.delivery.order.not.found");
            }
            // 接单前显示取送文字地址，但隐藏联系人、电话、精确坐标、实际 PIN 和履约图片。
            return participantDetail(order, false, true);
        }
        if (!Objects.equals(riderId, order.getRiderId())) throw fail("flash.delivery.order.not.found");
        return participantDetail(order, false, false);
    }

    @Transactional
    public FlashDeliveryOrderDetailView accept(Long riderId, Long orderId) {
        InfoUser rider = requireRider(riderId);
        // 抢单属于获取新任务，要求骑手承接闪送配送。
        if (!rider.supportsDeliveryType("FLASH")) throw fail("flash.delivery.rider.type.not.enabled");
        return riderDeliveryLockService.withLock(riderId, () -> {
            FlashDeliveryOrder target = requireOrder(orderId);
            riderDeliveryExclusivityService.assertCanAcceptFlash(riderId, target.getDeliveryType());
            Date now = new Date();
            // Mapper 使用“待接单且 rider_id 为空”的条件更新，受影响行数为 1 才表示抢单成功。
            if (orderMapper.accept(orderId, riderId, now) != 1) {
                throw fail("flash.delivery.order.already.accepted");
            }
            writeLog(orderId, WAITING_ACCEPTANCE, ACCEPTED, "RIDER", riderId, null, now);
            return participantDetail(requireOrder(orderId), false, false);
        });
    }

    @Transactional
    public void pickup(Long riderId, Long orderId, FlashDeliveryProofRequest request) {
        transitionWithProof(riderId, orderId, request, ACCEPTED, PICKED_UP, "PICKUP", "picked_up_at");
    }

    @Transactional
    public void deliver(Long riderId, Long orderId, FlashDeliveryDeliverRequest request) {
        requireRider(riderId);
        FlashDeliveryOrder order = requireOrder(orderId);
        if (!Objects.equals(riderId, order.getRiderId()) || !PICKED_UP.equals(order.getStatus())) {
            throw fail("flash.delivery.transition.not.allowed");
        }
        if (Boolean.TRUE.equals(order.getPinRequired())
                && (request == null || !Objects.equals(order.getDeliveryPinCode(), trimToNull(request.getPinCode())))) {
            throw fail("flash.delivery.pin.invalid");
        }
        transitionWithProof(riderId, orderId, request, PICKED_UP, DELIVERED, "DELIVERY", "delivered_at");
    }

    public List<FlashDeliveryPricing> adminPricing() {
        return pricingMapper.selectList(new QueryWrapper<FlashDeliveryPricing>()
                .orderByAsc("start_time", "id"));
    }

    @Transactional
    public FlashDeliveryPricing createPricing(Long adminId, FlashDeliveryPricingRequest request) {
        validatePricingRequest(request, false);
        lockPricing();
        ensureNoPricingOverlap(request, null);
        Date now = new Date();
        FlashDeliveryPricing pricing = pricingFrom(request);
        pricing.setConfigVersion(1);
        pricing.setUpdatedBy(adminId);
        pricing.setCreateTime(now);
        pricing.setUpdateTime(now);
        pricingMapper.insert(pricing);
        return pricing;
    }

    @Transactional
    public FlashDeliveryPricing updatePricing(Long adminId, Long pricingId,
                                               FlashDeliveryPricingRequest request) {
        validatePricingRequest(request, true);
        FlashDeliveryPricing preliminary = pricingId == null ? null : pricingMapper.selectById(pricingId);
        if (preliminary == null) throw fail("flash.delivery.pricing.not.found");
        lockPricing();
        FlashDeliveryPricing pricing = pricingMapper.selectById(pricingId);
        if (pricing == null) throw fail("flash.delivery.pricing.not.found");
        ensureNoPricingOverlap(request, pricingId);
        Date now = new Date();
        UpdateWrapper<FlashDeliveryPricing> update = new UpdateWrapper<FlashDeliveryPricing>()
                .eq("id", pricingId).eq("config_version", request.getConfigVersion())
                .set("start_time", request.getStartTime())
                .set("end_time", request.getEndTime())
                .set("starting_distance", scaleDistance(request.getStartingDistance()))
                .set("starting_fare", request.getStartingFare())
                .set("distance", scaleDistance(request.getDistance()))
                .set("freight", request.getFreight())
                .set("urgent_rate", normalizeRate(request.getUrgentRate()))
                .set("minimum_urgent_fee", request.getMinimumUrgentFee())
                .set("updated_by", adminId)
                .set("update_time", now)
                .setSql("config_version = config_version + 1");
        // 版本条件防止两个管理员同时保存时后提交者静默覆盖先提交者。
        if (pricingMapper.update(null, update) != 1) throw fail("flash.delivery.pricing.changed");
        pricing.setStartTime(request.getStartTime());
        pricing.setEndTime(request.getEndTime());
        pricing.setStartingDistance(scaleDistance(request.getStartingDistance()));
        pricing.setStartingFare(request.getStartingFare());
        pricing.setDistance(scaleDistance(request.getDistance()));
        pricing.setFreight(request.getFreight());
        pricing.setUrgentRate(normalizeRate(request.getUrgentRate()));
        pricing.setMinimumUrgentFee(request.getMinimumUrgentFee());
        pricing.setConfigVersion(request.getConfigVersion() + 1);
        pricing.setUpdatedBy(adminId);
        pricing.setUpdateTime(now);
        return pricing;
    }

    @Transactional
    public void deletePricing(Long pricingId) {
        FlashDeliveryPricing preliminary = pricingId == null ? null : pricingMapper.selectById(pricingId);
        if (preliminary == null) {
            throw fail("flash.delivery.pricing.not.found");
        }
        lockPricing();
        FlashDeliveryPricing pricing = pricingMapper.selectById(pricingId);
        if (pricing == null) throw fail("flash.delivery.pricing.not.found");
        if (pricingMapper.deleteById(pricingId) != 1) throw fail("flash.delivery.pricing.not.found");
    }

    public IPage<FlashDeliveryOrder> adminOrders(int pageNum, int pageSize, String status,
                                                 String serviceType, String orderNo,
                                                 Long userId, Long riderId) {
        validateOptionalServiceType(serviceType);
        QueryWrapper<FlashDeliveryOrder> query = new QueryWrapper<FlashDeliveryOrder>().orderByDesc("create_time");
        optionalEq(query, "status", status);
        optionalEq(query, "service_type", serviceType);
        if (hasText(orderNo)) query.like("order_no", orderNo.trim());
        if (userId != null) query.eq("user_id", userId);
        if (riderId != null) query.eq("rider_id", riderId);
        return orderMapper.selectPage(page(pageNum, pageSize), query);
    }

    public FlashDeliveryAdminOrderDetailView adminDetail(Long orderId) {
        FlashDeliveryOrder order = requireOrder(orderId);
        FlashDeliveryAdminOrderDetailView view = new FlashDeliveryAdminOrderDetailView();
        view.setOrder(order);
        view.setImages(imageMapper.selectList(new QueryWrapper<FlashDeliveryOrderImage>()
                .eq("order_id", orderId).orderByAsc("proof_type", "sort_order")));
        view.setLogs(logMapper.selectList(new QueryWrapper<FlashDeliveryOrderLog>()
                .eq("order_id", orderId).orderByAsc("create_time", "id")));
        return view;
    }

    @Transactional
    public void adminCancel(Long adminId, Long orderId, FlashDeliveryReasonRequest request) {
        FlashDeliveryOrder order = requireOrder(orderId);
        if (!FlashDeliveryStateMachine.canAdminCancel(order.getStatus())) throw fail("flash.delivery.cancel.not.allowed");
        String reason = requiredReason(request);
        Date now = new Date();
        if (orderMapper.cancel(orderId, order.getStatus(), "ADMIN", adminId, reason, now) != 1) {
            throw fail("flash.delivery.state.changed");
        }
        writeLog(orderId, order.getStatus(), CANCELLED, "ADMIN", adminId, reason, now);
    }

    @Transactional
    public boolean complete(Long operatorId, String operatorType, Long orderId) {
        Date now = new Date();
        if (orderMapper.transitionByStatus(orderId, DELIVERED, COMPLETED, "completed_at", now) != 1) return false;
        writeLog(orderId, DELIVERED, COMPLETED, operatorType, operatorId, null, now);
        return true;
    }

    public List<FlashDeliveryOrder> autoCompleteCandidates(Date deadline, int limit) {
        return orderMapper.selectAutoCompletable(deadline, Math.min(Math.max(limit, 1), 500));
    }

    private QuoteContext calculateQuote(FlashDeliveryQuoteRequest request) {
        if (request == null) throw fail("flash.delivery.request.required");
        validateServiceType(request.getServiceType());
        validateDeliveryType(request.getDeliveryType());
        if (!hasText(request.getPackageType()) || !PACKAGE_TYPES.contains(request.getPackageType())) {
            throw fail("flash.delivery.package.type.invalid");
        }
        validateItem(request);
        Date now = new Date();
        String deliveryMode = normalizeDeliveryMode(request.getDeliveryMode());
        validateSchedule(deliveryMode, request.getScheduledPickupStartAt(), request.getScheduledPickupEndAt(), now);
        validateAddress(request.getPickup());
        validateAddress(request.getDelivery());
        if (sameAddressText(request.getPickup(), request.getDelivery())
                || request.getPickup().getLatitude().compareTo(request.getDelivery().getLatitude()) == 0
                && request.getPickup().getLongitude().compareTo(request.getDelivery().getLongitude()) == 0) {
            throw fail("flash.delivery.address.same");
        }
        Date pricingDate = "SCHEDULED".equals(deliveryMode) ? request.getScheduledPickupStartAt() : now;
        FlashDeliveryPricing pricing = findPricingAt(pricingTargetTime(pricingDate));
        // 路线服务优先使用 Google Routes；不可用时在服务内部降级为球面直线距离。
        RouteDistance route = routeService.calculate(
                new GeoPoint(request.getPickup().getLatitude(), request.getPickup().getLongitude()),
                new GeoPoint(request.getDelivery().getLatitude(), request.getDelivery().getLongitude()));
        if (route.distanceMeters() > MAX_DISTANCE_METERS) throw fail("flash.delivery.distance.too.far");
        FlashDeliveryPriceBreakdown breakdown = pricingCalculator.calculateBreakdown(pricing,
                route.distanceMeters(), request.getDeliveryType(), request.getTipAmount());
        return new QuoteContext(pricing, route, breakdown);
    }

    private FlashDeliveryPricing findPricingAt(String targetTime) {
        FlashDeliveryPricing pricing = findPricingAtOrNull(targetTime);
        if (pricing == null) throw fail("flash.delivery.pricing.not.available");
        return pricing;
    }

    private FlashDeliveryPricing findPricingAtOrNull(String targetTime) {
        List<FlashDeliveryPricing> pricing = pricingMapper.selectAtTime(targetTime);
        if (pricing == null || pricing.isEmpty()) return null;
        if (pricing.size() > 1) throw fail("flash.delivery.pricing.overlap");
        return pricing.get(0);
    }

    private void validatePricingRequest(FlashDeliveryPricingRequest request, boolean requireVersion) {
        if (request == null) throw fail("flash.delivery.pricing.invalid");
        BigDecimal startingDistance = normalizeDistance(request.getStartingDistance());
        BigDecimal distance = normalizeDistance(request.getDistance());
        BigDecimal urgentRate = normalizeRate(request.getUrgentRate());
        if (!validStartTime(request.getStartTime()) || !validEndTime(request.getEndTime())
                || toMinute(request.getStartTime()) >= toMinute(request.getEndTime())
                || startingDistance == null || startingDistance.signum() <= 0
                || startingDistance.compareTo(MAX_PRICING_DISTANCE) > 0
                || request.getStartingFare() == null || request.getStartingFare() <= 0
                || distance == null || distance.signum() <= 0
                || distance.compareTo(MAX_PRICING_DISTANCE) > 0
                || request.getFreight() == null || request.getFreight() <= 0
                || urgentRate == null || urgentRate.signum() < 0
                || urgentRate.compareTo(MAX_PRICING_DISTANCE) > 0
                || request.getMinimumUrgentFee() == null || request.getMinimumUrgentFee() < 0
                || requireVersion && (request.getConfigVersion() == null || request.getConfigVersion() <= 0)) {
            throw fail("flash.delivery.pricing.invalid");
        }
    }

    private void ensureNoPricingOverlap(FlashDeliveryPricingRequest request, Long excludedId) {
        if (pricingMapper.countOverlapping(request.getStartTime(), request.getEndTime(), excludedId) > 0) {
            throw fail("flash.delivery.pricing.overlap");
        }
    }

    private void lockPricing() {
        if (!Objects.equals("GLOBAL", pricingMapper.lockPricing())) {
            throw fail("flash.delivery.pricing.invalid");
        }
    }

    private FlashDeliveryPricing pricingFrom(FlashDeliveryPricingRequest request) {
        FlashDeliveryPricing pricing = new FlashDeliveryPricing();
        pricing.setStartTime(request.getStartTime());
        pricing.setEndTime(request.getEndTime());
        pricing.setStartingDistance(scaleDistance(request.getStartingDistance()));
        pricing.setStartingFare(request.getStartingFare());
        pricing.setDistance(scaleDistance(request.getDistance()));
        pricing.setFreight(request.getFreight());
        pricing.setUrgentRate(normalizeRate(request.getUrgentRate()));
        pricing.setMinimumUrgentFee(request.getMinimumUrgentFee());
        return pricing;
    }

    private boolean validStartTime(String value) {
        return value != null && value.matches("(?:[01]\\d|2[0-3]):[0-5]\\d");
    }

    private boolean validEndTime(String value) {
        return "24:00".equals(value) || validStartTime(value);
    }

    private int toMinute(String value) {
        if ("24:00".equals(value)) return 24 * 60;
        return Integer.parseInt(value.substring(0, 2)) * 60 + Integer.parseInt(value.substring(3));
    }

    private BigDecimal scaleDistance(BigDecimal value) {
        return normalizeDistance(value);
    }

    private BigDecimal normalizeDistance(BigDecimal value) {
        return value == null ? null : value.setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal normalizeRate(BigDecimal value) {
        return value == null ? null : value.setScale(2, RoundingMode.HALF_UP);
    }

    private void transitionWithProof(Long riderId, Long orderId, FlashDeliveryProofRequest request,
                                     String expected, String next, String proofType, String timeColumn) {
        List<String> urls = validateProofUrls(request == null ? null : request.getImageUrls(), true);
        requireRider(riderId);
        Date now = new Date();
        if (orderMapper.transitionByRider(orderId, riderId, expected, next, timeColumn, now) != 1) {
            throw fail("flash.delivery.transition.not.allowed");
        }
        saveImages(orderId, proofType, "RIDER", riderId, urls, now);
        writeLog(orderId, expected, next, "RIDER", riderId, null, now);
    }

    /** 状态、图片和日志处于同一事务；任一图片写入失败会回滚本次业务操作。 */
    private void saveImages(Long orderId, String proofType, String operatorType,
                            Long operatorId, List<String> urls, Date now) {
        for (int index = 0; index < urls.size(); index++) {
            FlashDeliveryOrderImage image = new FlashDeliveryOrderImage();
            image.setOrderId(orderId);
            image.setProofType(proofType);
            image.setImageUrl(urls.get(index));
            image.setSortOrder(index);
            image.setOperatorType(operatorType);
            image.setOperatorId(operatorId);
            image.setCreateTime(now);
            imageMapper.insert(image);
        }
    }

    private List<String> validateProofUrls(List<String> values, boolean required) {
        if (values == null || values.isEmpty()) {
            if (!required) return List.of();
            throw fail("flash.delivery.proof.required");
        }
        if (values.size() > 9) throw fail("flash.delivery.proof.too.many");
        List<String> urls = new ArrayList<>();
        for (String value : values) {
            try {
                String url = value == null ? null : value.trim();
                URI uri = url == null ? null : URI.create(url);
                if (uri == null || url.length() > 1000 || uri.getHost() == null
                        || !("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme()))) {
                    throw new IllegalArgumentException();
                }
                urls.add(url);
            } catch (IllegalArgumentException exception) {
                throw fail("flash.delivery.proof.url.invalid");
            }
        }
        return urls;
    }

    private FlashDeliveryOrderDetailView participantDetail(FlashDeliveryOrder order,
                                                           boolean userView,
                                                           boolean hideSensitiveFields) {
        FlashDeliveryOrderDetailView view = detailOrderView(order, !hideSensitiveFields);
        if (userView) {
            view.setDeliveryPinCode(Boolean.TRUE.equals(order.getPinRequired()) ? order.getDeliveryPinCode() : null);
            view.setRider(riderSummary(order));
        }
        if (!hideSensitiveFields && order.getId() != null) {
            groupImageUrls(view, imageMapper.selectList(new QueryWrapper<FlashDeliveryOrderImage>()
                    .eq("order_id", order.getId()).orderByAsc("proof_type", "sort_order")));
        }
        return view;
    }

    private FlashDeliveryUserOrderListView userOrderListView(FlashDeliveryOrder order) {
        FlashDeliveryUserOrderListView view = new FlashDeliveryUserOrderListView();
        view.setId(order.getId());
        view.setOrderNo(order.getOrderNo());
        view.setServiceType(order.getServiceType());
        view.setDeliveryType(order.getDeliveryType());
        view.setStatus(order.getStatus());
        view.setPackageType(order.getPackageType());
        view.setQuantity(order.getQuantity());
        view.setTotalWeightKg(order.getTotalWeightKg());
        view.setDeliveryMode(order.getDeliveryMode());
        view.setScheduledPickupStartAt(order.getScheduledPickupStartAt());
        view.setScheduledPickupEndAt(order.getScheduledPickupEndAt());
        view.setPickupAddress(order.getPickupAddress());
        view.setPickupDetailAddress(order.getPickupAddressDetail());
        view.setDeliveryAddress(order.getDeliveryAddress());
        view.setDeliveryDetailAddress(order.getDeliveryAddressDetail());
        view.setEstimatedDurationSeconds(order.getEstimatedDurationSeconds());
        view.setBaseDeliveryFee(order.getBaseDeliveryFee());
        view.setUrgentFee(order.getUrgentFee());
        view.setTipAmount(order.getTipAmount());
        view.setAmount(order.getAmount());
        view.setCurrency(order.getCurrency());
        view.setDeliveredAt(order.getDeliveredAt());
        view.setCreateTime(order.getCreateTime());
        return view;
    }

    private FlashDeliveryRiderOrderListView riderOrderListView(FlashDeliveryOrder order,
                                                                 BigDecimal longitude, BigDecimal latitude) {
        FlashDeliveryRiderOrderListView view = new FlashDeliveryRiderOrderListView();
        view.setId(order.getId());
        view.setOrderNo(order.getOrderNo());
        view.setServiceType(order.getServiceType());
        view.setDeliveryType(order.getDeliveryType());
        view.setStatus(order.getStatus());
        view.setPackageType(order.getPackageType());
        view.setQuantity(order.getQuantity());
        view.setTotalWeightKg(order.getTotalWeightKg());
        view.setSpecification(order.getSpecification());
        view.setPackageSize(order.getPackageSize());
        view.setDeliveryMode(order.getDeliveryMode());
        view.setScheduledPickupStartAt(order.getScheduledPickupStartAt());
        view.setScheduledPickupEndAt(order.getScheduledPickupEndAt());
        view.setPinRequired(order.getPinRequired());
        view.setPickupAddress(order.getPickupAddress());
        view.setPickupDetailAddress(order.getPickupAddressDetail());
        view.setDeliveryAddress(order.getDeliveryAddress());
        view.setDeliveryDetailAddress(order.getDeliveryAddressDetail());
        view.setPickupDistanceMeters(distanceMeters(longitude, latitude,
                order.getPickupLongitude(), order.getPickupLatitude()));
        view.setDistanceMeters(order.getDistanceMeters());
        view.setEstimatedDurationSeconds(order.getEstimatedDurationSeconds());
        view.setBaseDeliveryFee(order.getBaseDeliveryFee());
        view.setDistanceFee(order.getDistanceFee());
        view.setUrgentFee(order.getUrgentFee());
        view.setTipAmount(order.getTipAmount());
        view.setAmount(order.getAmount());
        view.setCurrency(order.getCurrency());
        view.setCreateTime(order.getCreateTime());
        return view;
    }

    private FlashDeliveryOrderDetailView detailOrderView(FlashDeliveryOrder order, boolean includeSensitiveFields) {
        FlashDeliveryOrderDetailView view = new FlashDeliveryOrderDetailView();
        populateOrderView(view, order, includeSensitiveFields);
        return view;
    }

    private void groupImageUrls(FlashDeliveryOrderDetailView view, List<FlashDeliveryOrderImage> images) {
        List<String> sender = new ArrayList<>();
        List<String> pickup = new ArrayList<>();
        List<String> delivery = new ArrayList<>();
        for (FlashDeliveryOrderImage image : images) {
            if ("SENDER".equals(image.getProofType())) sender.add(image.getImageUrl());
            else if ("PICKUP".equals(image.getProofType())) pickup.add(image.getImageUrl());
            else if ("DELIVERY".equals(image.getProofType())) delivery.add(image.getImageUrl());
        }
        view.setSenderImageUrls(sender);
        view.setPickupImageUrls(pickup);
        view.setDeliveryImageUrls(delivery);
    }

    private FlashDeliveryServiceView serviceView(FlashDeliveryPricing pricing, String serviceType) {
        FlashDeliveryServiceView view = new FlashDeliveryServiceView();
        view.setServiceType(serviceType);
        view.setPricingId(pricing.getId());
        view.setStartTime(pricing.getStartTime());
        view.setEndTime(pricing.getEndTime());
        view.setStartingDistance(pricing.getStartingDistance());
        view.setStartingFare(pricing.getStartingFare());
        view.setDistance(pricing.getDistance());
        view.setFreight(pricing.getFreight());
        view.setUrgentRate(pricing.getUrgentRate());
        view.setMinimumUrgentFee(pricing.getMinimumUrgentFee());
        view.setPricingVersion(pricing.getConfigVersion());
        view.setCurrency("TWD");
        return view;
    }

    private void populateOrderView(FlashDeliveryOrderView view, FlashDeliveryOrder order,
                                   boolean includeSensitiveFields) {
        view.setId(order.getId());
        view.setOrderNo(order.getOrderNo());
        view.setServiceType(order.getServiceType());
        view.setDeliveryType(order.getDeliveryType());
        view.setStatus(order.getStatus());
        view.setPackageType(order.getPackageType());
        view.setQuantity(order.getQuantity());
        view.setTotalWeightKg(order.getTotalWeightKg());
        view.setSpecification(order.getSpecification());
        view.setPackageSize(order.getPackageSize());
        view.setDeliveryMode(order.getDeliveryMode());
        view.setScheduledPickupStartAt(order.getScheduledPickupStartAt());
        view.setScheduledPickupEndAt(order.getScheduledPickupEndAt());
        view.setPinRequired(order.getPinRequired());
        view.setPickup(addressView(order, true, includeSensitiveFields));
        view.setDelivery(addressView(order, false, includeSensitiveFields));
        view.setDistanceMeters(order.getDistanceMeters());
        view.setDistanceSource(order.getDistanceSource());
        view.setEstimatedDurationSeconds(order.getEstimatedDurationSeconds());
        view.setBaseDeliveryFee(order.getBaseDeliveryFee());
        view.setDistanceFee(order.getDistanceFee());
        view.setUrgentFee(order.getUrgentFee());
        view.setTipAmount(order.getTipAmount());
        view.setAmount(order.getAmount());
        view.setCurrency(order.getCurrency());
        view.setUserNote(includeSensitiveFields ? order.getUserNote() : null);
        view.setAcceptedAt(order.getAcceptedAt());
        view.setPickedUpAt(order.getPickedUpAt());
        view.setDeliveredAt(order.getDeliveredAt());
        view.setCompletedAt(order.getCompletedAt());
        view.setCancelledAt(order.getCancelledAt());
        view.setCancelReason(order.getCancelReason());
        view.setCreateTime(order.getCreateTime());
        view.setUpdateTime(order.getUpdateTime());
    }

    private FlashDeliveryAddressView addressView(FlashDeliveryOrder order, boolean pickup,
                                                 boolean includeSensitiveFields) {
        FlashDeliveryAddressView view = new FlashDeliveryAddressView();
        if (pickup) {
            view.setName(includeSensitiveFields ? order.getPickupName() : null);
            view.setPhone(includeSensitiveFields ? order.getPickupPhone() : null);
            view.setAddress(order.getPickupAddress()); view.setAddressDetail(order.getPickupAddressDetail());
            view.setCity(order.getPickupCity()); view.setArea(order.getPickupArea());
            view.setHandoffMethod(order.getPickupHandoffMethod());
            view.setLongitude(includeSensitiveFields ? order.getPickupLongitude() : null);
            view.setLatitude(includeSensitiveFields ? order.getPickupLatitude() : null);
        } else {
            view.setName(includeSensitiveFields ? order.getDeliveryName() : null);
            view.setPhone(includeSensitiveFields ? order.getDeliveryPhone() : null);
            view.setAddress(order.getDeliveryAddress()); view.setAddressDetail(order.getDeliveryAddressDetail());
            view.setCity(order.getDeliveryCity()); view.setArea(order.getDeliveryArea());
            view.setHandoffMethod(order.getDeliveryHandoffMethod());
            view.setLongitude(includeSensitiveFields ? order.getDeliveryLongitude() : null);
            view.setLatitude(includeSensitiveFields ? order.getDeliveryLatitude() : null);
        }
        return view;
    }

    private FlashDeliveryRiderSummaryView riderSummary(FlashDeliveryOrder order) {
        if (order.getRiderId() == null) return null;
        InfoUser rider = userMapper.selectInfoUserByUserId(order.getRiderId());
        if (rider == null) return null;
        FlashDeliveryRiderSummaryView view = new FlashDeliveryRiderSummaryView();
        view.setName(hasText(rider.getNickName()) ? rider.getNickName() : rider.getUserName());
        view.setAvatar(rider.getAvatar());
        view.setRating(rider.getStar());
        view.setImUserId(rider.getImUserId() == null ? null : String.valueOf(rider.getImUserId()));
        if (ACCEPTED.equals(order.getStatus()) || PICKED_UP.equals(order.getStatus())) {
            view.setLongitude(rider.getLongitude());
            view.setLatitude(rider.getLatitude());
        }
        return view;
    }

    private void applyBreakdown(FlashDeliveryQuoteView view, FlashDeliveryPriceBreakdown breakdown) {
        view.setBillableDistance(breakdown.getBillableDistance());
        view.setDistanceFee(breakdown.getDistanceFee());
        view.setBaseDeliveryFee(breakdown.getBaseDeliveryFee());
        view.setUrgentFee(breakdown.getUrgentFee());
        view.setTipAmount(breakdown.getTipAmount());
        view.setAmount(breakdown.getAmount());
    }

    private void copyAddress(FlashDeliveryAddressRequest source, FlashDeliveryOrder target, boolean pickup) {
        if (pickup) {
            target.setPickupName(source.getName().trim()); target.setPickupPhone(source.getPhone().trim());
            target.setPickupAddress(source.getAddress().trim()); target.setPickupAddressDetail(trimToNull(source.getAddressDetail()));
            target.setPickupCity(trimToNull(source.getCity())); target.setPickupArea(trimToNull(source.getArea()));
            target.setPickupHandoffMethod(trimToNull(source.getHandoffMethod()));
            target.setPickupLongitude(source.getLongitude()); target.setPickupLatitude(source.getLatitude());
        } else {
            target.setDeliveryName(source.getName().trim()); target.setDeliveryPhone(source.getPhone().trim());
            target.setDeliveryAddress(source.getAddress().trim()); target.setDeliveryAddressDetail(trimToNull(source.getAddressDetail()));
            target.setDeliveryCity(trimToNull(source.getCity())); target.setDeliveryArea(trimToNull(source.getArea()));
            target.setDeliveryHandoffMethod(trimToNull(source.getHandoffMethod()));
            target.setDeliveryLongitude(source.getLongitude()); target.setDeliveryLatitude(source.getLatitude());
        }
    }

    private void validateAddress(FlashDeliveryAddressRequest address) {
        if (address == null || !hasText(address.getName()) || !hasText(address.getPhone())
                || !hasText(address.getAddress()) || address.getLatitude() == null || address.getLongitude() == null
                || address.getName().trim().length() > 64 || address.getPhone().trim().length() > 32
                || address.getAddress().trim().length() > 255
                || (address.getAddressDetail() != null && address.getAddressDetail().trim().length() > 255)
                || tooLong(address.getCity(), 64) || tooLong(address.getArea(), 64)
                || tooLong(address.getHandoffMethod(), 40)
                || address.getLatitude().compareTo(BigDecimal.valueOf(-90)) < 0
                || address.getLatitude().compareTo(BigDecimal.valueOf(90)) > 0
                || address.getLongitude().compareTo(BigDecimal.valueOf(-180)) < 0
                || address.getLongitude().compareTo(BigDecimal.valueOf(180)) > 0) {
            throw fail("flash.delivery.address.invalid");
        }
    }

    private Long resolveReceiverUserId(String phone) {
        if (!hasText(phone)) return null;
        StringBuilder normalized = new StringBuilder(phone.length());
        for (int index = 0; index < phone.length(); index++) {
            char value = phone.charAt(index);
            if (Character.isWhitespace(value) || value == '+' || value == '-'
                    || value == '(' || value == ')') {
                continue;
            }
            if (value < '0' || value > '9') return null;
            normalized.append(value);
        }
        if (normalized.isEmpty()) return null;
        List<Long> matches = userMapper.selectOrdinaryUserIdsByNormalizedPhone(normalized.toString());
        return matches != null && matches.size() == 1 ? matches.get(0) : null;
    }

    private boolean isParticipant(Long userId, FlashDeliveryOrder order) {
        return Objects.equals(userId, order.getUserId())
                || Objects.equals(userId, order.getReceiverUserId());
    }

    private InfoUser requireRider(Long riderId) {
        InfoUser rider = riderId == null ? null : userMapper.selectInfoUserByUserId(riderId);
        if (rider == null || !"2".equals(rider.getUserType())) throw fail("flash.delivery.rider.required");
        return rider;
    }

    private FlashDeliveryOrder requireOrder(Long orderId) {
        FlashDeliveryOrder order = orderId == null ? null : orderMapper.selectById(orderId);
        if (order == null) throw fail("flash.delivery.order.not.found");
        return order;
    }

    private void writeLog(Long orderId, String from, String to, String operatorType,
                          Long operatorId, String reason, Date now) {
        FlashDeliveryOrderLog log = new FlashDeliveryOrderLog();
        log.setOrderId(orderId); log.setFromStatus(from); log.setToStatus(to);
        log.setOperatorType(operatorType); log.setOperatorId(operatorId); log.setReason(reason); log.setCreateTime(now);
        logMapper.insert(log);
    }

    private String requiredReason(FlashDeliveryReasonRequest request) {
        if (request == null || !hasText(request.getReason()) || request.getReason().trim().length() > 500) {
            throw fail("flash.delivery.cancel.reason.required");
        }
        return request.getReason().trim();
    }

    private void validateServiceType(String value) {
        if (!hasText(value) || !SERVICE_TYPES.contains(value)) throw fail("flash.delivery.service.type.invalid");
    }

    private void validateDeliveryType(String value) {
        if (!hasText(value) || !DELIVERY_TYPES.contains(value)) {
            throw fail("flash.delivery.delivery.type.invalid");
        }
    }

    private void validateItem(FlashDeliveryQuoteRequest request) {
        BigDecimal weight = request.getTotalWeightKg();
        if (request.getQuantity() == null || request.getQuantity() <= 0
                || weight == null || weight.signum() <= 0
                || weight.scale() > 2 || weight.compareTo(MAX_TOTAL_WEIGHT_KG) > 0
                || tooLong(request.getSpecification(), 255)) {
            throw fail("flash.delivery.item.invalid");
        }
        if (request.getTipAmount() == null || request.getTipAmount() < 0) {
            throw fail("flash.delivery.tip.invalid");
        }
    }

    private String packageSize(BigDecimal weight) {
        if (weight.compareTo(new BigDecimal("5.00")) <= 0) return "SMALL";
        if (weight.compareTo(new BigDecimal("12.00")) <= 0) return "MEDIUM";
        return "LARGE";
    }

    private BigDecimal scaleWeight(BigDecimal weight) {
        return weight.setScale(2, RoundingMode.UNNECESSARY);
    }

    private String pricingTargetTime(Date date) {
        return PRICING_TIME_FORMAT.format(Instant.ofEpochMilli(date.getTime())
                .atZone(TAIPEI).toLocalTime());
    }

    private boolean matchesQuotedPrice(FlashDeliveryCreateRequest request, FlashDeliveryQuoteView latest) {
        return Objects.equals(request.getPricingId(), latest.getPricingId())
                && Objects.equals(request.getPricingVersion(), latest.getPricingVersion())
                && Objects.equals(request.getQuotedBaseDeliveryFee(), latest.getBaseDeliveryFee())
                && Objects.equals(request.getQuotedDistanceFee(), latest.getDistanceFee())
                && Objects.equals(request.getQuotedUrgentFee(), latest.getUrgentFee())
                && Objects.equals(request.getQuotedAmount(), latest.getAmount());
    }

    private void validateOptionalServiceType(String value) {
        if (hasText(value)) validateServiceType(value);
    }

    private void requireUserId(Long userId) {
        if (userId == null || userId <= 0) throw fail("flash.delivery.auth.required");
    }

    private Page<FlashDeliveryOrder> page(int pageNum, int pageSize) {
        return new Page<>(Math.max(1, pageNum), Math.min(Math.max(1, pageSize), 100));
    }

    private <T> IPage<T> mapPage(IPage<FlashDeliveryOrder> source,
                                 Function<FlashDeliveryOrder, T> mapper) {
        Page<T> result = new Page<>(source.getCurrent(), source.getSize(), source.getTotal());
        result.setRecords(source.getRecords().stream().map(mapper).toList());
        return result;
    }

    private QueryWrapper<FlashDeliveryOrder> riderOrdersQuery(Long riderId, String tab,
                                                                BigDecimal longitude, BigDecimal latitude,
                                                                boolean orderResults) {
        QueryWrapper<FlashDeliveryOrder> query = new QueryWrapper<>();
        Date now = new Date();
        switch (tab) {
            case "newTask" -> {
                query.eq("status", WAITING_ACCEPTANCE).isNull("rider_id")
                        .and(item -> item.eq("delivery_mode", "NOW")
                                .or().le("scheduled_pickup_start_at", now));
                if (longitude != null) {
                    Integer limitKm = newTaskDistanceLimit();
                    if (limitKm != null) {
                        query.apply("ST_Distance_Sphere(point(pickup_longitude, pickup_latitude), point({0}, {1})) <= {2}",
                                longitude, latitude, limitKm.longValue() * 1000L);
                    }
                    if (orderResults) {
                        query.orderByAsc("ST_Distance_Sphere(point(pickup_longitude, pickup_latitude), point(" + longitude
                                + ", " + latitude + "))");
                    }
                } else if (orderResults) {
                    query.orderByDesc("create_time");
                }
            }
            case "toPickup" -> {
                query.eq("rider_id", riderId).eq("status", ACCEPTED);
                if (orderResults) query.orderByAsc("create_time");
            }
            case "delivering" -> {
                query.eq("rider_id", riderId).eq("status", PICKED_UP);
                if (orderResults) query.orderByAsc("create_time");
            }
            case "completed" -> {
                query.eq("rider_id", riderId).in("status", DELIVERED, COMPLETED);
                if (orderResults) query.orderByDesc("create_time");
            }
            case "cancelled" -> {
                query.eq("rider_id", riderId).eq("status", CANCELLED);
                if (orderResults) query.orderByDesc("create_time");
            }
            default -> throw fail("flash.delivery.tab.invalid");
        }
        return query;
    }

    private void validateRiderCoordinates(BigDecimal longitude, BigDecimal latitude) {
        if ((longitude == null) != (latitude == null)
                || longitude != null && (longitude.compareTo(BigDecimal.valueOf(-180)) < 0
                || longitude.compareTo(BigDecimal.valueOf(180)) > 0
                || latitude.compareTo(BigDecimal.valueOf(-90)) < 0
                || latitude.compareTo(BigDecimal.valueOf(90)) > 0)) {
            throw fail("flash.delivery.coordinates.invalid");
        }
    }

    private Integer newTaskDistanceLimit() {
        try {
            List<SysDictData> values = DictUtils.getDictCache("sys_qs_newtask_distance");
            if (values != null && !values.isEmpty()) {
                int limit = Integer.parseInt(values.get(0).getDictValue());
                return limit > 0 ? limit : null;
            }
        } catch (Exception ignored) {
            // Keep the established rider-list behaviour: absent or malformed configuration disables distance filtering.
        }
        return null;
    }

    private Integer distanceMeters(BigDecimal longitude, BigDecimal latitude,
                                   BigDecimal targetLongitude, BigDecimal targetLatitude) {
        if (longitude == null || targetLongitude == null || targetLatitude == null) return null;
        double latitudeRadians = Math.toRadians(latitude.doubleValue());
        double targetLatitudeRadians = Math.toRadians(targetLatitude.doubleValue());
        double latitudeDelta = targetLatitudeRadians - latitudeRadians;
        double longitudeDelta = Math.toRadians(targetLongitude.doubleValue() - longitude.doubleValue());
        double value = Math.sin(latitudeDelta / 2) * Math.sin(latitudeDelta / 2)
                + Math.cos(latitudeRadians) * Math.cos(targetLatitudeRadians)
                * Math.sin(longitudeDelta / 2) * Math.sin(longitudeDelta / 2);
        return (int) Math.round(6_371_000D * 2D * Math.atan2(Math.sqrt(value), Math.sqrt(1D - value)));
    }

    private Long firstLong(List<Object> values) {
        if (values == null || values.isEmpty() || values.get(0) == null) return null;
        Object value = values.get(0);
        if (value instanceof Number number) return number.longValue();
        return Long.valueOf(String.valueOf(value));
    }

    private String normalizeDeliveryMode(String value) {
        String mode = hasText(value) ? value.trim() : "NOW";
        if (!DELIVERY_MODES.contains(mode)) throw fail("flash.delivery.mode.invalid");
        return mode;
    }

    private void validateSchedule(String mode, Date start, Date end, Date now) {
        if ("NOW".equals(mode)) {
            if (start != null || end != null) throw fail("flash.delivery.schedule.invalid");
            return;
        }
        if (start == null || end == null || start.before(now)
                || end.getTime() - start.getTime() != SCHEDULE_SLOT_MILLIS
                || start.getTime() - now.getTime() > MAX_SCHEDULE_DELAY_MILLIS) {
            throw fail("flash.delivery.schedule.invalid");
        }
    }

    private boolean isAvailableAt(FlashDeliveryOrder order, Date now) {
        return "NOW".equals(order.getDeliveryMode())
                || "SCHEDULED".equals(order.getDeliveryMode())
                && order.getScheduledPickupStartAt() != null
                && !order.getScheduledPickupStartAt().after(now);
    }

    private void optionalEq(QueryWrapper<FlashDeliveryOrder> query, String column, String value) {
        if (hasText(value)) query.eq(column, value.trim());
    }

    private boolean hasText(String value) { return StringUtils.isNotEmpty(value) && !value.trim().isEmpty(); }
    private String trimToNull(String value) { return hasText(value) ? value.trim() : null; }
    private boolean tooLong(String value, int max) { return value != null && value.trim().length() > max; }
    private boolean sameAddressText(FlashDeliveryAddressRequest left, FlashDeliveryAddressRequest right) {
        return normalized(left.getAddress()).equals(normalized(right.getAddress()))
                && normalized(left.getAddressDetail()).equals(normalized(right.getAddressDetail()));
    }
    private String normalized(String value) { return value == null ? "" : value.trim().toLowerCase(Locale.ROOT); }
    private ServiceException fail(String key) { return new ServiceException(MessageUtils.message(key)); }

    private record QuoteContext(FlashDeliveryPricing pricing, RouteDistance route,
                                FlashDeliveryPriceBreakdown breakdown) { }
}
