const { getRedisClient } = require("./client")
const dealSaveDb = require("../../db/dealSave.db")

const USER_KEY_PREFIX = "users:cache:"
const USER_SAVED_SET_PREFIX = "users:saved:"
const USER_UNSAVED_SET_PREFIX = "users:unsaved:"
const USER_SAVED_HASH_PREFIX = "users:savedmap:"
const DEFAULT_USER_TTL_SECONDS = 60 * 60

function createRedisClient() {
  return getRedisClient()
}

function normalizeUserId(userId) {
  const id = Number(userId)
  return Number.isInteger(id) && id > 0 ? id : null
}

async function ensureUserCache(userId, { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
  const id = normalizeUserId(userId)
  if (!id) return false
  const redis = createRedisClient()
  const key = `${USER_KEY_PREFIX}${id}`
  const savedMapKey = `${USER_SAVED_HASH_PREFIX}${id}`
  try {
    const exists = await redis.exists(key)
    if (!exists) {
      await redis.call(
        "JSON.SET",
        key,
        "$",
        JSON.stringify({ id, savedDeals: [] })
      )
    }
    if (ttlSeconds) {
      await redis.expire(key, Number(ttlSeconds))
      await redis.expire(savedMapKey, Number(ttlSeconds))
    }
    return true
  } catch {
    return false
  } finally {}
}

async function getUserSavedIdsFromRedis(userId) {
  const id = normalizeUserId(userId)
  if (!id) return []
  const redis = createRedisClient()
  const key = `${USER_KEY_PREFIX}${id}`
  const setKey = `${USER_SAVED_SET_PREFIX}${id}`
  const unsavedKey = `${USER_UNSAVED_SET_PREFIX}${id}`
  const savedMapKey = `${USER_SAVED_HASH_PREFIX}${id}`
  try {
    const [raw, setIds, unsavedIds] = await Promise.all([
      redis.call("JSON.GET", key, "$.savedDeals"),
      redis.smembers(setKey),
      redis.smembers(unsavedKey),
    ])
    const fromJson = raw ? JSON.parse(raw) : []
    const arr = Array.isArray(fromJson) ? (Array.isArray(fromJson[0]) ? fromJson[0] : fromJson) : []
    const jsonIds = arr
      .map((v) => Number(v))
      .filter((v) => Number.isInteger(v) && v > 0)
    const setList = (Array.isArray(setIds) ? setIds : [])
      .map((v) => Number(v))
      .filter((v) => Number.isInteger(v) && v > 0)
    const unsavedList = (Array.isArray(unsavedIds) ? unsavedIds : [])
      .map((v) => Number(v))
      .filter((v) => Number.isInteger(v) && v > 0)
    return {
      jsonIds,
      savedSet: new Set(setList),
      unsavedSet: new Set(unsavedList),
    }
  } catch {
    return { jsonIds: [], savedSet: new Set(), unsavedSet: new Set() }
  } finally {}
}

async function getUserSavedMapForDeals(userId, dealIds = []) {
  const uid = normalizeUserId(userId)
  const ids = Array.from(
    new Set((Array.isArray(dealIds) ? dealIds : []).map(Number).filter((id) => Number.isInteger(id) && id > 0))
  )
  if (!uid || !ids.length) return new Map()
  const redis = createRedisClient()
  const key = `${USER_SAVED_HASH_PREFIX}${uid}`
  try {
    const exists = await redis.exists(key)
    if (!exists) {
      const saved = await dealSaveDb.findDealSavesByUser(uid, {
        select: { dealId: true },
        where: { userId: uid },
      })
      const pipeline = redis.pipeline()
      saved.forEach((entry) => {
        if (!entry?.dealId) return
        pipeline.hset(key, String(entry.dealId), "1")
      })
      pipeline.expire(key, DEFAULT_USER_TTL_SECONDS)
      await pipeline.exec()
      const savedSet = new Set(saved.map((s) => Number(s.dealId)))
      const map = new Map()
      ids.forEach((id) => {
        if (savedSet.has(id)) map.set(id, true)
      })
      return map
    }

    const results = await redis.hmget(key, ids.map(String))
    const map = new Map()
    results.forEach((raw, idx) => {
      const value = raw == null ? 0 : Number(raw)
      if (value) map.set(ids[idx], true)
    })
    return map
  } catch {
    return new Map()
  } finally {}
}

async function setUserSavedDeals(userId, ids = [], { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
  const uid = normalizeUserId(userId)
  if (!uid) return false
  const redis = createRedisClient()
  const key = `${USER_KEY_PREFIX}${uid}`
  const savedMapKey = `${USER_SAVED_HASH_PREFIX}${uid}`
  try {
    const list = Array.isArray(ids) ? ids : []
    await redis.call("JSON.SET", key, "$.savedDeals", JSON.stringify(list))
    await redis.del(savedMapKey)
    if (list.length) {
      const pipeline = redis.pipeline()
      list.forEach((id) => pipeline.hset(savedMapKey, String(id), "1"))
      await pipeline.exec()
    }
    if (ttlSeconds) {
      await redis.expire(key, Number(ttlSeconds))
      await redis.expire(savedMapKey, Number(ttlSeconds))
    }
    return true
  } catch {
    return false
  } finally {}
}

async function addUserSavedDeal(userId, dealId, { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
  const uid = normalizeUserId(userId)
  const did = normalizeUserId(dealId)
  if (!uid || !did) return false
  const redis = createRedisClient()
  const key = `${USER_KEY_PREFIX}${uid}`
  const setKey = `${USER_SAVED_SET_PREFIX}${uid}`
  const unsavedKey = `${USER_UNSAVED_SET_PREFIX}${uid}`
  const savedMapKey = `${USER_SAVED_HASH_PREFIX}${uid}`
  try {
    await ensureUserCache(uid, { ttlSeconds })
    const raw = await redis.call("JSON.GET", key, "$.savedDeals")
    let list = []
    if (raw) {
      const parsed = JSON.parse(raw)
      const arr = Array.isArray(parsed) ? parsed[0] : []
      list = Array.isArray(arr) ? arr : []
    }
    const exists = Array.isArray(list)
      ? list.some((v) => Number(v) === did)
      : false
    if (!exists) {
      list = [did, ...list]
      await redis.call("JSON.SET", key, "$.savedDeals", JSON.stringify(list))
    }
    await redis.sadd(setKey, String(did))
    await redis.srem(unsavedKey, String(did))
    await redis.hset(savedMapKey, String(did), "1")
    if (ttlSeconds) {
      await redis.expire(setKey, Number(ttlSeconds))
      await redis.expire(unsavedKey, Number(ttlSeconds))
      await redis.expire(key, Number(ttlSeconds))
      await redis.expire(savedMapKey, Number(ttlSeconds))
    }
    return true
  } catch {
    return false
  } finally {}
}

async function removeUserSavedDeal(userId, dealId, { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
  const uid = normalizeUserId(userId)
  const did = normalizeUserId(dealId)
  if (!uid || !did) return false
  const redis = createRedisClient()
  const key = `${USER_KEY_PREFIX}${uid}`
  const setKey = `${USER_SAVED_SET_PREFIX}${uid}`
  const unsavedKey = `${USER_UNSAVED_SET_PREFIX}${uid}`
  const savedMapKey = `${USER_SAVED_HASH_PREFIX}${uid}`
  try {
    const raw = await redis.call("JSON.GET", key, "$.savedDeals")
    if (raw) {
      const parsed = JSON.parse(raw)
      const arr = Array.isArray(parsed) ? parsed[0] : []
      const list = Array.isArray(arr)
        ? arr.filter((v) => Number(v) !== did)
        : []
      await redis.call("JSON.SET", key, "$.savedDeals", JSON.stringify(list))
    }
    await redis.srem(setKey, String(did))
    await redis.sadd(unsavedKey, String(did))
    await redis.hdel(savedMapKey, String(did))
    if (ttlSeconds) {
      await redis.expire(setKey, Number(ttlSeconds))
      await redis.expire(unsavedKey, Number(ttlSeconds))
      await redis.expire(key, Number(ttlSeconds))
      await redis.expire(savedMapKey, Number(ttlSeconds))
    }
    return true
  } catch {
    return false
  } finally {}
}

module.exports = {
  ensureUserCache,
  getUserSavedIdsFromRedis,
  getUserSavedMapForDeals,
  setUserSavedDeals,
  addUserSavedDeal,
  removeUserSavedDeal,
}
