package com.ruoyi.app.order;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
import com.ruoyi.system.service.IPosOrderService;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.test.util.ReflectionTestUtils;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;

class RiderDeliveryExclusivityServiceTest {
    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 urgentRejectsActiveFood() {
        Fixture fixture = new Fixture();
        when(fixture.foodOrders.count(any(Wrapper.class))).thenReturn(1L);
        assertThrows(ServiceException.class,
                () -> fixture.service.assertCanAcceptFlash(123L, "URGENT"));
    }

    @Test
    void urgentRejectsActiveNormalFlash() {
        Fixture fixture = new Fixture();
        when(fixture.foodOrders.count(any(Wrapper.class))).thenReturn(0L);
        when(fixture.flashOrders.selectCount(any(Wrapper.class))).thenReturn(1L);
        assertThrows(ServiceException.class,
                () -> fixture.service.assertCanAcceptFlash(123L, "URGENT"));
    }

    @Test
    void urgentAllowsRiderWithoutActiveWork() {
        Fixture fixture = new Fixture();
        when(fixture.foodOrders.count(any(Wrapper.class))).thenReturn(0L);
        when(fixture.flashOrders.selectCount(any(Wrapper.class))).thenReturn(0L);
        assertDoesNotThrow(() -> fixture.service.assertCanAcceptFlash(123L, "URGENT"));
    }

    @Test
    void normalFlashRejectsActiveUrgent() {
        Fixture fixture = new Fixture();
        when(fixture.flashOrders.selectCount(any(Wrapper.class))).thenReturn(1L);
        assertThrows(ServiceException.class,
                () -> fixture.service.assertCanAcceptFlash(123L, "NORMAL"));
    }

    @Test
    void foodRejectsActiveUrgent() {
        Fixture fixture = new Fixture();
        when(fixture.flashOrders.selectCount(any(Wrapper.class))).thenReturn(1L);
        assertThrows(ServiceException.class, () -> fixture.service.assertCanAcceptFood(123L));
    }

    @Test
    void foodAllowsActiveNormalFlashWhenNoUrgentExists() {
        Fixture fixture = new Fixture();
        when(fixture.flashOrders.selectCount(any(Wrapper.class))).thenReturn(0L);
        assertDoesNotThrow(() -> fixture.service.assertCanAcceptFood(123L));
    }

    @Test
    @SuppressWarnings({"rawtypes", "unchecked"})
    void activeQueriesUseOnlyDefinedFoodAndFlashStates() {
        Fixture fixture = new Fixture();
        when(fixture.foodOrders.count(any(Wrapper.class))).thenReturn(0L);
        when(fixture.flashOrders.selectCount(any(Wrapper.class))).thenReturn(0L);

        fixture.service.assertCanAcceptFlash(123L, "URGENT");
        fixture.service.assertCanAcceptFood(123L);

        ArgumentCaptor<Wrapper> foodQuery = ArgumentCaptor.forClass(Wrapper.class);
        ArgumentCaptor<Wrapper> flashQuery = ArgumentCaptor.forClass(Wrapper.class);
        verify(fixture.foodOrders).count(foodQuery.capture());
        verify(fixture.flashOrders, times(2)).selectCount(flashQuery.capture());
        String foodSql = foodQuery.getValue().getSqlSegment().toLowerCase();
        String allFlashSql = flashQuery.getAllValues().get(0).getSqlSegment().toLowerCase();
        String urgentFlashSql = flashQuery.getAllValues().get(1).getSqlSegment().toLowerCase();
        assertTrue(foodSql.contains("qs_id"));
        assertTrue(foodSql.contains("delivery_status"));
        assertTrue(foodSql.contains("state"));
        assertTrue(foodSql.contains("after_sale_status"));
        assertTrue(allFlashSql.contains("rider_id"));
        assertTrue(allFlashSql.contains("status"));
        assertTrue(urgentFlashSql.contains("delivery_type"));
        assertTrue(!allFlashSql.contains("service_type"));
        assertTrue(!urgentFlashSql.contains("service_type"));
    }

    private static class Fixture {
        final IPosOrderService foodOrders = mock(IPosOrderService.class);
        final FlashDeliveryOrderMapper flashOrders = mock(FlashDeliveryOrderMapper.class);
        final RiderDeliveryExclusivityService service =
                new RiderDeliveryExclusivityService(foodOrders, flashOrders);
    }
}
