const { getRedisClient } = require("./client")
const { getSellersByIds, getSellerById } = require("./sellerCache.service")
const { getUsersPublicByIds, setUsersPublicInRedis } = require("./userPublicCache.service")
const userDB = require("../../db/user.db")
const { getMyVotesForDeals } = require("./dealVote.service")

function createRedisClient() {
  return getRedisClient()
}

const USER_SAVED_HASH_PREFIX = "users:savedmap:"

async function getUserSavedMap(redis, dealIds = [], viewerId = null) {
  const uid = Number(viewerId)
  if (!Number.isInteger(uid) || !dealIds.length) return new Map()
  try {
    const results = await redis.hmget(`${USER_SAVED_HASH_PREFIX}${uid}`, dealIds.map(String))
    const map = new Map()
    results.forEach((raw, idx) => {
      const value = raw == null ? 0 : Number(raw)
      if (value) map.set(Number(dealIds[idx]), true)
    })
    return map
  } catch {
    return new Map()
  }
}

async function getHotDealListId(redis, hotListId) {
  if (hotListId) return String(hotListId)
  const latest = await redis.get("deals:lists:hot:latest")
  return latest ? String(latest) : null
}

async function getListId(redis, listKeyPrefix, listId) {
  if (listId) return String(listId)
  const latest = await redis.get(`${listKeyPrefix}:latest`)
  return latest ? String(latest) : null
}

async function getHotDealIds({ hotListId } = {}) {
  const redis = createRedisClient()

  try {
    const listId = await getHotDealListId(redis, hotListId)
    if (!listId) return { hotListId: null, dealIds: [] }

    const key = `deals:lists:hot:${listId}`
    const raw = await redis.call("JSON.GET", key, "$.dealIds")
    if (!raw) return { hotListId: listId, dealIds: [] }

    const parsed = JSON.parse(raw)
    const dealIds = Array.isArray(parsed) ? parsed[0] : []

    return {
      hotListId: listId,
      dealIds: Array.isArray(dealIds) ? dealIds.map((id) => Number(id)) : [],
    }
  } catch {
    return { hotListId: null, dealIds: [] }
  } finally {}
}

async function getDealsByIdsFromRedis(ids = [], viewerId = null) {
  if (!ids.length) return []
  const redis = createRedisClient()

  try {
    const pipeline = redis.pipeline()
    ids.forEach((id) => {
      pipeline.call("JSON.GET", `deals:cache:${id}`)
    })

    const results = await pipeline.exec()
    const deals = []

    results.forEach(([, raw], idx) => {
      if (!raw) return
      try {
        const deal = JSON.parse(raw)
        if (deal && deal.id) {
          deals.push({ deal, index: idx })
        }
      } catch {
        return
      }
    })

    // Preserve original order (ids array order)
    const ordered = deals
      .sort((a, b) => a.index - b.index)
      .map((item) => item.deal)

    const sellerIds = ordered
      .map((deal) => Number(deal?.sellerId))
      .filter((id) => Number.isInteger(id) && id > 0)
    const sellerMap = sellerIds.length ? await getSellersByIds(sellerIds) : new Map()
    const voteMap = viewerId ? await getMyVotesForDeals(ordered.map((d) => d.id), viewerId) : new Map()
    const savedMap = viewerId ? await getUserSavedMap(redis, ordered.map((d) => d.id), viewerId) : new Map()

    const userIds = ordered
      .map((deal) => Number(deal?.userId))
      .filter((id) => Number.isInteger(id) && id > 0)
    const userMap = userIds.length ? await getUsersPublicByIds(userIds) : new Map()
    const missingUserIds = Array.from(
      new Set(userIds.filter((id) => !userMap.has(id)))
    )

    if (missingUserIds.length) {
      const missingSet = new Set(missingUserIds)
      const ttlPipeline = redis.pipeline()
      ordered.forEach((deal) => {
        ttlPipeline.ttl(`deals:cache:${deal.id}`)
      })
      const ttlResults = await ttlPipeline.exec()
      const ttlByDealId = new Map()
      ttlResults.forEach(([, ttl], idx) => {
        const dealId = ordered[idx]?.id
        if (dealId) ttlByDealId.set(Number(dealId), Number(ttl))
      })

      const users = await userDB.findUsersByIds(missingUserIds, {
        select: {
          id: true,
          username: true,
          avatarUrl: true,
          userBadges: {
            orderBy: { earnedAt: "desc" },
            select: {
              earnedAt: true,
              badge: { select: { id: true, name: true, iconUrl: true, description: true } },
            },
          },
        },
      })

      const ttlByUserId = {}
      ordered.forEach((deal) => {
        const uid = Number(deal?.userId)
        if (!missingSet.has(uid)) return
        const ttl = ttlByDealId.get(Number(deal?.id))
        if (ttl == null || ttl <= 0) return
        ttlByUserId[uid] = Math.max(ttlByUserId[uid] || 0, ttl)
      })

      if (users.length) {
        await setUsersPublicInRedis(users, { ttlSecondsById: ttlByUserId })
        users.forEach((u) => userMap.set(u.id, u))
      }
    }

    const enriched = ordered.map((deal) => {
      let next = deal
      if (!next?.user && next?.userId) {
        const user = userMap.get(Number(next.userId)) || null
        if (user) next = { ...next, user }
      }
      if (!next?.seller && next?.sellerId) {
        const seller = sellerMap.get(Number(next.sellerId)) || null
        if (seller) next = { ...next, seller }
      }
      const myVote = viewerId ? Number(voteMap.get(Number(next.id)) ?? 0) : 0
      const isSaved = viewerId ? savedMap.get(Number(next.id)) === true : false
      return { ...next, myVote, isSaved }
    })

    return enriched
  } catch {
    return []
  } finally {}
}

async function getDealByIdFromRedis(id, viewerId = null) {
  const redis = createRedisClient()
  try {
    const raw = await redis.call("JSON.GET", `deals:cache:${id}`)
    if (!raw) return null
    let deal = JSON.parse(raw)
    if (deal?.sellerId && !deal?.seller) {
      const seller = await getSellerById(Number(deal.sellerId))
      if (seller) deal = { ...deal, seller }
    }
    if (viewerId) {
      const voteMap = await getMyVotesForDeals([deal.id], viewerId)
      const savedMap = await getUserSavedMap(redis, [deal.id], viewerId)
      const isSaved = savedMap.get(Number(deal.id)) === true
      deal = { ...deal, myVote: Number(voteMap.get(Number(deal.id)) ?? 0), isSaved }
    }
    return deal
  } catch {
    return null
  } finally {}
}

async function getHotRangeDealIds({ range, listId } = {}) {
  const redis = createRedisClient()

  try {
    const prefix =
      range === "day"
        ? "deals:lists:hot_day"
        : range === "week"
        ? "deals:lists:hot_week"
        : range === "month"
        ? "deals:lists:hot_month"
        : null
    if (!prefix) return { listId: null, dealIds: [] }

    const resolvedId = await getListId(redis, prefix, listId)
    if (!resolvedId) return { listId: null, dealIds: [] }

    const key = `${prefix}:${resolvedId}`
    const raw = await redis.call("JSON.GET", key, "$.dealIds")
    if (!raw) return { listId: resolvedId, dealIds: [] }

    const parsed = JSON.parse(raw)
    const dealIds = Array.isArray(parsed) ? parsed[0] : []

    return {
      listId: resolvedId,
      dealIds: Array.isArray(dealIds) ? dealIds.map((id) => Number(id)) : [],
    }
  } catch {
    return { listId: null, dealIds: [] }
  } finally {}
}

module.exports = { getHotDealIds, getHotRangeDealIds, getDealsByIdsFromRedis, getDealByIdFromRedis }
