package com.nanri.aiimage.modules.ziniao.service;

import com.nanri.aiimage.common.exception.BusinessException;
import com.nanri.aiimage.config.ZiniaoProperties;
import com.nanri.aiimage.modules.ziniao.client.ZiniaoClient;
import com.nanri.aiimage.modules.ziniao.memory.service.ZiniaoMemoryStoreService;
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoSessionCacheDto;
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopCacheDto;
import com.nanri.aiimage.modules.ziniao.service.ZiniaoApiKeyProvider;
import com.nanri.aiimage.modules.ziniao.model.dto.ZiniaoOpenShopRequest;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoOpenShopVo;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoSessionVo;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopItemVo;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopListVo;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoStaffItemVo;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoStaffListVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Slf4j
public class ZiniaoAuthService {

    private static final String CACHE_TYPE_COMPANY_ID = "COMPANY_ID";
    private static final String CACHE_TYPE_STAFF_LIST = "STAFF_LIST";
    private static final String CACHE_TYPE_USER_STORES = "USER_STORES";
    private static final String CACHE_TYPE_SHOP_MATCH = "SHOP_MATCH";
    private static final Duration COMPANY_ID_CACHE_TTL = Duration.ofHours(12);
    private static final Duration STAFF_LIST_CACHE_TTL = Duration.ofMinutes(30);
    private static final Duration USER_STORES_CACHE_TTL = Duration.ofMinutes(30);
    private static final Duration SHOP_MATCH_CACHE_TTL = Duration.ofMinutes(30);

    public StoreMatchResult matchStoreByNameAcrossStaff(String targetShopName, Long preferUserId) {
        ensureEnabled();
        String normalizedTarget = normalizeShopName(targetShopName);
        if (normalizedTarget.isBlank()) {
            return new StoreMatchResult(false, null, null, null, null, null);
        }

        List<String> apiKeys = getScanApiKeys();
        long startedAt = System.currentTimeMillis();
        int scannedKeys = 0;
        for (String apiKey : apiKeys) {
            if (isScanLimitExceeded(startedAt, scannedKeys)) {
                throw new BusinessException("紫鸟 key 轮询超出扫描限制，请缩小范围或提高扫描上限");
            }
            scannedKeys++;

            Long companyId;
            try {
                companyId = resolveCompanyId(apiKey);
            } catch (BusinessException ex) {
                // key 无效/不可用时继续尝试下一个 key
                if (isInvalidApiKeyError(ex)) {
                    log.info("[ziniao-match] skip invalid apiKey while resolving companyId, keyHash={}", shortKeyHash(apiKey));
                    continue;
                }
                throw ex;
            }

            StoreMatchResult cachedMatch = getCachedShopMatch(apiKey, companyId, normalizedTarget);
            if (cachedMatch != null) {
                log.info("[ziniao-match] hit shop-match cache, keyHash={}, companyId={}, shopName={}, shopId={}, userId={}",
                        shortKeyHash(apiKey), companyId, normalizedTarget, cachedMatch.shopId(), cachedMatch.matchedUserId());
                String openStoreUrl = buildOpenStoreUrl(cachedMatch.shopId(), cachedMatch.matchedUserId(),
                        ziniaoClient.getUserLoginToken(apiKey, companyId, cachedMatch.matchedUserId()));
                return new StoreMatchResult(true, cachedMatch.shopId(), cachedMatch.shopName(), cachedMatch.platform(), cachedMatch.matchedUserId(), openStoreUrl);
            }

            List<ZiniaoStaffItemVo> staff = getOrLoadStaff(apiKey, companyId);
            List<Long> userIds = buildUserIds(staff, preferUserId);
            for (Long staffUserId : userIds) {
                if (staffUserId == null || staffUserId <= 0) {
                    continue;
                }
                List<ZiniaoShopCacheDto> stores;
                try {
                    stores = getOrLoadUserStores(apiKey, companyId, staffUserId);
                } catch (BusinessException ex) {
                    if (isSkippableUserStoresError(ex)) {
                        continue;
                    }
                    throw ex;
                }

                for (ZiniaoShopCacheDto store : stores) {
                    String storeName = normalizeShopName(store == null ? null : store.getShopName());
                    if (!storeName.isBlank() && storeName.equals(normalizedTarget)) {
                        log.info("[ziniao-match] matched via upstream scan, keyHash={}, companyId={}, shopName={}, shopId={}, userId={}",
                                shortKeyHash(apiKey), companyId, normalizedTarget, store.getShopId(), staffUserId);
                        cacheShopMatch(apiKey, companyId, normalizedTarget,
                                new StoreMatchResult(true, store.getShopId(), store.getShopName(), store.getPlatform(), staffUserId, null),
                                SHOP_MATCH_CACHE_TTL);
                        String openStoreUrl = buildOpenStoreUrl(store.getShopId(), staffUserId,
                                ziniaoClient.getUserLoginToken(apiKey, companyId, staffUserId));
                        return new StoreMatchResult(true, store.getShopId(), store.getShopName(), store.getPlatform(), staffUserId, openStoreUrl);
                    }
                }
            }
        }

        return new StoreMatchResult(false, null, null, null, null, null);
    }

    public String buildOpenStoreUrlByScope(String apiKey, Long companyId, Long userId, String shopId) {
        ensureEnabled();
        if (apiKey == null || apiKey.isBlank()) {
            throw new BusinessException("紫鸟 apiKey 未配置");
        }
        if (companyId == null || companyId <= 0) {
            throw new BusinessException("紫鸟 companyId 不合法");
        }
        if (userId == null || userId <= 0) {
            throw new BusinessException("紫鸟 userId 不合法");
        }
        if (shopId == null || shopId.isBlank()) {
            throw new BusinessException("店铺不存在");
        }
        String loginToken = ziniaoClient.getUserLoginToken(apiKey, companyId, userId);
        return buildOpenStoreUrl(shopId, userId, loginToken);
    }

    public Long resolveCompanyIdForIndex(String apiKey) {
        return resolveCompanyId(apiKey);
    }

    public List<ZiniaoStaffItemVo> getOrLoadStaffForIndex(String apiKey, Long companyId) {
        return getOrLoadStaff(apiKey, companyId);
    }

    public List<ZiniaoShopCacheDto> getOrLoadUserStoresForIndex(String apiKey, Long companyId, Long userId) {
        return getOrLoadUserStores(apiKey, companyId, userId);
    }

    public boolean isInvalidUserStoresError(BusinessException ex) {
        String message = ex == null ? null : ex.getMessage();
        if (message == null || message.isBlank()) {
            return false;
        }
        return message.contains("code=40004")
                || message.contains("userId存在无效的参数值");
    }

    public void evictInvalidUserForIndex(String apiKey, Long companyId, Long userId) {
        if (apiKey == null || apiKey.isBlank() || companyId == null || companyId <= 0 || userId == null || userId <= 0) {
            return;
        }
        String staffCacheKey = buildApiKeyHash(apiKey) + ":" + companyId;
        ziniaoMemoryStoreService.getList(CACHE_TYPE_STAFF_LIST, staffCacheKey, ZiniaoStaffItemVo.class)
                .ifPresent(staff -> {
                    List<ZiniaoStaffItemVo> filtered = staff.stream()
                            .filter(item -> item != null && !userId.equals(item.getUserId()))
                            .toList();
                    if (filtered.isEmpty()) {
                        ziniaoMemoryStoreService.delete(CACHE_TYPE_STAFF_LIST, staffCacheKey);
                    } else {
                        ziniaoMemoryStoreService.put(CACHE_TYPE_STAFF_LIST, staffCacheKey, filtered, STAFF_LIST_CACHE_TTL);
                    }
                });
        ziniaoMemoryStoreService.delete(CACHE_TYPE_USER_STORES, buildApiKeyHash(apiKey) + ":" + companyId + ":" + userId);
    }

    private boolean isSkippableUserStoresError(BusinessException ex) {
        String message = ex == null ? null : ex.getMessage();
        if (message == null || message.isBlank()) {
            return false;
        }
        return message.contains("code=40004")
                || message.contains("userId存在无效的参数值")
                || message.contains("无权限")
                || message.contains("没有权限");
    }

    private String normalizeShopName(String value) {
        if (value == null) {
            return "";
        }
        return value.replace("\u3000", " ")
                .trim();
    }

    public record StoreMatchResult(boolean matched, String shopId, String shopName, String platform, Long matchedUserId,
                                   String openStoreUrl) {
    }

    private final ZiniaoProperties ziniaoProperties;
    private final ZiniaoClient ziniaoClient;
    private final ZiniaoSessionCacheService ziniaoSessionCacheService;
    private final ZiniaoMemoryStoreService ziniaoMemoryStoreService;
    private final ZiniaoApiKeyProvider ziniaoApiKeyProvider;

    public ZiniaoSessionVo getSession(String sessionId, Long userId) {
        ensureEnabled();
        ZiniaoSessionCacheDto session = sessionId == null || sessionId.isBlank() ? initSession(userId) : requireOrInitSession(sessionId, userId);
        ZiniaoSessionVo vo = new ZiniaoSessionVo();
        vo.setSessionId(session.getSessionId());
        vo.setEnabled(ziniaoProperties.isEnabled());
        vo.setAuthenticated(Boolean.TRUE);
        vo.setCompanyId(session.getCompanyId());
        vo.setCurrentUserId(session.getCurrentUserId());
        vo.setZiniaoUserId(session.getZiniaoUserId());
        vo.setNickname(session.getNickname());
        vo.setAppToken(null);
        vo.setTokenType(null);
        vo.setExpireAt(session.getExpireAt());
        vo.setShopCount(ziniaoSessionCacheService.getShops(session.getSessionId()).size());
        vo.setCurrentShopId(session.getDefaultShopId());
        vo.setCurrentShopName(resolveCurrentShopName(session.getSessionId(), session.getDefaultShopId()));
        return vo;
    }

    public ZiniaoStaffListVo listStaff() {
        ensureEnabled();
        ZiniaoStaffListVo vo = new ZiniaoStaffListVo();
        String apiKey = resolveAvailableApiKey();
        Long companyId = resolveCompanyIdForStaff(apiKey);
        vo.getItems().addAll(ziniaoClient.listStaff(apiKey, companyId));
        return vo;
    }

    public ZiniaoShopListVo listShops(String sessionId, Long userId) {
        ZiniaoSessionCacheDto session = requireOrInitSession(sessionId, userId);
        Long currentUserId = requireUserId(session.getCurrentUserId());
        String apiKey = resolveSessionApiKey(session);
        Long companyId = resolveCompanyId(apiKey);
        List<ZiniaoShopCacheDto> shops = ziniaoClient.listUserStores(apiKey, companyId, currentUserId);
        ziniaoSessionCacheService.saveShops(session.getSessionId(), shops);
        if (!shops.isEmpty() && (session.getDefaultShopId() == null || session.getDefaultShopId().isBlank())) {
            session.setDefaultShopId(shops.get(0).getShopId());
            ziniaoSessionCacheService.saveSession(session);
        }
        ZiniaoShopListVo vo = new ZiniaoShopListVo();
        for (ZiniaoShopCacheDto shop : shops) {
            ZiniaoShopItemVo item = new ZiniaoShopItemVo();
            item.setShopId(shop.getShopId());
            item.setShopName(shop.getShopName());
            item.setPlatform(shop.getPlatform());
            item.setSelected(shop.getShopId() != null && shop.getShopId().equals(session.getDefaultShopId()));
            vo.getItems().add(item);
        }
        return vo;
    }

    public ZiniaoOpenShopVo openShop(ZiniaoOpenShopRequest request) {
        ZiniaoSessionCacheDto session = requireOrInitSession(request.getSessionId(), request.getUserId());
        Long currentUserId = requireUserId(request.getUserId() != null ? request.getUserId() : session.getCurrentUserId());
        String apiKey = resolveSessionApiKey(session);
        Long companyId = resolveCompanyId(apiKey);
        String userToken = ziniaoClient.getUserLoginToken(apiKey, companyId, currentUserId);
        List<ZiniaoShopCacheDto> shops = ziniaoSessionCacheService.getShops(session.getSessionId());
        if (shops.isEmpty()) {
            shops = ziniaoClient.listUserStores(apiKey, companyId, currentUserId);
            ziniaoSessionCacheService.saveShops(session.getSessionId(), shops);
        }
        ZiniaoShopCacheDto targetShop = shops.stream()
                .filter(item -> item.getShopId() != null && item.getShopId().equals(request.getShopId()))
                .findFirst()
                .orElseThrow(() -> new BusinessException("店铺不存在或不属于当前员工"));
        session.setCurrentUserId(currentUserId);
        session.setDefaultShopId(targetShop.getShopId());
        ziniaoSessionCacheService.saveSession(session);
        ziniaoSessionCacheService.saveCurrentShop(session.getSessionId(), targetShop);

        ZiniaoOpenShopVo vo = new ZiniaoOpenShopVo();
        vo.setUserId(currentUserId);
        vo.setShopId(targetShop.getShopId());
        vo.setShopName(targetShop.getShopName());
        vo.setLoginToken(userToken);
        vo.setOpenStoreUrl(buildOpenStoreUrl(targetShop.getShopId(), currentUserId, userToken));
        return vo;
    }

    public List<ZiniaoShopCacheDto> listShopsForConfiguredUser() {
        ensureEnabled();
        Long userId = parseConfiguredOpenStoreUserId();
        String apiKey = resolveAvailableApiKey();
        return ziniaoClient.listUserStores(apiKey, resolveCompanyId(apiKey), userId);
    }

    public String buildOpenStoreUrlForShop(ZiniaoShopCacheDto shop) {
        ensureEnabled();
        if (shop == null || shop.getShopId() == null || shop.getShopId().isBlank()) {
            throw new BusinessException("店铺不存在");
        }
        Long userId = parseConfiguredOpenStoreUserId();
        String apiKey = resolveAvailableApiKey();
        String loginToken = ziniaoClient.getUserLoginToken(apiKey, resolveCompanyId(apiKey), userId);
        return buildOpenStoreUrl(shop.getShopId(), userId, loginToken);
    }

    private ZiniaoSessionCacheDto initSession(Long userId) {
        String sessionId = "ziniao_" + UUID.randomUUID().toString().replace("-", "");
        ZiniaoSessionCacheDto session = new ZiniaoSessionCacheDto();
        session.setSessionId(sessionId);
        session.setExpireAt(Instant.now().plusSeconds(ziniaoProperties.getSessionTtlHours() * 3600).toEpochMilli());
        String apiKey = resolveAvailableApiKey();
        session.setApiKey(apiKey);
        session.setCompanyId(resolveCompanyId(apiKey));
        session.setCurrentUserId(userId);
        session.setZiniaoUserId(String.valueOf(userId == null ? 0L : userId));
        session.setNickname("API Key模式");
        ziniaoSessionCacheService.saveSession(session);
        return session;
    }

    private ZiniaoSessionCacheDto requireOrInitSession(String sessionId, Long userId) {
        if (sessionId == null || sessionId.isBlank()) {
            return initSession(userId);
        }
        ZiniaoSessionCacheDto session = ziniaoSessionCacheService.getSession(sessionId);
        if (session == null) {
            return initSession(userId);
        }
        if (userId != null && userId > 0) {
            session.setCurrentUserId(userId);
            session.setZiniaoUserId(String.valueOf(userId));
        }
        // 旧 session 可能没有保存 apiKey，补齐后续请求所需
        if (session.getApiKey() == null || session.getApiKey().isBlank()) {
            session.setApiKey(resolveAvailableApiKey());
        }
        ziniaoSessionCacheService.saveSession(session);
        return session;
    }

    private String resolveCurrentShopName(String sessionId, String currentShopId) {
        if (currentShopId == null || currentShopId.isBlank()) {
            return null;
        }
        return ziniaoSessionCacheService.getShops(sessionId).stream()
                .filter(item -> currentShopId.equals(item.getShopId()))
                .map(ZiniaoShopCacheDto::getShopName)
                .findFirst()
                .orElse(null);
    }

    private String buildOpenStoreUrl(String storeId, Long userId, String loginToken) {
        String scheme = requireText(ziniaoProperties.getOpenStoreScheme(), "紫鸟 open-store-scheme 未配置");
        String launchUrl = requireText(ziniaoProperties.getOpenStoreLaunchUrl(), "紫鸟 open-store-launch-url 未配置");
        String encodedLaunchUrl = Base64.getEncoder().encodeToString(launchUrl.getBytes(StandardCharsets.UTF_8));
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(scheme)
                .queryParam("storeId", storeId)
                .queryParam("openapiToken", loginToken)
                .queryParam("userId", userId)
                .queryParam("lanuchUrl", encodedLaunchUrl)
                .queryParam("autoopen", Boolean.TRUE.equals(ziniaoProperties.getOpenStoreAutoOpen()))
                .queryParam("debuggPort", ziniaoProperties.getOpenStoreDebugPort())
                .queryParam("notPromptForDownload", Boolean.TRUE.equals(ziniaoProperties.getOpenStoreNotPromptForDownload()) ? 1 : 0);
        if (ziniaoProperties.getOpenStoreForceDownloadPath() != null && !ziniaoProperties.getOpenStoreForceDownloadPath().isBlank()) {
            String encodedPath = Base64.getEncoder().encodeToString(ziniaoProperties.getOpenStoreForceDownloadPath().getBytes(StandardCharsets.UTF_8));
            builder.queryParam("forceDownloadPath", encodedPath);
        }
        if (ziniaoProperties.getOpenStoreExtraArgs() != null && !ziniaoProperties.getOpenStoreExtraArgs().isBlank()) {
            builder.queryParam("options_argument", ziniaoProperties.getOpenStoreExtraArgs());
        }
        return builder.build().encode().toUriString();
    }

    private void ensureEnabled() {
        if (!ziniaoProperties.isEnabled()) {
            throw new BusinessException("紫鸟集成未启用，请先配置环境变量");
        }
        if (!ziniaoApiKeyProvider.hasApiKey()) {
            throw new BusinessException("紫鸟 apiKey 未配置，请先在店铺密钥管理中维护可用令牌");
        }
    }

    private Long resolveCompanyIdForStaff(String apiKey) {
        return resolveCompanyId(apiKey);
    }

    private Long requireUserId(Long userId) {
        if (userId == null || userId <= 0) {
            throw new BusinessException("请先选择紫鸟员工");
        }
        return userId;
    }

    private Long resolveCompanyId(String apiKey) {
        if (ziniaoProperties.getCompanyId() != null && ziniaoProperties.getCompanyId() > 0) {
            return ziniaoProperties.getCompanyId();
        }

        String normalizedApiKey = requireText(apiKey, "紫鸟 apiKey 未配置");
        String cacheKey = buildApiKeyHash(normalizedApiKey);
        Long cached = ziniaoMemoryStoreService.get(CACHE_TYPE_COMPANY_ID, cacheKey, Long.class).orElse(null);
        if (cached != null && cached > 0) {
            log.info("[ziniao-company] hit cache, keyHash={}, companyId={}", shortKeyHash(apiKey), cached);
            return cached;
        }
        log.info("[ziniao-company] cache miss, requesting upstream, keyHash={}", shortKeyHash(apiKey));
        Long companyId = ziniaoClient.getCompanyIdByApiKey(normalizedApiKey);
        ziniaoMemoryStoreService.put(CACHE_TYPE_COMPANY_ID, cacheKey, companyId, COMPANY_ID_CACHE_TTL);
        log.info("[ziniao-company] cached upstream result, keyHash={}, companyId={}", shortKeyHash(apiKey), companyId);
        return companyId;
    }


    private List<ZiniaoStaffItemVo> getOrLoadStaff(String apiKey, Long companyId) {
        String cacheKey = buildApiKeyHash(apiKey) + ":" + companyId;
        List<ZiniaoStaffItemVo> cached = ziniaoMemoryStoreService.getList(CACHE_TYPE_STAFF_LIST, cacheKey, ZiniaoStaffItemVo.class)
                .orElse(null);
        if (cached != null) {
            log.info("[ziniao-staff] hit cache, keyHash={}, companyId={}, size={}", shortKeyHash(apiKey), companyId, cached.size());
            return cached;
        }
        log.info("[ziniao-staff] cache miss, requesting upstream, keyHash={}, companyId={}", shortKeyHash(apiKey), companyId);
        List<ZiniaoStaffItemVo> staff = ziniaoClient.listStaff(apiKey, companyId);
        ziniaoMemoryStoreService.put(CACHE_TYPE_STAFF_LIST, cacheKey, staff, STAFF_LIST_CACHE_TTL);
        log.info("[ziniao-staff] cached upstream result, keyHash={}, companyId={}, size={}", shortKeyHash(apiKey), companyId, staff.size());
        return staff;
    }

    private List<ZiniaoShopCacheDto> getOrLoadUserStores(String apiKey, Long companyId, Long userId) {
        String cacheKey = buildApiKeyHash(apiKey) + ":" + companyId + ":" + userId;
        List<ZiniaoShopCacheDto> cached = ziniaoMemoryStoreService.getList(CACHE_TYPE_USER_STORES, cacheKey, ZiniaoShopCacheDto.class)
                .orElse(null);
        if (cached != null) {
            log.info("[ziniao-stores] hit cache, keyHash={}, companyId={}, userId={}, size={}", shortKeyHash(apiKey), companyId, userId, cached.size());
            return cached;
        }
        log.info("[ziniao-stores] cache miss, requesting upstream, keyHash={}, companyId={}, userId={}", shortKeyHash(apiKey), companyId, userId);
        List<ZiniaoShopCacheDto> stores = ziniaoClient.listUserStores(apiKey, companyId, userId);
        ziniaoMemoryStoreService.put(CACHE_TYPE_USER_STORES, cacheKey, stores, USER_STORES_CACHE_TTL);
        log.info("[ziniao-stores] cached upstream result, keyHash={}, companyId={}, userId={}, size={}", shortKeyHash(apiKey), companyId, userId, stores.size());
        return stores;
    }

    private StoreMatchResult getCachedShopMatch(String apiKey, Long companyId, String normalizedShopName) {
        StoreMatchResult cached = ziniaoMemoryStoreService.get(CACHE_TYPE_SHOP_MATCH, buildShopMatchCacheKey(apiKey, companyId, normalizedShopName), StoreMatchResult.class)
                .orElse(null);
        if (cached == null || !cached.matched() || cached.shopId() == null || cached.matchedUserId() == null) {
            return null;
        }
        return cached;
    }

    private void cacheShopMatch(String apiKey, Long companyId, String normalizedShopName, StoreMatchResult result, Duration ttl) {
        ziniaoMemoryStoreService.put(CACHE_TYPE_SHOP_MATCH, buildShopMatchCacheKey(apiKey, companyId, normalizedShopName), result, ttl);
    }

    private String buildShopMatchCacheKey(String apiKey, Long companyId, String normalizedShopName) {
        return buildApiKeyHash(apiKey) + ":" + companyId + ":" + normalizedShopName;
    }

    private String buildApiKeyHash(String apiKey) {
        try {
            java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(apiKey.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : hash) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception ex) {
            throw new BusinessException("生成紫鸟 apiKey 缓存键失败");
        }
    }

    private String shortKeyHash(String apiKey) {
        String hash = buildApiKeyHash(apiKey);
        return hash.length() <= 12 ? hash : hash.substring(0, 12);
    }

    private List<Long> buildUserIds(List<ZiniaoStaffItemVo> staff, Long preferUserId) {
        List<Long> userIds = new java.util.ArrayList<>();
        if (preferUserId != null && preferUserId > 0) {
            userIds.add(preferUserId);
        }
        for (ZiniaoStaffItemVo item : staff) {
            if (item != null && item.getUserId() != null && item.getUserId() > 0 && (userIds.isEmpty() || !userIds.contains(item.getUserId()))) {
                userIds.add(item.getUserId());
            }
        }
        return userIds;
    }

    private List<String> getScanApiKeys() {
        List<String> keys = ziniaoApiKeyProvider.listApiKeys();
        if (keys.isEmpty()) {
            throw new BusinessException("紫鸟 apiKey 未配置，请先在店铺密钥管理中维护可用令牌");
        }
        Integer maxKeys = ziniaoProperties.getKeyScanMaxKeys();
        if (maxKeys == null || maxKeys <= 0 || keys.size() <= maxKeys) {
            return keys;
        }
        return keys.subList(0, maxKeys);
    }

    private String resolveAvailableApiKey() {
        BusinessException lastError = null;
        List<String> apiKeys = getScanApiKeys();
        for (String apiKey : apiKeys) {
            try {
                resolveCompanyId(apiKey);
                return apiKey;
            } catch (BusinessException ex) {
                if (isInvalidApiKeyError(ex)) {
                    lastError = ex;
                    continue;
                }
                throw ex;
            }
        }
        if (lastError != null) {
            throw lastError;
        }
        throw new BusinessException("未找到可用的紫鸟 apiKey");
    }

    private String resolveSessionApiKey(ZiniaoSessionCacheDto session) {
        if (session != null && session.getApiKey() != null && !session.getApiKey().isBlank()) {
            return session.getApiKey().trim();
        }
        return resolveAvailableApiKey();
    }

    private boolean isInvalidApiKeyError(BusinessException ex) {
        String message = ex == null ? null : ex.getMessage();
        if (message == null || message.isBlank()) {
            return false;
        }
        if (message.contains("白名单") || message.contains("IP") || message.contains("whitelist")) {
            return false;
        }
        return message.contains("isv.invalid-api-key")
                || message.contains("无效的apiKey参数")
                || message.contains("非法的参数");
    }

    private boolean isScanLimitExceeded(long startedAt, int scannedKeys) {
        Integer maxKeys = ziniaoProperties.getKeyScanMaxKeys();
        if (maxKeys != null && maxKeys > 0 && scannedKeys >= maxKeys) {
            return true;
        }
        Integer maxSeconds = ziniaoProperties.getKeyScanMaxSeconds();
        return maxSeconds != null && maxSeconds > 0
                && System.currentTimeMillis() - startedAt >= maxSeconds * 1000L;
    }

    private Long parseConfiguredOpenStoreUserId() {
        String configured = requireText(ziniaoProperties.getOpenStoreUserId(), "紫鸟 open-store-user-id 未配置");
        try {
            return Long.parseLong(configured);
        } catch (Exception ex) {
            throw new BusinessException("紫鸟 open-store-user-id 配置不合法");
        }
    }

    private String maskToken(String token) {
        if (token == null || token.isBlank()) {
            return "";
        }
        if (token.length() <= 8) {
            return token;
        }
        return token.substring(0, 4) + "****" + token.substring(token.length() - 4);
    }

    private String requireText(String value, String message) {
        if (value == null || value.isBlank() || "change-me".equalsIgnoreCase(value.trim())) {
            throw new BusinessException(message);
        }
        return value.trim();
    }
}
