package com.ruoyi.app.order;

import com.ruoyi.app.user.MerchantTokenSessionService;
import com.ruoyi.app.utils.event.PushEventService;
import com.ruoyi.system.domain.InfoUser;
import com.ruoyi.system.domain.PosStore;
import com.ruoyi.system.mapper.MerchantSubaccountStoreMapper;
import com.ruoyi.system.mapper.PosStoreMapper;
import com.ruoyi.system.service.IInfoUserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class MerchantNotificationRouterTest {
    private MerchantSubaccountStoreMapper relations;
    private PosStoreMapper stores;
    private IInfoUserService users;
    private MerchantTokenSessionService sessions;
    private PushEventService events;
    private MerchantNotificationRouter router;

    @BeforeEach
    void setUp() {
        relations = mock(MerchantSubaccountStoreMapper.class);
        stores = mock(PosStoreMapper.class);
        users = mock(IInfoUserService.class);
        sessions = mock(MerchantTokenSessionService.class);
        events = mock(PushEventService.class);
        router = spy(new MerchantNotificationRouter(relations, stores, users, sessions, events));
        doAnswer(invocation -> invocation.getArgument(0)).when(router).localize(anyString(), anyLong());
        doNothing().when(router).sendExternal(anyString(), anyString(), anyString(), anyString());
        when(stores.selectPosStoreById(9L)).thenReturn(store(9, 101L));
        when(users.getById(101L)).thenReturn(owner(101L));
    }

    @Test
    void sendsMessageToEveryOnlineSubaccountWithoutDuplicatingOwner() {
        InfoUser first = subaccount(501L, "cid-a");
        InfoUser second = subaccount(502L, "cid-b");
        when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(first, second));
        when(sessions.hasAppSession(501L)).thenReturn(true);
        when(sessions.hasAppSession(502L)).thenReturn(true);

        router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");

        verify(events).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
        verify(events).PublisherEvent(502L, "title", "content,NO:DD-1", "body");
        verify(events, never()).PublisherEvent(101L, "title", "content,NO:DD-1", "body");
        verify(router).sendExternal("cid-a", "title", "content,NO:DD-1", "body");
        verify(router).sendExternal("cid-b", "title", "content,NO:DD-1", "body");
    }

    @Test
    void storesOneMessagePerAccountButPushesSharedCidOnlyOnce() {
        InfoUser first = subaccount(501L, "shared-cid");
        InfoUser second = subaccount(502L, "shared-cid");
        when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(first, second));
        when(sessions.hasAppSession(anyLong())).thenReturn(true);

        router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");

        verify(events).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
        verify(events).PublisherEvent(502L, "title", "content,NO:DD-1", "body");
        verify(router, times(1)).sendExternal("shared-cid", "title", "content,NO:DD-1", "body");
    }

    @Test
    void fallsBackToOwnerWhenNoSubaccountHasAppSessionAndCid() {
        InfoUser offline = subaccount(501L, "cid-a");
        InfoUser missingCid = subaccount(502L, null);
        when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(offline, missingCid));

        router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");

        verify(events).PublisherEvent(101L, "title", "content,NO:DD-1", "body");
        verify(events, never()).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
    }

    @Test
    void disabledSubaccountAndDisabledOwnerDoNotReceiveNotification() {
        InfoUser disabledSubaccount = subaccount(501L, "cid-a");
        disabledSubaccount.setStatus("1");
        InfoUser disabledOwner = owner(101L);
        disabledOwner.setStatus("1");
        when(users.getById(101L)).thenReturn(disabledOwner);
        when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(disabledSubaccount));
        when(sessions.hasAppSession(501L)).thenReturn(true);

        router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");

        verify(events, never()).PublisherEvent(anyLong(), anyString(), anyString(), anyString());
        verify(relations, never()).selectEligibleSubaccountsByStoreId(9L);
    }

    @Test
    void platformOrOwnerDisabledSubaccountsAreExcludedAndOwnerReceivesFallback() {
        InfoUser platformDisabled = subaccount(501L, "cid-a");
        platformDisabled.setStatus("1");
        InfoUser ownerDisabled = subaccount(502L, "cid-b");
        ownerDisabled.setSubaccountStatus("1");
        when(relations.selectEligibleSubaccountsByStoreId(9L))
                .thenReturn(List.of(platformDisabled, ownerDisabled));
        when(sessions.hasAppSession(anyLong())).thenReturn(true);

        router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");

        verify(events).PublisherEvent(101L, "title", "content,NO:DD-1", "body");
        verify(events, never()).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
        verify(events, never()).PublisherEvent(502L, "title", "content,NO:DD-1", "body");
    }

    @Test
    void oneExternalFailureDoesNotBlockOtherMessagesOrPushes() {
        InfoUser first = subaccount(501L, "cid-a");
        InfoUser second = subaccount(502L, "cid-b");
        when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(first, second));
        when(sessions.hasAppSession(anyLong())).thenReturn(true);
        doThrow(new IllegalStateException("push failed"))
                .when(router).sendExternal("cid-a", "title", "content,NO:DD-1", "body");

        router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");

        verify(events).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
        verify(events).PublisherEvent(502L, "title", "content,NO:DD-1", "body");
        verify(router).sendExternal("cid-b", "title", "content,NO:DD-1", "body");
    }

    @Test
    void missingStoreRoutesOnlyToProvidedOwnerFallback() {
        when(stores.selectPosStoreById(99L)).thenReturn(null);

        router.sendStoreNotification(99L, 101L, "title", "content", "body", "DD-1");

        verify(relations, never()).selectEligibleSubaccountsByStoreId(99L);
        verify(events).PublisherEvent(101L, "title", "content,NO:DD-1", "body");
    }

    private static InfoUser owner(Long id) {
        InfoUser user = new InfoUser();
        user.setUserId(id);
        user.setUserType("1");
        user.setStatus("0");
        user.setDelFlag("0");
        user.setCid("owner-cid");
        return user;
    }

    private static InfoUser subaccount(Long id, String cid) {
        InfoUser user = new InfoUser();
        user.setUserId(id);
        user.setUserType("5");
        user.setStatus("0");
        user.setSubaccountStatus("0");
        user.setDelFlag("0");
        user.setCid(cid);
        return user;
    }

    private static PosStore store(Integer id, Long ownerId) {
        PosStore store = new PosStore();
        store.setId(id);
        store.setUserId(ownerId);
        store.setDelFlag("0");
        return store;
    }
}
