const { getRedisClient } = require("./client")
const { recordCacheMiss } = require("./cacheMetrics.service")

const USER_PUBLIC_ID_KEY_PREFIX = "users:public:id:"
const USER_PUBLIC_NAME_KEY_PREFIX = "users:public:name:"
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
}

function normalizeUserPayload(user) {
  const id = normalizeUserId(user?.id)
  if (!id) return null
  const badgesSource = Array.isArray(user?.badges)
    ? user.badges
    : Array.isArray(user?.userBadges)
      ? user.userBadges
      : []
  const badgeIds = badgesSource
    .map((item) => {
      if (!item) return null
      const badge = item.badge || item
      const id = Number(badge?.id)
      return Number.isInteger(id) && id > 0 ? id : null
    })
    .filter(Boolean)
  return {
    id,
    username: user?.username ?? null,
    avatarUrl: user?.avatarUrl ?? null,
    badgeIds: Array.from(new Set(badgeIds)),
  }
}

async function getUserPublicFromRedis(userId) {
  const id = normalizeUserId(userId)
  if (!id) return null
  const redis = createRedisClient()
  const key = `${USER_PUBLIC_ID_KEY_PREFIX}${id}`
  try {
    const raw = await redis.call("JSON.GET", key)
    if (!raw) {
      await recordCacheMiss({ key, label: "user-public" })
      return null
    }
    return JSON.parse(raw)
  } catch {
    return null
  } finally {}
}

async function getUsersPublicByIds(userIds = []) {
  const ids = Array.from(
    new Set((Array.isArray(userIds) ? userIds : []).map(normalizeUserId).filter(Boolean))
  )
  if (!ids.length) return new Map()

  const redis = createRedisClient()
  try {
    const pipeline = redis.pipeline()
    ids.forEach((id) => pipeline.call("JSON.GET", `${USER_PUBLIC_ID_KEY_PREFIX}${id}`))
    const results = await pipeline.exec()
    const map = new Map()

    results.forEach(([, raw], idx) => {
      if (!raw) return
      try {
        const user = JSON.parse(raw)
        if (user && user.id) map.set(ids[idx], user)
      } catch {
        return
      }
    })

    return map
  } catch {
    return new Map()
  } finally {}
}

async function setUsersPublicInRedis(users = [], { ttlSecondsById = null } = {}) {
  const payloads = (Array.isArray(users) ? users : [])
    .map(normalizeUserPayload)
    .filter(Boolean)
  if (!payloads.length) return 0

  const redis = createRedisClient()
  try {
    const pipeline = redis.pipeline()
    payloads.forEach((user) => {
      const key = `${USER_PUBLIC_ID_KEY_PREFIX}${user.id}`
      pipeline.call("JSON.SET", key, "$", JSON.stringify(user))
      if (user.username) {
        pipeline.set(`${USER_PUBLIC_NAME_KEY_PREFIX}${String(user.username).toLowerCase()}`, String(user.id))
      }
      const ttlSeconds = ttlSecondsById?.[user.id]
      if (ttlSeconds) {
        pipeline.expire(key, Number(ttlSeconds))
        if (user.username) {
          pipeline.expire(
            `${USER_PUBLIC_NAME_KEY_PREFIX}${String(user.username).toLowerCase()}`,
            Number(ttlSeconds)
          )
        }
      }
    })
    await pipeline.exec()
    return payloads.length
  } catch {
    return 0
  } finally {}
}

async function setUserPublicInRedis(user, { ttlSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
  const payload = normalizeUserPayload(user)
  if (!payload) return false
  const count = await setUsersPublicInRedis([payload], { ttlSecondsById: { [payload.id]: ttlSeconds } })
  return count > 0
}

async function ensureUserMinTtl(userId, { minSeconds = DEFAULT_USER_TTL_SECONDS } = {}) {
  const id = normalizeUserId(userId)
  if (!id) return { bumped: false }
  const redis = createRedisClient()
  const key = `${USER_PUBLIC_ID_KEY_PREFIX}${id}`
  const minTtl = Math.max(1, Number(minSeconds) || DEFAULT_USER_TTL_SECONDS)
  try {
    const ttl = await redis.ttl(key)
    if (ttl === -2) return { bumped: false } // no key
    if (ttl === -1 || ttl < minTtl) {
      const nextTtl = minTtl
      await redis.expire(key, nextTtl)
      return { bumped: true, ttl: nextTtl }
    }
    return { bumped: false, ttl }
  } catch {
    return { bumped: false }
  } finally {}
}

async function getUserIdByUsername(userName) {
  const normalized = String(userName || "").trim().toLowerCase()
  if (!normalized) return null
  const redis = createRedisClient()
  try {
    const raw = await redis.get(`${USER_PUBLIC_NAME_KEY_PREFIX}${normalized}`)
    if (!raw) {
      await recordCacheMiss({
        key: `${USER_PUBLIC_NAME_KEY_PREFIX}${normalized}`,
        label: "user-name",
      })
    }
    const id = raw ? Number(raw) : null
    return Number.isInteger(id) && id > 0 ? id : null
  } catch {
    return null
  } finally {}
}

module.exports = {
  getUserPublicFromRedis,
  getUsersPublicByIds,
  setUserPublicInRedis,
  setUsersPublicInRedis,
  ensureUserMinTtl,
  getUserIdByUsername,
}
