const { getRedisClient } = require("./client") const SELLER_DEAL_INDEX_KEY_PREFIX = "index:seller:" const SELLER_DEAL_INDEX_KEY_SUFFIX = ":deals" function normalizePositiveInt(value) { const num = Number(value) if (!Number.isInteger(num) || num <= 0) return null return num } function normalizeEpochMs(value) { const num = Number(value) if (!Number.isFinite(num) || num <= 0) return null return Math.floor(num) } function toEpochMs(value) { if (value instanceof Date) return value.getTime() const date = new Date(value) if (Number.isNaN(date.getTime())) return null return date.getTime() } function isActiveStatus(status) { return String(status || "").toUpperCase() === "ACTIVE" } function getSellerDealIndexKey(sellerId) { const sid = normalizePositiveInt(sellerId) if (!sid) return null return `${SELLER_DEAL_INDEX_KEY_PREFIX}${sid}${SELLER_DEAL_INDEX_KEY_SUFFIX}` } function normalizeDealIndexPayload(payload = {}) { const dealId = normalizePositiveInt(payload.dealId ?? payload.id) const sellerId = normalizePositiveInt(payload.sellerId) const createdAtTs = normalizeEpochMs(payload.createdAtTs) ?? normalizeEpochMs(toEpochMs(payload.createdAt)) const status = String(payload.status || "").toUpperCase() return { dealId, sellerId, createdAtTs, status } } function isIndexableDeal(payload = {}) { const normalized = normalizeDealIndexPayload(payload) return Boolean( normalized.dealId && normalized.sellerId && normalized.createdAtTs && isActiveStatus(normalized.status) ) } function addDealToSellerIndexInPipeline(pipeline, payload = {}) { const normalized = normalizeDealIndexPayload(payload) if (!normalized.dealId || !normalized.sellerId || !normalized.createdAtTs) return false if (!isActiveStatus(normalized.status)) return false const key = getSellerDealIndexKey(normalized.sellerId) if (!key) return false pipeline.zadd(key, String(normalized.createdAtTs), String(normalized.dealId)) return true } function removeDealFromSellerIndexInPipeline(pipeline, payload = {}) { const normalized = normalizeDealIndexPayload(payload) if (!normalized.dealId || !normalized.sellerId) return false const key = getSellerDealIndexKey(normalized.sellerId) if (!key) return false pipeline.zrem(key, String(normalized.dealId)) return true } async function reconcileDealSellerIndex({ before = null, after = null } = {}) { const prev = normalizeDealIndexPayload(before || {}) const next = normalizeDealIndexPayload(after || {}) const prevIndexable = isIndexableDeal(prev) const nextIndexable = isIndexableDeal(next) const redis = getRedisClient() const pipeline = redis.pipeline() let commandCount = 0 if (prevIndexable) { const removedForStatus = !nextIndexable const movedSeller = nextIndexable && prev.sellerId !== next.sellerId if (removedForStatus || movedSeller) { if (removeDealFromSellerIndexInPipeline(pipeline, prev)) commandCount += 1 } } if (nextIndexable) { const isNew = !prevIndexable const movedSeller = prevIndexable && prev.sellerId !== next.sellerId const scoreChanged = prevIndexable && prev.sellerId === next.sellerId && Number(prev.createdAtTs) !== Number(next.createdAtTs) if (isNew || movedSeller || scoreChanged) { if (addDealToSellerIndexInPipeline(pipeline, next)) commandCount += 1 } } if (!commandCount) return 0 try { await pipeline.exec() return commandCount } catch { return 0 } } async function getRecentDealIdsBySeller({ sellerId, offset = 0, limit = 30, } = {}) { const sid = normalizePositiveInt(sellerId) if (!sid) return [] const key = getSellerDealIndexKey(sid) if (!key) return [] const safeOffset = Math.max(0, Number(offset) || 0) const safeLimit = Math.max(1, Math.min(Number(limit) || 30, 300)) const redis = getRedisClient() try { const ids = await redis.zrevrange(key, safeOffset, safeOffset + safeLimit - 1) return Array.isArray(ids) ? ids.map((id) => Number(id)).filter((id) => Number.isInteger(id) && id > 0) : [] } catch { return [] } } async function getSellerDealIndexCount(sellerId) { const sid = normalizePositiveInt(sellerId) if (!sid) return 0 const key = getSellerDealIndexKey(sid) if (!key) return 0 const redis = getRedisClient() try { const count = await redis.zcard(key) return Number.isFinite(Number(count)) ? Number(count) : 0 } catch { return 0 } } module.exports = { getSellerDealIndexKey, addDealToSellerIndexInPipeline, removeDealFromSellerIndexInPipeline, reconcileDealSellerIndex, getRecentDealIdsBySeller, getSellerDealIndexCount, }