package com.ruoyi.app.order;

import com.ruoyi.app.user.MerchantTokenSessionService;
import com.ruoyi.app.utils.PayPush;
import com.ruoyi.app.utils.event.PushEventService;
import com.ruoyi.common.utils.LocaleUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.InfoUser;
import com.ruoyi.system.domain.PosStore;
import com.ruoyi.system.domain.constants.MerchantAccountConstants;
import com.ruoyi.system.mapper.MerchantSubaccountStoreMapper;
import com.ruoyi.system.mapper.PosStoreMapper;
import com.ruoyi.system.service.IInfoUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/** Routes one store notification to online subaccounts or the merchant owner fallback. */
@Service
public class MerchantNotificationRouter {
    private static final Logger log = LoggerFactory.getLogger(MerchantNotificationRouter.class);

    private final MerchantSubaccountStoreMapper relationMapper;
    private final PosStoreMapper posStoreMapper;
    private final IInfoUserService infoUserService;
    private final MerchantTokenSessionService tokenSessionService;
    private final PushEventService pushEventService;

    public MerchantNotificationRouter(MerchantSubaccountStoreMapper relationMapper,
                                      PosStoreMapper posStoreMapper,
                                      IInfoUserService infoUserService,
                                      MerchantTokenSessionService tokenSessionService,
                                      PushEventService pushEventService) {
        this.relationMapper = relationMapper;
        this.posStoreMapper = posStoreMapper;
        this.infoUserService = infoUserService;
        this.tokenSessionService = tokenSessionService;
        this.pushEventService = pushEventService;
    }

    public void sendStoreNotification(Long storeId, Long ownerUserId,
                                      String titleKey, String contentKey,
                                      String body, String orderNo) {
        StoreRoute route = resolveRoute(storeId, ownerUserId);
        Long resolvedOwnerId = route.ownerUserId();
        InfoUser owner = resolvedOwnerId == null ? null : infoUserService.getById(resolvedOwnerId);
        boolean activeOwner = isActiveOwner(owner);
        List<InfoUser> eligible = !route.storeValid() || !activeOwner
                ? List.of() : relationMapper.selectEligibleSubaccountsByStoreId(storeId);
        Map<Long, InfoUser> recipients = new LinkedHashMap<>();
        if (eligible != null) {
            for (InfoUser candidate : eligible) {
                if (isSubaccountRecipient(candidate)) {
                    recipients.putIfAbsent(candidate.getUserId(), candidate);
                }
            }
        }
        if (recipients.isEmpty()) {
            if (activeOwner) {
                recipients.put(owner.getUserId(), owner);
            } else {
                log.warn("Store notification has no active recipient, storeId={}, ownerUserId={}",
                        storeId, resolvedOwnerId);
            }
        }

        Set<String> pushedCids = new LinkedHashSet<>();
        for (InfoUser recipient : recipients.values()) {
            sendToRecipient(recipient, titleKey, contentKey, body, orderNo, pushedCids);
        }
    }

    private StoreRoute resolveRoute(Long storeId, Long fallbackOwnerId) {
        if (storeId == null) {
            log.warn("Store notification missing storeId, fallbackOwnerId={}", fallbackOwnerId);
            return new StoreRoute(fallbackOwnerId, false);
        }
        PosStore store = posStoreMapper.selectPosStoreById(storeId);
        if (store == null || !MerchantAccountConstants.NOT_DELETED.equals(store.getDelFlag())) {
            log.warn("Store notification cannot resolve active store, storeId={}, fallbackOwnerId={}",
                    storeId, fallbackOwnerId);
            return new StoreRoute(fallbackOwnerId, false);
        }
        if (fallbackOwnerId != null && !fallbackOwnerId.equals(store.getUserId())) {
            log.warn("Store notification owner mismatch, storeId={}, requestedOwnerId={}, actualOwnerId={}",
                    storeId, fallbackOwnerId, store.getUserId());
        }
        return new StoreRoute(store.getUserId(), true);
    }

    private boolean isSubaccountRecipient(InfoUser candidate) {
        return candidate != null
                && candidate.getUserId() != null
                && MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(candidate.getUserType())
                && MerchantAccountConstants.STATUS_ENABLED.equals(candidate.getStatus())
                && MerchantAccountConstants.STATUS_ENABLED.equals(candidate.getSubaccountStatus())
                && MerchantAccountConstants.NOT_DELETED.equals(candidate.getDelFlag())
                && StringUtils.isNotEmpty(candidate.getCid())
                && tokenSessionService.hasAppSession(candidate.getUserId());
    }

    private boolean isActiveOwner(InfoUser owner) {
        return owner != null
                && (MerchantAccountConstants.OWNER_USER_TYPE.equals(owner.getUserType())
                || "3".equals(owner.getUserType()) || "4".equals(owner.getUserType()))
                && MerchantAccountConstants.STATUS_ENABLED.equals(owner.getStatus())
                && MerchantAccountConstants.NOT_DELETED.equals(owner.getDelFlag());
    }

    private void sendToRecipient(InfoUser recipient, String titleKey, String contentKey,
                                 String body, String orderNo, Set<String> pushedCids) {
        String title = localize(titleKey, recipient.getUserId());
        String content = localize(contentKey, recipient.getUserId());
        if (StringUtils.isNotEmpty(orderNo)) {
            content += ",NO:" + orderNo;
        }
        try {
            publishMessage(recipient.getUserId(), title, content, body);
        } catch (Exception exception) {
            log.error("Store notification message publish failed, userId={}", recipient.getUserId(), exception);
        }

        String cid = recipient.getCid();
        if (StringUtils.isEmpty(cid) || !pushedCids.add(cid)) {
            return;
        }
        try {
            sendExternal(cid, title, content, body);
        } catch (Exception exception) {
            log.error("Store notification external push failed, userId={}", recipient.getUserId(), exception);
        }
    }

    String localize(String messageKey, Long userId) {
        Locale locale = LocaleUtils.getUserLocale(userId);
        return MessageUtils.message(messageKey, locale);
    }

    void publishMessage(Long userId, String title, String content, String body) {
        pushEventService.PublisherEvent(userId, title, content, body);
    }

    void sendExternal(String cid, String title, String content, String body) {
        new PayPush().shpush(cid, title, content, body);
    }

    private record StoreRoute(Long ownerUserId, boolean storeValid) {
    }
}
