package com.ruoyi.app.order;

import com.ruoyi.app.order.dto.OrderCreatItem;
import com.ruoyi.app.order.dto.OrderCreateInput;
import com.ruoyi.app.pay.LinePayOrderGuard;
import com.ruoyi.app.pay.LinePayRefundService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.system.domain.InfoUser;
import com.ruoyi.system.domain.OrderParent;
import com.ruoyi.system.domain.PosOrder;
import com.ruoyi.system.domain.PosStore;
import com.ruoyi.system.service.IInfoUserService;
import com.ruoyi.system.service.IOperatingHoursService;
import com.ruoyi.system.service.IOrderParentService;
import com.ruoyi.system.service.IPosOrderService;
import com.ruoyi.system.service.IPosStoreService;
import com.ruoyi.system.service.MerchantAccessContext;
import com.ruoyi.system.service.MerchantStoreAccessService;
import com.ruoyi.system.utils.JwtUtil;
import com.ruoyi.system.utils.AuthContext;
import com.ruoyi.system.utils.OrderLogHelper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.List;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;

class PosOrderShOprateControllerTest {
    private MockedStatic<MessageUtils> messages;
    private MockedStatic<AuthContext> authContext;

    @BeforeEach
    void setUp() {
        messages = mockStatic(MessageUtils.class);
        messages.when(() -> MessageUtils.message(anyString()))
                .thenAnswer(invocation -> invocation.getArgument(0));
        authContext = mockStatic(AuthContext.class);
    }

    @AfterEach
    void tearDown() {
        authContext.close();
        messages.close();
    }

    @Test
    void ordinaryAndNightMarketMerchantMustUseOwnShId() {
        PosOrderShOprateController controller = controllerWithStoreOwner(101L);
        assertDoesNotThrow(() -> controller.validateMerchantOrderInput(user(101L, "1", null),
                input(item(101L, 9L))));
        assertDoesNotThrow(() -> controller.validateMerchantOrderInput(user(101L, "3", null),
                input(item(101L, 9L))));
        assertThrows(ServiceException.class, () -> controller.validateMerchantOrderInput(
                user(101L, "1", null), input(item(999L, 9L))));
    }

    @Test
    void stallMerchantMustUseAssociatedStoreAndRequestMustBeSingleStore() {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        assertDoesNotThrow(() -> controller.validateMerchantOrderInput(user(101L, "4", 9L),
                input(item(500L, 9L))));
        assertThrows(ServiceException.class, () -> controller.validateMerchantOrderInput(
                user(101L, "4", 9L), input(item(500L, 8L))));

        OrderCreateInput multi = input(item(101L, 9L));
        multi.setItems(List.of(item(101L, 9L), item(101L, 10L)));
        assertThrows(ServiceException.class,
                () -> controllerWithStoreOwner(101L).validateMerchantOrderInput(
                        user(101L, "1", null), multi));
    }

    @Test
    void directMerchantCannotPairOwnShIdWithAnotherOwnersStore() {
        PosOrderShOprateController controller = controllerWithStoreOwner(999L);

        assertThrows(ServiceException.class, () -> controller.validateMerchantOrderInput(
                user(101L, "1", null), input(item(101L, 9L))));
    }

    @Test
    void usersAndRidersCannotCreateMerchantOrders() {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        assertThrows(ServiceException.class, () -> controller.validateMerchantOrderInput(
                user(101L, "0", null), input(item(101L, 9L))));
        assertThrows(ServiceException.class, () -> controller.validateMerchantOrderInput(
                user(101L, "2", null), input(item(101L, 9L))));
    }

    @Test
    void createOrderPersistsMerchantSourceBeforeReturning() {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        IInfoUserService users = mock(IInfoUserService.class);
        IPosOrderService orders = mock(IPosOrderService.class);
        IPosStoreService stores = mock(IPosStoreService.class);
        IOperatingHoursService hours = mock(IOperatingHoursService.class);
        IOrderParentService parents = mock(IOrderParentService.class);
        ReflectionTestUtils.setField(controller, "infoUserService", users);
        ReflectionTestUtils.setField(controller, "posOrderService", orders);
        ReflectionTestUtils.setField(controller, "posStoreService", stores);
        ReflectionTestUtils.setField(controller, "operatingHoursService", hours);
        ReflectionTestUtils.setField(controller, "orderParentService", parents);
        wireMerchantSecurity(controller, 101L);
        when(users.getOne(any())).thenReturn(user(101L, "1", null));
        PosStore store = new PosStore();
        store.setId(9);
        store.setUserId(101L);
        when(stores.getById(9L)).thenReturn(store);
        when(stores.list(org.mockito.ArgumentMatchers
                .<com.baomidou.mybatisplus.core.conditions.Wrapper<PosStore>>any()))
                .thenReturn(List.of(store));
        when(hours.list(org.mockito.ArgumentMatchers
                .<com.baomidou.mybatisplus.core.conditions.Wrapper<com.ruoyi.system.domain.OperatingHours>>any()))
                .thenReturn(List.of());
        when(orders.save(any())).thenReturn(true);
        when(parents.save(any())).thenReturn(true);
        OrderParent parent = new OrderParent();
        parent.setDdId("DD-100");
        when(parents.getOne(any())).thenReturn(parent);

        controller.createOrder(JwtUtil.token("101", "merchant"), input(item(101L, 9L)));

        ArgumentCaptor<PosOrder> captor = ArgumentCaptor.forClass(PosOrder.class);
        verify(orders).save(captor.capture());
        org.junit.jupiter.api.Assertions.assertEquals("MERCHANT",
                captor.getValue().getOrderSource());
    }

    @Test
    void deliveryOrderCannotBeAcceptedByMerchantBeforeRiderAssignment() {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        IInfoUserService users = mock(IInfoUserService.class);
        IPosOrderService orders = mock(IPosOrderService.class);
        LinePayOrderGuard guard = mock(LinePayOrderGuard.class);
        ReflectionTestUtils.setField(controller, "infoUserService", users);
        ReflectionTestUtils.setField(controller, "posOrderService", orders);
        ReflectionTestUtils.setField(controller, "linePayOrderGuard", guard);
        ReflectionTestUtils.setField(controller, "orderLogHelper", mock(OrderLogHelper.class));
        wireMerchantSecurity(controller, 101L);

        PosOrder order = new PosOrder();
        order.setId(10L);
        order.setDdId("DD-DELIVERY");
        order.setType(0L);
        order.setState(0L);
        order.setPayStatus(1L);
        order.setDeliveryStatus(0L);
        order.setShId(101L);
        when(orders.getOne(any())).thenReturn(order);
        when(users.getOne(any())).thenReturn(user(101L, "1", null));

        assertThrows(ServiceException.class,
                () -> controller.acceptOrder(JwtUtil.token("101", "merchant"), 10L));

        verify(orders, never()).saveOrUpdate(any(PosOrder.class));
    }

    @Test
    void dispatchingDeliveryOrderKeepsAssignedRiderStatus() {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        IInfoUserService users = mock(IInfoUserService.class);
        IPosOrderService orders = mock(IPosOrderService.class);
        ReflectionTestUtils.setField(controller, "infoUserService", users);
        ReflectionTestUtils.setField(controller, "posOrderService", orders);
        ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));
        ReflectionTestUtils.setField(controller, "orderLogHelper", mock(OrderLogHelper.class));
        ReflectionTestUtils.setField(controller, "orderInvoiceService", mock(OrderInvoiceService.class));
        wireMerchantSecurity(controller, 101L);

        PosOrder order = new PosOrder();
        order.setId(10L);
        order.setDdId("DD-DELIVERY");
        order.setType(0L);
        order.setState(1L);
        order.setPayStatus(1L);
        order.setDeliveryStatus(1L);
        order.setQsId(88L);
        order.setShId(101L);
        when(orders.getOne(any())).thenReturn(order);
        when(users.getOne(any())).thenReturn(user(101L, "1", null));
        when(users.list(org.mockito.ArgumentMatchers
                .<com.baomidou.mybatisplus.core.conditions.Wrapper<InfoUser>>any())).thenReturn(List.of());

        controller.dispatchOrder(JwtUtil.token("101", "merchant"), 10L);

        ArgumentCaptor<PosOrder> captor = ArgumentCaptor.forClass(PosOrder.class);
        verify(orders).saveOrUpdate(captor.capture());
        org.junit.jupiter.api.Assertions.assertEquals(2L, captor.getValue().getState());
        org.junit.jupiter.api.Assertions.assertNull(captor.getValue().getDeliveryStatus());
    }

    @Test
    void subaccountCannotOperateOrderFromUnauthorizedStoreEvenWhenOwnerMatches() {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        IInfoUserService users = mock(IInfoUserService.class);
        IPosOrderService orders = mock(IPosOrderService.class);
        MerchantStoreAccessService access = mock(MerchantStoreAccessService.class);
        ReflectionTestUtils.setField(controller, "infoUserService", users);
        ReflectionTestUtils.setField(controller, "posOrderService", orders);
        ReflectionTestUtils.setField(controller, "merchantStoreAccessService", access);
        ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));

        PosOrder order = new PosOrder();
        order.setId(10L);
        order.setMdId(99L);
        order.setShId(101L);
        order.setState(0L);
        InfoUser subaccount = user(501L, "5", null);
        subaccount.setMerchantOwnerId(101L);
        authContext.when(AuthContext::requireUserId).thenReturn(501L);
        when(users.getOne(any())).thenReturn(subaccount);
        when(orders.getOne(any())).thenReturn(order);
        org.mockito.Mockito.doThrow(new ServiceException("merchant.store.access.denied"))
                .when(access).requireOrderAccess(501L, 10L);

        assertThrows(ServiceException.class,
                () -> controller.acceptOrder(JwtUtil.token("501", "subaccount"), 10L));
        verify(orders, never()).saveOrUpdate(any(PosOrder.class));
    }

    @Test
    void unauthorizedSubaccountCannotReachCancellationRefundFlow() {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        IInfoUserService users = mock(IInfoUserService.class);
        IPosOrderService orders = mock(IPosOrderService.class);
        MerchantStoreAccessService access = mock(MerchantStoreAccessService.class);
        LinePayRefundService refunds = mock(LinePayRefundService.class);
        ReflectionTestUtils.setField(controller, "infoUserService", users);
        ReflectionTestUtils.setField(controller, "posOrderService", orders);
        ReflectionTestUtils.setField(controller, "merchantStoreAccessService", access);
        ReflectionTestUtils.setField(controller, "linePayOrderGuard", mock(LinePayOrderGuard.class));
        ReflectionTestUtils.setField(controller, "linePayRefundService", refunds);

        PosOrder order = new PosOrder();
        order.setId(10L);
        order.setMdId(99L);
        order.setShId(101L);
        order.setState(0L);
        InfoUser subaccount = user(501L, "5", null);
        authContext.when(AuthContext::requireUserId).thenReturn(501L);
        when(users.getOne(any())).thenReturn(subaccount);
        when(orders.getOne(any())).thenReturn(order);
        org.mockito.Mockito.doThrow(new ServiceException("merchant.store.access.denied"))
                .when(access).requireOrderAccess(501L, 10L);

        assertThrows(ServiceException.class,
                () -> controller.cancelOrder(JwtUtil.token("501", "subaccount"), 10L));
        verify(orders, never()).update(any(), any());
        verify(refunds, never()).createRefundIntent(any(), anyString());
    }

    @Test
    void subaccountCreatesOrderAsOwnerForAuthorizedStore() {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        MerchantStoreAccessService access = mock(MerchantStoreAccessService.class);
        ReflectionTestUtils.setField(controller, "merchantStoreAccessService", access);
        when(access.resolve(501L)).thenReturn(new MerchantAccessContext(501L, 101L, false, Set.of(9L)));
        InfoUser subaccount = user(501L, "5", null);
        subaccount.setMerchantOwnerId(101L);
        OrderCreatItem item = item(999L, 9L);

        controller.validateMerchantOrderInput(subaccount, input(item));

        org.junit.jupiter.api.Assertions.assertEquals(101L, item.getShId());
        verify(access).requireStoreAccess(501L, 9L);
    }

    private static InfoUser user(Long id, String type, Long storeId) {
        InfoUser user = new InfoUser();
        user.setUserId(id);
        user.setUserType(type);
        user.setStoreId(storeId);
        return user;
    }

    private static OrderCreateInput input(OrderCreatItem item) {
        OrderCreateInput input = new OrderCreateInput();
        input.setDdId("DD-100");
        input.setPaymentMethod("3");
        input.setItems(List.of(item));
        return input;
    }

    private static OrderCreatItem item(Long shId, Long mdId) {
        OrderCreatItem item = new OrderCreatItem();
        item.setShId(shId);
        item.setMdId(mdId);
        item.setAmount(280);
        return item;
    }

    private static PosOrderShOprateController controllerWithStoreOwner(Long ownerId) {
        PosOrderShOprateController controller = new PosOrderShOprateController();
        IPosStoreService stores = mock(IPosStoreService.class);
        PosStore store = new PosStore();
        store.setId(9);
        store.setUserId(ownerId);
        when(stores.getById(9L)).thenReturn(store);
        ReflectionTestUtils.setField(controller, "posStoreService", stores);
        return controller;
    }

    private void wireMerchantSecurity(PosOrderShOprateController controller, Long userId) {
        MerchantStoreAccessService access = mock(MerchantStoreAccessService.class);
        authContext.when(AuthContext::requireUserId).thenReturn(userId);
        ReflectionTestUtils.setField(controller, "merchantStoreAccessService", access);
    }
}
