const { getRedisClient } = require("./client")
const dealDb = require("../../db/deal.db")
const { ensureMinDealTtl } = require("./dealCache.service")

function createRedisClient() {
  return getRedisClient()
}

const USER_VOTE_HASH_PREFIX = "users:votes:"
const USER_VOTE_TTL_SECONDS = 6 * 60 * 60

async function updateDealVoteInRedis({ dealId, userId, voteType, score }) {
  if (!dealId || !userId) return
  const redis = createRedisClient()

  try {
    const key = `deals:cache:${dealId}`
    const raw = await redis.call("JSON.GET", key)
    if (!raw) return { updated: false, delta: 0, score: null }

    const deal = JSON.parse(raw)
    const currentScore = Number.isFinite(deal?.score) ? Number(deal.score) : 0
    const maxNotifiedMilestone = Number.isFinite(deal?.maxNotifiedMilestone)
      ? Number(deal.maxNotifiedMilestone)
      : 0
    const dealUserId = Number(deal?.userId)
    const normalizedUserId = Number(userId)
    const normalizedVoteType = Number(voteType)
    const oldRaw = await redis.hget(
      `${USER_VOTE_HASH_PREFIX}${normalizedUserId}`,
      String(dealId)
    )
    const oldVote = oldRaw == null ? 0 : Number(oldRaw)
    const delta = normalizedVoteType - oldVote
    const nextScore =
      score !== undefined && score !== null ? Number(score) : currentScore + delta
    await redis.call("JSON.SET", key, "$.score", nextScore)
    if (normalizedVoteType === 0) {
      await redis.hdel(`${USER_VOTE_HASH_PREFIX}${normalizedUserId}`, String(dealId))
    } else {
      await redis.hset(`${USER_VOTE_HASH_PREFIX}${normalizedUserId}`, String(dealId), String(normalizedVoteType))
      await redis.expire(`${USER_VOTE_HASH_PREFIX}${normalizedUserId}`, USER_VOTE_TTL_SECONDS)
    }
    await ensureMinDealTtl(dealId, { minSeconds: 15 * 60 })

    return { updated: true, delta, score: nextScore, maxNotifiedMilestone, dealUserId }
  } catch {
    return { updated: false, delta: 0, score: null }
  } finally {}
}

async function getDealVoteFromRedis(dealId, userId) {
  const id = Number(dealId)
  const uid = Number(userId)
  if (!Number.isInteger(id) || !Number.isInteger(uid)) return 0
  const redis = createRedisClient()
  try {
    const userKey = `${USER_VOTE_HASH_PREFIX}${uid}`
    const exists = await redis.exists(userKey)
    if (!exists) {
      const dbVotes = await dealDb.findVotes(
        { userId: Number(uid) },
        { select: { dealId: true, voteType: true } }
      )
      const pipeline = redis.pipeline()
      dbVotes.forEach((vote) => {
        const did = Number(vote.dealId)
        const v = Number(vote.voteType)
        if (!Number.isInteger(did)) return
        if (Number.isFinite(v) && v !== 0) {
          pipeline.hset(userKey, String(did), String(v))
        }
      })
      pipeline.expire(userKey, USER_VOTE_TTL_SECONDS)
      await pipeline.exec()
      const fromDb = dbVotes.find((v) => Number(v.dealId) === id)
      const dbVote = Number(fromDb?.voteType ?? 0)
      return Number.isFinite(dbVote) ? dbVote : 0
    }
    const raw = await redis.hget(userKey, String(id))
    if (raw != null) {
      const value = Number(raw)
      return Number.isFinite(value) ? value : 0
    }
    return 0
  } catch {
    return 0
  } finally {}
}

async function getMyVotesForDeals(dealIds = [], userId) {
  const uid = Number(userId)
  if (!Number.isInteger(uid)) return new Map()
  const ids = (Array.isArray(dealIds) ? dealIds : [])
    .map((id) => Number(id))
    .filter((id) => Number.isInteger(id) && id > 0)
  if (!ids.length) return new Map()

  const redis = createRedisClient()
  try {
    const userKey = `${USER_VOTE_HASH_PREFIX}${uid}`
    const exists = await redis.exists(userKey)
    if (exists) {
      const results = await redis.hmget(userKey, ids.map(String))
      const map = new Map()
      results.forEach((raw, idx) => {
        const value = raw == null ? 0 : Number(raw)
        map.set(ids[idx], Number.isFinite(value) ? value : 0)
      })
      return map
    }

    const dbVotes = await dealDb.findVotes(
      { dealId: { in: ids }, userId: Number(uid) },
      { select: { dealId: true, voteType: true } }
    )
    const map = new Map()
    ids.forEach((id) => map.set(id, 0))
    const pipeline = redis.pipeline()
    dbVotes.forEach((vote) => {
      const did = Number(vote.dealId)
      const v = Number(vote.voteType)
      if (!Number.isInteger(did)) return
      if (Number.isFinite(v) && v !== 0) {
        map.set(did, v)
        pipeline.hset(userKey, String(did), String(v))
      }
    })
    pipeline.expire(userKey, USER_VOTE_TTL_SECONDS)
    await pipeline.exec()
    return map
  } catch {
    return new Map()
  } finally {}
}

module.exports = {
  updateDealVoteInRedis,
  getDealVoteFromRedis,
  getMyVotesForDeals,
}
