package com.ruoyi.app.flashdelivery.service;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryAddressRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryCreateRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryProofRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryPricingRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryQuoteRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryDeliverRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryRiderOrderPageView;
import com.ruoyi.app.flashdelivery.exception.FlashDeliveryQuoteChangedException;
import com.ruoyi.app.flashdelivery.route.FlashDeliveryRouteService;
import com.ruoyi.app.flashdelivery.route.RouteDistance;
import com.ruoyi.app.order.RiderDeliveryExclusivityService;
import com.ruoyi.app.order.RiderDeliveryLockService;
import com.ruoyi.system.domain.InfoUser;
import com.ruoyi.system.domain.flash.FlashDeliveryOrder;
import com.ruoyi.system.domain.flash.FlashDeliveryOrderImage;
import com.ruoyi.system.domain.flash.FlashDeliveryPricing;
import com.ruoyi.system.mapper.InfoUserMapper;
import com.ruoyi.system.mapper.flash.FlashDeliveryOrderImageMapper;
import com.ruoyi.system.mapper.flash.FlashDeliveryOrderLogMapper;
import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
import com.ruoyi.system.mapper.flash.FlashDeliveryPricingMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.MockedStatic;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.StaticMessageSource;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.springframework.test.util.ReflectionTestUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.Date;
import java.util.Calendar;

import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.ACCEPTED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.ruoyi.common.exception.ServiceException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.doThrow;

class FlashDeliveryApplicationServiceTest {
    private static ConfigurableListableBeanFactory originalBeanFactory;

    @BeforeAll
    static void initializeMessages() {
        originalBeanFactory = (ConfigurableListableBeanFactory)
                ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        StaticMessageSource messageSource = new StaticMessageSource();
        messageSource.setUseCodeAsDefaultMessage(true);
        beanFactory.registerSingleton("messageSource", messageSource);
        new SpringUtils().postProcessBeanFactory(beanFactory);
    }

    @AfterAll
    static void restoreMessages() {
        new SpringUtils().postProcessBeanFactory(originalBeanFactory);
    }

    @Test
    void quoteUsesServerRouteAndCurrentTimePricing() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3001, 600, "ROUTE"));

        var quote = fixture.service.quote(request());

        assertEquals(3001, quote.getDistanceMeters());
        assertEquals(90L, quote.getAmount());
        assertEquals(1L, quote.getPricingId());
        assertEquals("00:00", quote.getStartTime());
        assertEquals("24:00", quote.getEndTime());
        assertEquals("ROUTE", quote.getDistanceSource());
    }

    @Test
    void quoteRejectsRoutesLongerThanFortyKilometres() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(40001, 1800, "ROUTE"));

        assertThrows(ServiceException.class, () -> fixture.service.quote(request()));
    }

    @Test
    void wrongDeliveryPinCannotPersistProofOrChangeState() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
        FlashDeliveryOrder order = new FlashDeliveryOrder();
        order.setId(10L);
        order.setRiderId(8L);
        order.setStatus("PICKED_UP");
        order.setPinRequired(true);
        order.setDeliveryPinCode("4821");
        when(fixture.orderMapper.selectById(10L)).thenReturn(order);
        FlashDeliveryDeliverRequest request = new FlashDeliveryDeliverRequest();
        request.setPinCode("1234");
        request.setImageUrls(List.of("https://example.com/delivery.jpg"));

        assertThrows(ServiceException.class, () -> fixture.service.deliver(8L, 10L, request));
        verify(fixture.imageMapper, never()).insert(any(com.ruoyi.system.domain.flash.FlashDeliveryOrderImage.class));
        verify(fixture.orderMapper, never()).transitionByRider(any(), any(), any(), any(), any(), any());
    }

    @Test
    void correctDeliveryPinPersistsDeliveryProofAndChangesState() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
        FlashDeliveryOrder order = new FlashDeliveryOrder();
        order.setId(10L);
        order.setRiderId(8L);
        order.setStatus("PICKED_UP");
        order.setPinRequired(true);
        order.setDeliveryPinCode("4821");
        when(fixture.orderMapper.selectById(10L)).thenReturn(order);
        when(fixture.orderMapper.transitionByRider(eq(10L), eq(8L), eq("PICKED_UP"),
                eq("DELIVERED"), eq("delivered_at"), any())).thenReturn(1);
        FlashDeliveryDeliverRequest request = new FlashDeliveryDeliverRequest();
        request.setPinCode("4821");
        request.setImageUrls(List.of("https://example.com/delivery.jpg"));

        fixture.service.deliver(8L, 10L, request);

        ArgumentCaptor<com.ruoyi.system.domain.flash.FlashDeliveryOrderImage> image =
                ArgumentCaptor.forClass(com.ruoyi.system.domain.flash.FlashDeliveryOrderImage.class);
        verify(fixture.imageMapper).insert(image.capture());
        assertEquals("DELIVERY", image.getValue().getProofType());
        assertEquals("RIDER", image.getValue().getOperatorType());
    }

    @Test
    void scheduledCreateGeneratesUserOnlyPinAndStoresSenderProof() {
        Fixture fixture = new Fixture();
        when(fixture.userMapper.selectOrdinaryUserIdsByNormalizedPhone("0922222222"))
                .thenReturn(List.of(88L));
        when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
        doAnswer(invocation -> {
            FlashDeliveryOrder order = invocation.getArgument(0);
            order.setId(99L);
            return 1;
        }).when(fixture.orderMapper).insert(any(FlashDeliveryOrder.class));
        when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
        when(fixture.logMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
        FlashDeliveryCreateRequest request = createRequest();

        var result = fixture.service.create(7L, request);

        assertEquals(4, result.getDeliveryPinCode().length());
        ArgumentCaptor<com.ruoyi.system.domain.flash.FlashDeliveryOrderImage> image =
                ArgumentCaptor.forClass(com.ruoyi.system.domain.flash.FlashDeliveryOrderImage.class);
        verify(fixture.imageMapper).insert(image.capture());
        assertEquals("SENDER", image.getValue().getProofType());
        assertEquals("USER", image.getValue().getOperatorType());
        ArgumentCaptor<FlashDeliveryOrder> saved = ArgumentCaptor.forClass(FlashDeliveryOrder.class);
        verify(fixture.orderMapper).insert(saved.capture());
        assertEquals("SCHEDULED", saved.getValue().getDeliveryMode());
        assertEquals("NORMAL", saved.getValue().getDeliveryType());
        assertEquals("DOCUMENT", saved.getValue().getPackageType());
        assertEquals(1, saved.getValue().getQuantity());
        assertEquals(new BigDecimal("3.00"), saved.getValue().getTotalWeightKg());
        assertEquals("30 x 20 x 10 cm", saved.getValue().getSpecification());
        assertEquals("SMALL", saved.getValue().getPackageSize());
        assertEquals(88L, saved.getValue().getReceiverUserId());
        assertEquals(1L, saved.getValue().getPricingId());
        assertEquals("00:00", saved.getValue().getPricingStartTime());
        assertEquals("24:00", saved.getValue().getPricingEndTime());
        assertEquals(new BigDecimal("3.00"), saved.getValue().getStartingDistance());
        assertEquals(90L, saved.getValue().getStartingFare());
        assertEquals(new BigDecimal("1.00"), saved.getValue().getDistance());
        assertEquals(15L, saved.getValue().getFreight());
        assertEquals(0L, saved.getValue().getDistanceFee());
        assertEquals(90L, saved.getValue().getBaseDeliveryFee());
        assertEquals(new BigDecimal("20.00"), saved.getValue().getUrgentRate());
        assertEquals(10L, saved.getValue().getMinimumUrgentFee());
        assertEquals(0L, saved.getValue().getUrgentFee());
        assertEquals(0L, saved.getValue().getTipAmount());
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void userListOnlyFiltersByOwnerAndReturnsCardFields() {
        Fixture fixture = new Fixture();
        FlashDeliveryOrder order = order(10L, "COMPLETED");
        Page<FlashDeliveryOrder> source = new Page<>(1, 10, 1);
        source.setRecords(List.of(order));
        when(fixture.orderMapper.selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class))).thenReturn(source);

        var result = fixture.service.userOrders(7L, 1, 10, null);

        ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.query.QueryWrapper> query =
                ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper.class);
        verify(fixture.orderMapper).selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class), query.capture());
        String sql = query.getValue().getSqlSegment().toLowerCase();
        var parameters = query.getValue().getParamNameValuePairs().values();
        assertTrue(sql.contains("user_id"));
        assertFalse(sql.contains("status"));
        assertFalse(sql.contains("service_type"));
        assertTrue(parameters.contains(7L));
        assertEquals("pickup road", result.getRecords().get(0).getPickupAddress());
        assertEquals(112L, result.getRecords().get(0).getAmount());
    }

    @Test
    void createDoesNotBindReceiverWhenNormalizedPhoneMatchesMultipleUsers() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
        when(fixture.userMapper.selectOrdinaryUserIdsByNormalizedPhone("886912345678"))
                .thenReturn(List.of(88L, 99L));
        doAnswer(invocation -> {
            FlashDeliveryOrder order = invocation.getArgument(0);
            order.setId(99L);
            return 1;
        }).when(fixture.orderMapper).insert(any(FlashDeliveryOrder.class));
        FlashDeliveryCreateRequest request = createRequest();
        request.getDelivery().setPhone("+886 912-345-678");

        fixture.service.create(7L, request);

        ArgumentCaptor<FlashDeliveryOrder> saved = ArgumentCaptor.forClass(FlashDeliveryOrder.class);
        verify(fixture.orderMapper).insert(saved.capture());
        assertNull(saved.getValue().getReceiverUserId());
    }

    @Test
    void createLeavesReceiverUnboundWhenPhoneHasNoRegisteredUser() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
        when(fixture.userMapper.selectOrdinaryUserIdsByNormalizedPhone("0922222222"))
                .thenReturn(List.of());
        doAnswer(invocation -> {
            FlashDeliveryOrder order = invocation.getArgument(0);
            order.setId(99L);
            return 1;
        }).when(fixture.orderMapper).insert(any(FlashDeliveryOrder.class));

        fixture.service.create(7L, createRequest());

        ArgumentCaptor<FlashDeliveryOrder> saved = ArgumentCaptor.forClass(FlashDeliveryOrder.class);
        verify(fixture.orderMapper).insert(saved.capture());
        assertNull(saved.getValue().getReceiverUserId());
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void receiverListFiltersByCreationTimeReceiverBinding() {
        Fixture fixture = new Fixture();
        when(fixture.orderMapper.selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class))).thenReturn(new Page<FlashDeliveryOrder>());

        fixture.service.userOrders(88L, 1, 10, "receiver");

        ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.query.QueryWrapper> query =
                ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper.class);
        verify(fixture.orderMapper).selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class), query.capture());
        assertTrue(query.getValue().getSqlSegment().toLowerCase().contains("receiver_user_id"));
        assertTrue(query.getValue().getParamNameValuePairs().values().contains(88L));
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void riderNewTaskUsesFoodOrderParametersAndReturnsPrototypeCardSummary() {
        Fixture fixture = new Fixture();
        rider(fixture, 8L);
        FlashDeliveryOrder order = order(10L, "WAITING_ACCEPTANCE");
        Page<FlashDeliveryOrder> source = new Page<>(1, 10, 1);
        source.setRecords(List.of(order));
        when(fixture.orderMapper.selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class))).thenReturn(source);
        when(fixture.orderMapper.selectObjs(any(Wrapper.class))).thenReturn(List.of(168L));

        SysDictData distanceLimit = new SysDictData();
        distanceLimit.setDictValue("5");
        FlashDeliveryRiderOrderPageView result;
        try (MockedStatic<DictUtils> dictionary = mockStatic(DictUtils.class)) {
            dictionary.when(() -> DictUtils.getDictCache("sys_qs_newtask_distance"))
                    .thenReturn(List.of(distanceLimit));
            result = fixture.service.riderOrders(8L, 1, 10, "newTask",
                    new BigDecimal("121.5000"), new BigDecimal("25.0000"));
        }

        assertEquals(1L, result.getNearbyTaskCount());
        assertEquals(168L, result.getHighestOrderAmount());
        assertEquals("pickup road", result.getRecords().get(0).getPickupAddress());
        assertEquals("delivery road", result.getRecords().get(0).getDeliveryAddress());
        assertTrue(result.getRecords().get(0).getPickupDistanceMeters() > 0);
        assertTrue(result.getRecords().get(0).getPinRequired());
        ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.query.QueryWrapper> query =
                ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper.class);
        verify(fixture.orderMapper).selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class), query.capture());
        String sql = query.getValue().getSqlSegment().toLowerCase();
        var parameters = query.getValue().getParamNameValuePairs().values();
        assertTrue(sql.contains("status"));
        assertTrue(sql.contains("rider_id is null"));
        assertTrue(sql.contains("scheduled_pickup_start_at"));
        assertTrue(sql.contains("st_distance_sphere"));
        assertTrue(parameters.contains("WAITING_ACCEPTANCE"));
        assertTrue(parameters.contains("NOW"));
        assertTrue(parameters.stream().anyMatch(Date.class::isInstance));

        ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.query.QueryWrapper> summaryQuery =
                ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper.class);
        verify(fixture.orderMapper).selectObjs(summaryQuery.capture());
        String summarySql = summaryQuery.getValue().getSqlSegment().toLowerCase();
        var summaryParameters = summaryQuery.getValue().getParamNameValuePairs().values();
        assertTrue(summarySql.contains("rider_id is null"));
        assertTrue(summarySql.contains("scheduled_pickup_start_at"));
        assertTrue(summarySql.contains("st_distance_sphere"));
        assertTrue(summaryParameters.contains("WAITING_ACCEPTANCE"));
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void riderNewTaskWithoutPositionFallsBackToNewestFirst() {
        Fixture fixture = new Fixture();
        rider(fixture, 8L);
        when(fixture.orderMapper.selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class))).thenReturn(new Page<FlashDeliveryOrder>());
        when(fixture.orderMapper.selectObjs(any(Wrapper.class))).thenReturn(List.of());

        fixture.service.riderOrders(8L, 1, 10, "newTask", null, null);

        ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.query.QueryWrapper> query =
                ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper.class);
        verify(fixture.orderMapper).selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class), query.capture());
        String sql = query.getValue().getSqlSegment().toLowerCase();
        assertTrue(sql.contains("order by create_time desc"));
        assertFalse(sql.contains("st_distance_sphere"));
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void riderTabsMapStatusesBeforePagingAndRestrictAssignedTabsToRider() {
        assertRiderTab("toPickup", List.of("ACCEPTED"));
        assertRiderTab("delivering", List.of("PICKED_UP"));
        assertRiderTab("completed", List.of("DELIVERED", "COMPLETED"));
        assertRiderTab("cancelled", List.of("CANCELLED"));
    }

    @Test
    void riderListRejectsUnknownTabAndUnpairedOrInvalidCoordinates() {
        Fixture fixture = new Fixture();
        rider(fixture, 8L);

        assertThrows(ServiceException.class,
                () -> fixture.service.riderOrders(8L, 1, 10, "refund", null, null));
        assertThrows(ServiceException.class,
                () -> fixture.service.riderOrders(8L, 1, 10, null, null, null));
        assertThrows(ServiceException.class,
                () -> fixture.service.riderOrders(8L, 1, 10, "newTask", BigDecimal.ONE, null));
        assertThrows(ServiceException.class,
                () -> fixture.service.riderOrders(8L, 1, 10, "newTask",
                        new BigDecimal("181"), BigDecimal.ZERO));
        verify(fixture.orderMapper, never()).selectPage(any(), any());
    }

    @Test
    void flashNewTaskListAndAcceptRequireFlashDeliveryType() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        rider.setDeliveryType("FOOD");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);

        assertThrows(ServiceException.class,
                () -> fixture.service.riderOrders(8L, 1, 10, "newTask", null, null));
        assertThrows(ServiceException.class, () -> fixture.service.accept(8L, 10L));
        verify(fixture.orderMapper, never()).selectPage(any(), any());
        verify(fixture.orderMapper, never()).accept(any(), any(), any());
    }

    @Test
    void riderDetailHidesWaitingFlashOrdersFromNonFlashRiders() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        rider.setDeliveryType("FOOD");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
        when(fixture.orderMapper.selectById(10L)).thenReturn(order(10L, "WAITING_ACCEPTANCE"));

        assertThrows(ServiceException.class, () -> fixture.service.riderDetail(8L, 10L));
        verify(fixture.imageMapper, never()).selectList(any());
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void enabledFlashRiderStillListsNewTasks() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        rider.setDeliveryType("FLASH");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
        when(fixture.orderMapper.selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class))).thenReturn(new Page<FlashDeliveryOrder>());

        fixture.service.riderOrders(8L, 1, 10, "newTask", null, null);
        verify(fixture.orderMapper).selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class));
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void ownOrderTabsRemainAccessibleRegardlessOfDeliveryType() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        rider.setDeliveryType("FOOD");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
        when(fixture.orderMapper.selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class))).thenReturn(new Page<FlashDeliveryOrder>());

        fixture.service.riderOrders(8L, 1, 10, "toPickup", null, null);
        verify(fixture.orderMapper).selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class));
    }

    @Test
    void assignedRiderTabsIgnoreLocationParameters() {
        Fixture fixture = new Fixture();
        rider(fixture, 8L);
        when(fixture.orderMapper.selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class))).thenReturn(new Page<FlashDeliveryOrder>());

        fixture.service.riderOrders(8L, 1, 10, "toPickup",
                new BigDecimal("181"), new BigDecimal("91"));

        verify(fixture.orderMapper).selectPage(any(), any());
    }

    @Test
    void preAcceptRiderDetailIsDirectAndShowsTextAddressWithoutSensitiveFields() {
        Fixture fixture = new Fixture();
        rider(fixture, 8L);
        FlashDeliveryOrder order = order(10L, "WAITING_ACCEPTANCE");
        when(fixture.orderMapper.selectById(10L)).thenReturn(order);

        var result = fixture.service.riderDetail(8L, 10L);

        assertEquals(10L, result.getId());
        assertEquals("pickup road", result.getPickup().getAddress());
        assertEquals("pickup door", result.getPickup().getAddressDetail());
        assertEquals("delivery road", result.getDelivery().getAddress());
        assertEquals("delivery door", result.getDelivery().getAddressDetail());
        assertNull(result.getPickup().getName());
        assertNull(result.getPickup().getPhone());
        assertNull(result.getPickup().getLongitude());
        assertNull(result.getPickup().getLatitude());
        assertNull(result.getDelivery().getName());
        assertNull(result.getDelivery().getPhone());
        assertNull(result.getDelivery().getLongitude());
        assertNull(result.getDelivery().getLatitude());
        assertNull(result.getDeliveryPinCode());
        assertNull(result.getUserNote());
        assertTrue(result.getSenderImageUrls().isEmpty());
        assertTrue(result.getPickupImageUrls().isEmpty());
        assertTrue(result.getDeliveryImageUrls().isEmpty());
        verify(fixture.imageMapper, never()).selectList(any());
        verify(fixture.logMapper, never()).selectList(any());
    }

    @Test
    void userDetailReturnsDirectOrderAndGroupsImageUrlsWithoutRawLogs() {
        Fixture fixture = new Fixture();
        FlashDeliveryOrder order = order(10L, ACCEPTED);
        order.setRiderId(null);
        when(fixture.orderMapper.selectById(10L)).thenReturn(order);
        when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of(
                image("SENDER", "https://example.com/sender.jpg"),
                image("PICKUP", "https://example.com/pickup.jpg"),
                image("DELIVERY", "https://example.com/delivery.jpg")));

        var result = fixture.service.userDetail(7L, 10L);

        assertEquals(10L, result.getId());
        assertEquals(List.of("https://example.com/sender.jpg"), result.getSenderImageUrls());
        assertEquals(List.of("https://example.com/pickup.jpg"), result.getPickupImageUrls());
        assertEquals(List.of("https://example.com/delivery.jpg"), result.getDeliveryImageUrls());
        assertEquals("4821", result.getDeliveryPinCode());
        verify(fixture.logMapper, never()).selectList(any());
    }

    @Test
    void matchedReceiverCanViewAndConfirmButCannotCancelOrder() {
        Fixture fixture = new Fixture();
        FlashDeliveryOrder order = order(10L, "DELIVERED");
        order.setReceiverUserId(88L);
        when(fixture.orderMapper.selectById(10L)).thenReturn(order);
        when(fixture.orderMapper.transitionByParticipant(eq(10L), eq(88L), eq("DELIVERED"),
                eq("COMPLETED"), any(Date.class))).thenReturn(1);
        when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of());

        assertDoesNotThrow(() -> fixture.service.userDetail(88L, 10L));
        assertDoesNotThrow(() -> fixture.service.confirmReceipt(88L, 10L));
        assertThrows(ServiceException.class,
                () -> fixture.service.userCancel(88L, 10L, new com.ruoyi.app.flashdelivery.dto.FlashDeliveryReasonRequest()));
        verify(fixture.orderMapper).transitionByParticipant(eq(10L), eq(88L), eq("DELIVERED"),
                eq("COMPLETED"), any(Date.class));
    }

    @Test
    void orderWithoutCreationTimeReceiverBindingIsNotClaimedLater() {
        Fixture fixture = new Fixture();
        FlashDeliveryOrder historicalOrder = order(10L, "DELIVERED");
        historicalOrder.setReceiverUserId(null);
        when(fixture.orderMapper.selectById(10L)).thenReturn(historicalOrder);

        assertThrows(ServiceException.class, () -> fixture.service.userDetail(88L, 10L));

        verify(fixture.userMapper, never()).selectOrdinaryUserIdsByNormalizedPhone(any());
    }

    @Test
    void pickupRejectsMissingProofBeforeChangingState() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
        FlashDeliveryProofRequest request = new FlashDeliveryProofRequest();
        request.setImageUrls(List.of());

        assertThrows(RuntimeException.class, () -> fixture.service.pickup(8L, 10L, request));
        verify(fixture.orderMapper, never()).transitionByRider(any(), any(), any(), any(), any(), any());
    }

    @Test
    void atomicAcceptFailureDoesNotBindSecondRider() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        rider.setDeliveryType("FLASH");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
        when(fixture.orderMapper.selectById(10L)).thenReturn(order(10L, "WAITING_ACCEPTANCE"));
        when(fixture.orderMapper.accept(eq(10L), eq(8L), any())).thenReturn(0);

        assertThrows(RuntimeException.class, () -> fixture.service.accept(8L, 10L));
    }

    @Test
    void flashAcceptUsesRiderLockAndChecksExclusivityBeforeAtomicUpdate() {
        Fixture fixture = new Fixture();
        rider(fixture, 8L);
        FlashDeliveryOrder waiting = order(10L, "WAITING_ACCEPTANCE");
        FlashDeliveryOrder accepted = order(10L, ACCEPTED);
        accepted.setRiderId(8L);
        when(fixture.orderMapper.selectById(10L)).thenReturn(waiting, accepted);
        when(fixture.orderMapper.accept(eq(10L), eq(8L), any(Date.class))).thenReturn(1);

        fixture.service.accept(8L, 10L);

        InOrder order = inOrder(fixture.lockService, fixture.exclusivityService, fixture.orderMapper);
        order.verify(fixture.lockService).withLock(eq(8L), any(RiderDeliveryLockService.LockedCall.class));
        order.verify(fixture.exclusivityService).assertCanAcceptFlash(8L, "NORMAL");
        order.verify(fixture.orderMapper).accept(eq(10L), eq(8L), any(Date.class));
    }

    @Test
    void flashExclusiveConflictDoesNotUpdateOrder() {
        Fixture fixture = new Fixture();
        rider(fixture, 8L);
        when(fixture.orderMapper.selectById(10L)).thenReturn(order(10L, "WAITING_ACCEPTANCE"));
        doThrow(new ServiceException("exclusive"))
                .when(fixture.exclusivityService).assertCanAcceptFlash(8L, "NORMAL");

        assertThrows(ServiceException.class, () -> fixture.service.accept(8L, 10L));

        verify(fixture.orderMapper, never()).accept(any(), any(), any());
    }

    @Test
    void oversizedProofUrlIsRejectedBeforeStateTransition() {
        Fixture fixture = new Fixture();
        InfoUser rider = new InfoUser();
        rider.setUserId(8L);
        rider.setUserType("2");
        when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
        FlashDeliveryProofRequest request = new FlashDeliveryProofRequest();
        request.setImageUrls(List.of("https://example.com/" + "a".repeat(1000)));

        assertThrows(RuntimeException.class, () -> fixture.service.pickup(8L, 10L, request));
        verify(fixture.orderMapper, never()).transitionByRider(any(), any(), any(), any(), any(), any());
    }

    @Test
    void missingServiceTypeReturnsBusinessErrorInsteadOfNullPointer() {
        Fixture fixture = new Fixture();
        FlashDeliveryQuoteRequest request = request();
        request.setServiceType(null);
        assertThrows(ServiceException.class, () -> fixture.service.quote(request));
    }

    @Test
    void equalTextAddressesAreRejectedEvenWhenCoordinatesDiffer() {
        Fixture fixture = new Fixture();
        FlashDeliveryQuoteRequest request = request();
        request.getDelivery().setAddress(request.getPickup().getAddress());
        request.getDelivery().setAddressDetail(request.getPickup().getAddressDetail());
        assertThrows(ServiceException.class, () -> fixture.service.quote(request));
    }

    @Test
    void concurrentPricingUpdateIsRejected() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectById(1L)).thenReturn(pricing());
        when(fixture.pricingMapper.countOverlapping("00:00", "12:00", 1L)).thenReturn(0);
        when(fixture.pricingMapper.update(isNull(), any(Wrapper.class))).thenReturn(0);
        FlashDeliveryPricingRequest request = pricingRequest();
        request.setEndTime("12:00");
        request.setConfigVersion(1);

        assertThrows(ServiceException.class,
                () -> fixture.service.updatePricing(1L, 1L, request));
        verify(fixture.pricingMapper, never()).updateById(any(FlashDeliveryPricing.class));
    }

    @Test
    void overlappingPricingPeriodIsRejectedBeforeInsert() {
        Fixture fixture = new Fixture();
        FlashDeliveryPricingRequest request = pricingRequest();
        when(fixture.pricingMapper.countOverlapping("00:00", "24:00", null)).thenReturn(1);

        assertThrows(ServiceException.class, () -> fixture.service.createPricing(1L, request));

        request.setDistance(new BigDecimal("0.004"));
        assertThrows(ServiceException.class, () -> fixture.service.createPricing(1L, request));

        verify(fixture.pricingMapper, never()).insert(any(FlashDeliveryPricing.class));
    }

    @Test
    void pricingDistanceHalfCentimetreNormalizesToDatabaseMinimum() {
        Fixture fixture = new Fixture();
        FlashDeliveryPricingRequest request = pricingRequest();
        request.setDistance(new BigDecimal("0.005"));

        fixture.service.createPricing(1L, request);

        ArgumentCaptor<FlashDeliveryPricing> saved = ArgumentCaptor.forClass(FlashDeliveryPricing.class);
        verify(fixture.pricingMapper).insert(saved.capture());
        assertEquals(new BigDecimal("0.01"), saved.getValue().getDistance());
    }

    @Test
    void pricingDistanceThatRoundsToZeroIsRejectedBeforePersistence() {
        Fixture fixture = new Fixture();
        FlashDeliveryPricingRequest request = pricingRequest();
        request.setDistance(new BigDecimal("0.001"));

        assertThrows(ServiceException.class, () -> fixture.service.createPricing(1L, request));

        verify(fixture.pricingMapper, never()).insert(any(FlashDeliveryPricing.class));
    }

    @Test
    void createPricingPersistsImmediatelyActiveTimePeriod() {
        Fixture fixture = new Fixture();
        FlashDeliveryPricingRequest request = pricingRequest();
        when(fixture.pricingMapper.countOverlapping("00:00", "24:00", null)).thenReturn(0);

        fixture.service.createPricing(7L, request);

        ArgumentCaptor<FlashDeliveryPricing> saved = ArgumentCaptor.forClass(FlashDeliveryPricing.class);
        verify(fixture.pricingMapper).insert(saved.capture());
        assertEquals(1, saved.getValue().getConfigVersion());
        assertEquals(7L, saved.getValue().getUpdatedBy());
        assertEquals(90L, saved.getValue().getStartingFare());

        InOrder writeOrder = inOrder(fixture.pricingMapper);
        writeOrder.verify(fixture.pricingMapper).lockPricing();
        writeOrder.verify(fixture.pricingMapper)
                .countOverlapping("00:00", "24:00", null);
        writeOrder.verify(fixture.pricingMapper).insert(any(FlashDeliveryPricing.class));
    }

    @Test
    void homeOmitsServiceWithoutCurrentPricingPeriod() {
        Fixture fixture = new Fixture();

        assertTrue(fixture.service.home().getServices().isEmpty());
    }

    @Test
    void quoteWithoutCurrentPricingPeriodFailsBeforeRouteLookup() {
        Fixture fixture = new Fixture();

        assertThrows(ServiceException.class, () -> fixture.service.quote(request()));

        verify(fixture.routeService, never()).calculate(any(), any());
    }

    @Test
    void multipleCurrentPricingPeriodsFailExplicitlyBeforeRouteLookup() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectAtTime(anyString()))
                .thenReturn(List.of(pricing(), pricing()));

        assertThrows(ServiceException.class, () -> fixture.service.quote(request()));

        verify(fixture.routeService, never()).calculate(any(), any());
    }

    @Test
    void updatingPricingUsesTheSingleGlobalLock() {
        Fixture fixture = new Fixture();
        FlashDeliveryPricing existing = pricing();
        when(fixture.pricingMapper.selectById(1L)).thenReturn(existing);
        when(fixture.pricingMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
        FlashDeliveryPricingRequest request = pricingRequest();
        request.setConfigVersion(1);

        fixture.service.updatePricing(9L, 1L, request);

        InOrder lockOrder = inOrder(fixture.pricingMapper);
        lockOrder.verify(fixture.pricingMapper).lockPricing();
        lockOrder.verify(fixture.pricingMapper)
                .countOverlapping("00:00", "24:00", 1L);
    }

    @Test
    void deletePricingRemovesConfigurationImmediately() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectById(18L)).thenReturn(pricing());
        when(fixture.pricingMapper.deleteById(18L)).thenReturn(1);

        fixture.service.deletePricing(18L);

        InOrder deleteOrder = inOrder(fixture.pricingMapper);
        deleteOrder.verify(fixture.pricingMapper).selectById(18L);
        deleteOrder.verify(fixture.pricingMapper).lockPricing();
        deleteOrder.verify(fixture.pricingMapper).selectById(18L);
        deleteOrder.verify(fixture.pricingMapper).deleteById(18L);
    }

    @Test
    void urgentQuoteReturnsSeparatedFeesAndUsesScheduledPickupTime() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectAtTime("16:30")).thenReturn(List.of(pricing()));
        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(6000, 900, "ROUTE"));
        FlashDeliveryQuoteRequest request = request();
        request.setDeliveryType("URGENT");
        request.setTipAmount(5L);
        request.setDeliveryMode("SCHEDULED");
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 16);
        calendar.set(Calendar.MINUTE, 30);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        request.setScheduledPickupStartAt(calendar.getTime());
        request.setScheduledPickupEndAt(new Date(calendar.getTimeInMillis() + 30L * 60L * 1000L));

        var quote = fixture.service.quote(request);

        assertEquals(45L, quote.getDistanceFee());
        assertEquals(135L, quote.getBaseDeliveryFee());
        assertEquals(27L, quote.getUrgentFee());
        assertEquals(5L, quote.getTipAmount());
        assertEquals(167L, quote.getAmount());
        verify(fixture.pricingMapper).selectAtTime("16:30");
    }

    @Test
    void createRejectsChangedQuoteAndReturnsLatestQuoteWithoutInsert() {
        Fixture fixture = new Fixture();
        when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
        FlashDeliveryCreateRequest request = createRequest();
        request.setQuotedAmount(91L);

        FlashDeliveryQuoteChangedException error = assertThrows(FlashDeliveryQuoteChangedException.class,
                () -> fixture.service.create(7L, request));

        assertEquals(90L, error.getLatestQuote().getAmount());
        assertEquals(1L, error.getLatestQuote().getPricingId());
        verify(fixture.orderMapper, never()).insert(any(FlashDeliveryOrder.class));
    }

    @Test
    void quoteValidatesDeliveryLevelAndCompleteItemInformation() {
        Fixture fixture = new Fixture();
        FlashDeliveryQuoteRequest request = request();
        request.setDeliveryType("FAST");
        FlashDeliveryQuoteRequest invalidDeliveryType = request;
        assertThrows(ServiceException.class, () -> fixture.service.quote(invalidDeliveryType));

        request = request();
        request.setQuantity(0);
        FlashDeliveryQuoteRequest invalidQuantity = request;
        assertThrows(ServiceException.class, () -> fixture.service.quote(invalidQuantity));

        request = request();
        request.setTotalWeightKg(new BigDecimal("20.01"));
        FlashDeliveryQuoteRequest overweight = request;
        assertThrows(ServiceException.class, () -> fixture.service.quote(overweight));
    }

    private FlashDeliveryQuoteRequest request() {
        FlashDeliveryQuoteRequest request = new FlashDeliveryQuoteRequest();
        request.setServiceType("HELP_SEND");
        request.setDeliveryType("NORMAL");
        request.setPackageType("DOCUMENT");
        request.setQuantity(1);
        request.setTotalWeightKg(new BigDecimal("3.00"));
        request.setSpecification("30 x 20 x 10 cm");
        request.setTipAmount(0L);
        request.setDeliveryMode("NOW");
        request.setPickup(address("A", "0911111111", "25.0330", "121.5645"));
        request.setDelivery(address("B", "0922222222", "25.0478", "121.5319"));
        request.getDelivery().setAddress("second road");
        return request;
    }

    private FlashDeliveryCreateRequest createRequest() {
        FlashDeliveryCreateRequest request = new FlashDeliveryCreateRequest();
        FlashDeliveryQuoteRequest quote = request();
        request.setServiceType(quote.getServiceType());
        request.setDeliveryType(quote.getDeliveryType());
        request.setPackageType(quote.getPackageType());
        request.setQuantity(quote.getQuantity());
        request.setTotalWeightKg(quote.getTotalWeightKg());
        request.setSpecification(quote.getSpecification());
        request.setTipAmount(quote.getTipAmount());
        request.setPickup(quote.getPickup());
        request.setDelivery(quote.getDelivery());
        request.setClientRequestId("flash-scheduled-001");
        request.setDeliveryMode("SCHEDULED");
        Date start = new Date(System.currentTimeMillis() + 60L * 60L * 1000L);
        request.setScheduledPickupStartAt(start);
        request.setScheduledPickupEndAt(new Date(start.getTime() + 30L * 60L * 1000L));
        request.setPinRequired(true);
        request.setPricingId(1L);
        request.setPricingVersion(1);
        request.setQuotedBaseDeliveryFee(90L);
        request.setQuotedDistanceFee(0L);
        request.setQuotedUrgentFee(0L);
        request.setQuotedAmount(90L);
        request.setSenderImageUrls(List.of("https://example.com/sender.jpg"));
        return request;
    }

    private FlashDeliveryAddressRequest address(String name, String phone, String lat, String lon) {
        FlashDeliveryAddressRequest address = new FlashDeliveryAddressRequest();
        address.setName(name);
        address.setPhone(phone);
        address.setAddress("road");
        address.setAddressDetail("door");
        address.setLatitude(new BigDecimal(lat));
        address.setLongitude(new BigDecimal(lon));
        return address;
    }

    private FlashDeliveryPricing pricing() {
        FlashDeliveryPricing pricing = new FlashDeliveryPricing();
        pricing.setId(1L);
        pricing.setStartTime("00:00");
        pricing.setEndTime("24:00");
        pricing.setStartingDistance(new BigDecimal("3.00"));
        pricing.setStartingFare(90L);
        pricing.setDistance(new BigDecimal("1.00"));
        pricing.setFreight(15L);
        pricing.setUrgentRate(new BigDecimal("20.00"));
        pricing.setMinimumUrgentFee(10L);
        pricing.setConfigVersion(1);
        return pricing;
    }

    private FlashDeliveryPricingRequest pricingRequest() {
        FlashDeliveryPricingRequest request = new FlashDeliveryPricingRequest();
        request.setStartTime("00:00");
        request.setEndTime("24:00");
        request.setStartingDistance(new BigDecimal("3.00"));
        request.setStartingFare(90L);
        request.setDistance(new BigDecimal("1.00"));
        request.setFreight(15L);
        request.setUrgentRate(new BigDecimal("20.00"));
        request.setMinimumUrgentFee(10L);
        return request;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private void assertRiderTab(String tab, List<String> statuses) {
        Fixture fixture = new Fixture();
        rider(fixture, 8L);
        when(fixture.orderMapper.selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class),
                any(Wrapper.class))).thenReturn(new Page<FlashDeliveryOrder>());

        fixture.service.riderOrders(8L, 1, 10, tab, null, null);

        ArgumentCaptor<com.baomidou.mybatisplus.core.conditions.query.QueryWrapper> query =
                ArgumentCaptor.forClass(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper.class);
        verify(fixture.orderMapper).selectPage(any(com.baomidou.mybatisplus.core.metadata.IPage.class), query.capture());
        // MyBatis-Plus 的条件参数在渲染 SQL 段时才写入参数表，必须先取 SQL 再断言参数。
        String sql = query.getValue().getSqlSegment();
        assertTrue(sql.contains("rider_id"), tab);
        var parameters = query.getValue().getParamNameValuePairs().values();
        assertTrue(parameters.contains(8L), tab);
        for (String status : statuses) assertTrue(parameters.contains(status), tab + ":" + status);
    }

    private void rider(Fixture fixture, Long riderId) {
        InfoUser rider = new InfoUser();
        rider.setUserId(riderId);
        rider.setUserType("2");
        rider.setDeliveryType("FLASH");
        when(fixture.userMapper.selectInfoUserByUserId(riderId)).thenReturn(rider);
    }

    private FlashDeliveryOrder order(Long id, String status) {
        FlashDeliveryOrder order = new FlashDeliveryOrder();
        order.setId(id);
        order.setOrderNo("FL-20260813-021");
        order.setUserId(7L);
        order.setServiceType("HELP_SEND");
        order.setDeliveryType("NORMAL");
        order.setStatus(status);
        order.setPackageType("DOCUMENT");
        order.setQuantity(1);
        order.setTotalWeightKg(new BigDecimal("3.00"));
        order.setPackageSize("SMALL");
        order.setDeliveryMode("NOW");
        order.setPinRequired(true);
        order.setDeliveryPinCode("4821");
        order.setPickupName("pickup contact");
        order.setPickupPhone("0911111111");
        order.setPickupAddress("pickup road");
        order.setPickupAddressDetail("pickup door");
        order.setPickupCity("台北市");
        order.setPickupArea("中山區");
        order.setPickupLongitude(new BigDecimal("121.5100"));
        order.setPickupLatitude(new BigDecimal("25.0100"));
        order.setDeliveryName("delivery contact");
        order.setDeliveryPhone("0922222222");
        order.setDeliveryAddress("delivery road");
        order.setDeliveryAddressDetail("delivery door");
        order.setDeliveryCity("台北市");
        order.setDeliveryArea("大安區");
        order.setDeliveryLongitude(new BigDecimal("121.5200"));
        order.setDeliveryLatitude(new BigDecimal("25.0200"));
        order.setDistanceMeters(3200);
        order.setEstimatedDurationSeconds(2100);
        order.setDistanceFee(12L);
        order.setBaseDeliveryFee(102L);
        order.setUrgentRate(new BigDecimal("20.00"));
        order.setMinimumUrgentFee(10L);
        order.setUrgentFee(0L);
        order.setTipAmount(10L);
        order.setAmount(112L);
        order.setCurrency("TWD");
        order.setUserNote("private note");
        order.setCreateTime(new Date());
        return order;
    }

    private FlashDeliveryOrderImage image(String proofType, String url) {
        FlashDeliveryOrderImage image = new FlashDeliveryOrderImage();
        image.setProofType(proofType);
        image.setImageUrl(url);
        return image;
    }

    private static class Fixture {
        final FlashDeliveryOrderMapper orderMapper = mock(FlashDeliveryOrderMapper.class);
        final FlashDeliveryPricingMapper pricingMapper = mock(FlashDeliveryPricingMapper.class);
        final FlashDeliveryOrderImageMapper imageMapper = mock(FlashDeliveryOrderImageMapper.class);
        final FlashDeliveryOrderLogMapper logMapper = mock(FlashDeliveryOrderLogMapper.class);
        final InfoUserMapper userMapper = mock(InfoUserMapper.class);
        final FlashDeliveryRouteService routeService = mock(FlashDeliveryRouteService.class);
        final RiderDeliveryLockService lockService = mock(RiderDeliveryLockService.class);
        final RiderDeliveryExclusivityService exclusivityService = mock(RiderDeliveryExclusivityService.class);
        final FlashDeliveryApplicationService service = new FlashDeliveryApplicationService(
                orderMapper, pricingMapper, imageMapper, logMapper, userMapper,
                routeService, new FlashDeliveryPricingCalculator(), lockService, exclusivityService);

        Fixture() {
            when(pricingMapper.lockPricing()).thenReturn("GLOBAL");
            when(lockService.withLock(any(), any())).thenAnswer(invocation -> {
                RiderDeliveryLockService.LockedCall<?> call = invocation.getArgument(1);
                return call.call();
            });
        }
    }
}
