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.memory.service.ZiniaoMemoryStoreService;
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopIndexEntryDto;
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopIndexRefreshCursorDto;
import com.nanri.aiimage.modules.ziniao.model.cache.ZiniaoShopCacheDto;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoStaffItemVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

@Service
@RequiredArgsConstructor
@Slf4j
public class ZiniaoShopIndexService {

    public static final String STATUS_ACTIVE = "ACTIVE";
    public static final String STATUS_STALE = "STALE";
    public static final String STATUS_CONFLICT = "CONFLICT";
    public static final String STATUS_PENDING = "PENDING";
    public static final String MATCH_STATUS_MATCHED = "MATCHED";
    public static final String MATCH_STATUS_PENDING = "PENDING";
    public static final String MATCH_STATUS_CONFLICT = "CONFLICT";
    public static final String MATCH_STATUS_STALE = "INDEX_STALE";

    private static final String CACHE_TYPE_SHOP_INDEX_ENTRY = "SHOP_INDEX_ENTRY";
    private static final String CACHE_TYPE_SHOP_INDEX_SCOPE_SNAPSHOT = "SHOP_INDEX_SCOPE_SNAPSHOT";
    private static final String CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR = "SHOP_INDEX_REFRESH_CURSOR";
    private static final Duration DEFAULT_ENTRY_TTL = Duration.ofHours(12);
    private static final Duration DEFAULT_CURSOR_TTL = Duration.ofHours(24);
    private static final int DEFAULT_REFRESH_BATCH_SIZE = 100;
    private static final int DEFAULT_LIST_BY_TYPE_LIMIT = 5000;

    private final ZiniaoMemoryStoreService ziniaoMemoryStoreService;
    private final ZiniaoApiKeyProvider ziniaoApiKeyProvider;
    private final ZiniaoAuthService ziniaoAuthService;
    private final ZiniaoProperties ziniaoProperties;

    public ZiniaoShopIndexRefreshCursorDto getRefreshCursor() {
        ZiniaoShopIndexRefreshCursorDto cursor = ziniaoMemoryStoreService
                .get(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", ZiniaoShopIndexRefreshCursorDto.class)
                .orElse(null);
        if (cursor == null) {
            cursor = new ZiniaoShopIndexRefreshCursorDto();
            cursor.setScopeKey("global");
            cursor.setStatus("IDLE");
            cursor.setMessage("索引刷新任务尚未执行");
        }
        return cursor;
    }

    public ZiniaoShopMatchResultVo findIndexedStoreByName(String targetShopName) {
        String normalizedShopName = normalizeShopName(targetShopName);
        if (normalizedShopName.isBlank()) {
            return pendingResult("店铺名为空，无法匹配索引");
        }

        ZiniaoShopIndexEntryDto entry = ziniaoMemoryStoreService
                .get(CACHE_TYPE_SHOP_INDEX_ENTRY, normalizedShopName, ZiniaoShopIndexEntryDto.class)
                .orElse(null);
        if (entry == null) {
            return pendingResult("店铺索引未命中，请等待后台刷新");
        }
        if (STATUS_CONFLICT.equals(entry.getStatus())) {
            return conflictResult(entry.getMessage() == null || entry.getMessage().isBlank()
                    ? "存在多个同名店铺，请人工确认"
                    : entry.getMessage());
        }
        if (!STATUS_ACTIVE.equals(entry.getStatus())) {
            return staleOrPendingResult(entry.getMessage() == null || entry.getMessage().isBlank()
                    ? "店铺索引暂不可用，请等待后台刷新"
                    : entry.getMessage(), entry);
        }
        if (entry.getShopId() == null || entry.getShopId().isBlank() || entry.getMatchedUserId() == null || entry.getMatchedUserId() <= 0) {
            return pendingResult("店铺索引信息不完整，请等待后台刷新");
        }

        String apiKey = findApiKeyByHash(entry.getApiKeyHash());
        if (apiKey == null) {
            return pendingResult("店铺索引所依赖的紫鸟 key 已失效，请等待后台刷新");
        }

        Long companyId = entry.getCompanyId();
        if (companyId == null || companyId <= 0) {
            return pendingResult("店铺索引缺少 companyId，请等待后台刷新");
        }

        String openStoreUrl = ziniaoAuthService.buildOpenStoreUrlByScope(apiKey, companyId, entry.getMatchedUserId(), entry.getShopId());
        ZiniaoShopMatchResultVo vo = new ZiniaoShopMatchResultVo();
        vo.setMatched(true);
        vo.setShopId(entry.getShopId());
        vo.setShopName(entry.getShopName());
        vo.setPlatform(entry.getPlatform());
        vo.setMatchedUserId(entry.getMatchedUserId());
        vo.setOpenStoreUrl(openStoreUrl);
        vo.setMatchStatus(isFresh(entry) ? MATCH_STATUS_MATCHED : MATCH_STATUS_STALE);
        vo.setMatchMessage(isFresh(entry) ? null : "店铺索引已过保鲜期，请等待后台刷新");
        return vo;
    }

    public void refreshShopIndex() {
        long now = Instant.now().toEpochMilli();
        ZiniaoShopIndexRefreshCursorDto cursor = new ZiniaoShopIndexRefreshCursorDto();
        cursor.setScopeKey("global");
        cursor.setStatus("RUNNING");
        cursor.setLastStartedAt(now);
        ziniaoMemoryStoreService.put(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", cursor, DEFAULT_CURSOR_TTL);

        Map<String, List<ZiniaoShopIndexEntryDto>> grouped = new LinkedHashMap<>();
        Set<Long> allInvalidUserIds = new LinkedHashSet<>();
        int refreshBatchSize = resolveRefreshBatchSize();
        try {
            List<String> apiKeys = ziniaoApiKeyProvider.listApiKeys();
            if (refreshBatchSize > 0 && apiKeys.size() > refreshBatchSize) {
                apiKeys = apiKeys.subList(0, refreshBatchSize);
            }
            for (String apiKey : apiKeys) {
                Long companyId;
                try {
                    companyId = ziniaoAuthService.resolveCompanyIdForIndex(apiKey);
                } catch (BusinessException ex) {
                    log.warn("[ziniao-index] skip apiKey while resolving companyId, msg={}", ex.getMessage());
                    continue;
                }

                Set<Long> invalidUserIds = new LinkedHashSet<>();
                List<ZiniaoStaffItemVo> staff = ziniaoAuthService.getOrLoadStaffForIndex(apiKey, companyId);
                for (Long userId : buildUserIds(staff)) {
                    if (invalidUserIds.contains(userId)) {
                        continue;
                    }
                    List<ZiniaoShopCacheDto> stores;
                    try {
                        stores = ziniaoAuthService.getOrLoadUserStoresForIndex(apiKey, companyId, userId);
                    } catch (BusinessException ex) {
                        if (ziniaoAuthService.isInvalidUserStoresError(ex)) {
                            invalidUserIds.add(userId);
                            allInvalidUserIds.add(userId);
                            ziniaoAuthService.evictInvalidUserForIndex(apiKey, companyId, userId);
                        }
                        log.warn("[ziniao-index] skip user stores, companyId={}, userId={}, msg={}", companyId, userId, ex.getMessage());
                        continue;
                    }
                    ziniaoMemoryStoreService.put(
                            CACHE_TYPE_SHOP_INDEX_SCOPE_SNAPSHOT,
                            buildScopeSnapshotKey(apiKey, companyId, userId),
                            stores,
                            resolveEntryTtl()
                    );
                    for (ZiniaoShopCacheDto store : stores) {
                        String normalizedShopName = normalizeShopName(store == null ? null : store.getShopName());
                        if (normalizedShopName.isBlank()) {
                            continue;
                        }
                        ZiniaoShopIndexEntryDto entry = new ZiniaoShopIndexEntryDto();
                        entry.setNormalizedShopName(normalizedShopName);
                        entry.setShopId(store.getShopId());
                        entry.setShopName(store.getShopName());
                        entry.setPlatform(store.getPlatform());
                        entry.setMatchedUserId(userId);
                        entry.setCompanyId(companyId);
                        entry.setApiKeyHash(buildApiKeyHash(apiKey));
                        entry.setStatus(STATUS_ACTIVE);
                        entry.setLastSeenAt(now);
                        entry.setLastRefreshedAt(now);
                        List<ZiniaoShopIndexEntryDto> candidates = grouped.computeIfAbsent(normalizedShopName, ignored -> new ArrayList<>());
                        if (candidates.stream().noneMatch(existing -> sameCandidate(existing, entry))) {
                            candidates.add(entry);
                        }
                    }
                }
                if (!invalidUserIds.isEmpty()) {
                    for (Long invalidUserId : invalidUserIds) {
                        ziniaoMemoryStoreService.delete(
                                CACHE_TYPE_SHOP_INDEX_SCOPE_SNAPSHOT,
                                buildScopeSnapshotKey(apiKey, companyId, invalidUserId)
                        );
                    }
                }
            }

            for (Map.Entry<String, List<ZiniaoShopIndexEntryDto>> groupedEntry : grouped.entrySet()) {
                String normalizedShopName = groupedEntry.getKey();
                List<ZiniaoShopIndexEntryDto> candidates = groupedEntry.getValue();
                ZiniaoShopIndexEntryDto entryToStore = candidates.get(0);
                if (candidates.size() > 1) {
                    entryToStore = buildConflictEntry(normalizedShopName, candidates, now);
                }
                ziniaoMemoryStoreService.put(CACHE_TYPE_SHOP_INDEX_ENTRY, normalizedShopName, entryToStore, resolveEntryTtl());
            }
            markMissingEntriesAsStale(grouped.keySet(), now);

            cursor.setStatus("SUCCESS");
            cursor.setMessage(allInvalidUserIds.isEmpty()
                    ? null
                    : "本轮刷新已跳过无效 userId 数: " + allInvalidUserIds.size());
            cursor.setInvalidUserCount(allInvalidUserIds.size());
            cursor.setSampleInvalidUserIds(allInvalidUserIds.stream().limit(20).toList());
            cursor.setLastFinishedAt(now);
            cursor.setLastSuccessAt(now);
            ziniaoMemoryStoreService.put(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", cursor, DEFAULT_CURSOR_TTL);
        } catch (Exception ex) {
            cursor.setStatus("FAILED");
            cursor.setMessage(ex.getMessage());
            cursor.setLastFinishedAt(now);
            ziniaoMemoryStoreService.put(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global", cursor, DEFAULT_CURSOR_TTL);
            if (ex instanceof BusinessException businessException) {
                throw businessException;
            }
            throw new BusinessException("刷新紫鸟店铺索引失败");
        }
    }

    public void invalidateIndex() {
        ziniaoMemoryStoreService.delete(CACHE_TYPE_SHOP_INDEX_REFRESH_CURSOR, "global");
    }

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

    private ZiniaoShopMatchResultVo pendingResult(String message) {
        ZiniaoShopMatchResultVo vo = new ZiniaoShopMatchResultVo();
        vo.setMatched(false);
        vo.setMatchStatus(MATCH_STATUS_PENDING);
        vo.setMatchMessage(message);
        return vo;
    }

    private ZiniaoShopMatchResultVo conflictResult(String message) {
        ZiniaoShopMatchResultVo vo = new ZiniaoShopMatchResultVo();
        vo.setMatched(false);
        vo.setMatchStatus(MATCH_STATUS_CONFLICT);
        vo.setMatchMessage(message);
        return vo;
    }

    private ZiniaoShopIndexEntryDto buildConflictEntry(String normalizedShopName, List<ZiniaoShopIndexEntryDto> candidates, long now) {
        ZiniaoShopIndexEntryDto representative = candidates == null || candidates.isEmpty() ? null : candidates.get(0);
        ZiniaoShopIndexEntryDto entry = new ZiniaoShopIndexEntryDto();
        entry.setNormalizedShopName(normalizedShopName);
        entry.setStatus(STATUS_CONFLICT);
        entry.setShopName(representative == null ? normalizedShopName : representative.getShopName());
        entry.setCompanyId(representative == null ? null : representative.getCompanyId());
        entry.setApiKeyHash(representative == null ? null : representative.getApiKeyHash());
        entry.setCandidateCount(candidates == null ? 0 : candidates.size());
        entry.setMessage("存在多个同名店铺，请人工确认（候选数: " + entry.getCandidateCount() + "）");
        entry.setLastSeenAt(now);
        entry.setLastRefreshedAt(now);
        if (candidates != null) {
            for (ZiniaoShopIndexEntryDto candidate : candidates) {
                if (candidate == null) {
                    continue;
                }
                if (candidate.getShopId() != null && !candidate.getShopId().isBlank() && !entry.getSampleShopIds().contains(candidate.getShopId())) {
                    entry.getSampleShopIds().add(candidate.getShopId());
                }
                if (candidate.getMatchedUserId() != null && candidate.getMatchedUserId() > 0 && !entry.getSampleUserIds().contains(candidate.getMatchedUserId())) {
                    entry.getSampleUserIds().add(candidate.getMatchedUserId());
                }
            }
        }
        return entry;
    }

    private ZiniaoShopMatchResultVo staleOrPendingResult(String defaultMessage, ZiniaoShopIndexEntryDto entry) {
        if (entry != null && STATUS_STALE.equals(entry.getStatus())) {
            ZiniaoShopMatchResultVo vo = new ZiniaoShopMatchResultVo();
            vo.setMatched(false);
            vo.setMatchStatus(MATCH_STATUS_STALE);
            vo.setMatchMessage(defaultMessage);
            return vo;
        }
        return pendingResult(defaultMessage);
    }

    private void markMissingEntriesAsStale(Set<String> activeNames, long now) {
        List<ZiniaoShopIndexEntryDto> existingEntries = ziniaoMemoryStoreService.listByType(
                CACHE_TYPE_SHOP_INDEX_ENTRY,
                ZiniaoShopIndexEntryDto.class,
                DEFAULT_LIST_BY_TYPE_LIMIT
        );
        if (existingEntries.isEmpty()) {
            return;
        }
        for (ZiniaoShopIndexEntryDto existingEntry : existingEntries) {
            if (existingEntry == null || existingEntry.getNormalizedShopName() == null || existingEntry.getNormalizedShopName().isBlank()) {
                continue;
            }
            if (activeNames.contains(existingEntry.getNormalizedShopName())) {
                continue;
            }
            if (STATUS_CONFLICT.equals(existingEntry.getStatus())
                    && (existingEntry.getCandidateCount() == null || existingEntry.getCandidateCount() <= 0)
                    && (existingEntry.getShopName() == null || existingEntry.getShopName().isBlank())
                    && (existingEntry.getApiKeyHash() == null || existingEntry.getApiKeyHash().isBlank())
                    && (existingEntry.getCompanyId() == null || existingEntry.getCompanyId() <= 0)) {
                ziniaoMemoryStoreService.delete(CACHE_TYPE_SHOP_INDEX_ENTRY, existingEntry.getNormalizedShopName());
                continue;
            }
            existingEntry.setStatus(STATUS_STALE);
            existingEntry.setMessage("店铺索引已过期，请等待后台刷新");
            existingEntry.setLastRefreshedAt(now);
            ziniaoMemoryStoreService.put(
                    CACHE_TYPE_SHOP_INDEX_ENTRY,
                    existingEntry.getNormalizedShopName(),
                    existingEntry,
                    resolveEntryTtl()
            );
        }
    }

    private boolean isFresh(ZiniaoShopIndexEntryDto entry) {
        if (entry == null || entry.getLastRefreshedAt() == null || entry.getLastRefreshedAt() <= 0) {
            return false;
        }
        Integer freshMinutes = ziniaoProperties.getShopIndexFreshMinutes();
        if (freshMinutes == null || freshMinutes <= 0) {
            return true;
        }
        long freshWindowMillis = Duration.ofMinutes(freshMinutes).toMillis();
        return Instant.now().toEpochMilli() - entry.getLastRefreshedAt() <= freshWindowMillis;
    }

    private Duration resolveEntryTtl() {
        Integer ttlHours = ziniaoProperties.getShopIndexEntryTtlHours();
        if (ttlHours == null || ttlHours <= 0) {
            return DEFAULT_ENTRY_TTL;
        }
        return Duration.ofHours(ttlHours);
    }

    private int resolveRefreshBatchSize() {
        Integer batchSize = ziniaoProperties.getShopIndexRefreshBatchSize();
        if (batchSize == null || batchSize <= 0) {
            return DEFAULT_REFRESH_BATCH_SIZE;
        }
        return batchSize;
    }

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

    private boolean sameCandidate(ZiniaoShopIndexEntryDto left, ZiniaoShopIndexEntryDto right) {
        if (left == null || right == null) {
            return false;
        }
        if (left.getShopId() != null && right.getShopId() != null) {
            return Objects.equals(left.getShopId(), right.getShopId())
                    && Objects.equals(left.getMatchedUserId(), right.getMatchedUserId())
                    && Objects.equals(left.getCompanyId(), right.getCompanyId())
                    && Objects.equals(left.getApiKeyHash(), right.getApiKeyHash());
        }
        return Objects.equals(left.getShopName(), right.getShopName())
                && Objects.equals(left.getMatchedUserId(), right.getMatchedUserId())
                && Objects.equals(left.getCompanyId(), right.getCompanyId())
                && Objects.equals(left.getApiKeyHash(), right.getApiKeyHash());
    }

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

    private String findApiKeyByHash(String apiKeyHash) {
        if (apiKeyHash == null || apiKeyHash.isBlank()) {
            return null;
        }
        for (String apiKey : ziniaoApiKeyProvider.listApiKeys()) {
            if (apiKeyHash.equals(buildApiKeyHash(apiKey))) {
                return apiKey;
            }
        }
        return null;
    }

    private String buildApiKeyHash(String apiKey) {
        try {
            MessageDigest digest = 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 缓存键失败");
        }
    }
}
