const { getRedisClient } = require("./client")
const { getOrCacheDeal, getDealIdByCommentId, ensureMinDealTtl } = require("./dealCache.service")

const DEFAULT_TTL_SECONDS = 15 * 60
const DEAL_KEY_PREFIX = "deals:cache:"
const COMMENT_LOOKUP_KEY = "comments:lookup"
const COMMENT_IDS_KEY = "comments:ids"

function createRedisClient() {
  return getRedisClient()
}

function normalizeParentId(value) {
  if (value === undefined || value === null || value === "" || value === "null") return null
  const n = Number(value)
  return Number.isInteger(n) && n > 0 ? n : null
}

function pickComments(deal, parentId) {
  const list = Array.isArray(deal?.comments) ? deal.comments : []
  return list.filter(
    (c) =>
      (normalizeParentId(c.parentId) ?? null) === (normalizeParentId(parentId) ?? null) &&
      !c.deletedAt
  )
}

function sortComments(list, sort) {
  const mode = String(sort || "NEW").toUpperCase()
  if (mode === "TOP") {
    return list.sort((a, b) => {
      const diff = Number(b.likeCount || 0) - Number(a.likeCount || 0)
      if (diff !== 0) return diff
      return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
    })
  }
  return list.sort(
    (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  )
}


async function updateDealCommentsInRedis(dealId, comments, commentCount) {
  const redis = createRedisClient()
  try {
    const pipeline = redis.pipeline()
    pipeline.call("JSON.SET", `${DEAL_KEY_PREFIX}${dealId}`, "$.comments", JSON.stringify(comments))
    if (typeof commentCount === "number") {
      pipeline.call("JSON.SET", `${DEAL_KEY_PREFIX}${dealId}`, "$.commentCount", Number(commentCount))
    }
    await pipeline.exec()
  } catch {
    // ignore cache failures
  } finally {}
}

async function getCommentsForDeal({
  dealId,
  deal = null,
  parentId = null,
  page = 1,
  limit = 10,
  sort = "NEW",
  viewerId = null,
} = {}) {
  const resolvedDeal =
    deal || (await getOrCacheDeal(dealId, { ttlSeconds: DEFAULT_TTL_SECONDS }))
  if (!resolvedDeal) return { page: 1, total: 0, totalPages: 0, results: [] }

  const normalizedLimit = Math.max(1, Math.min(Number(limit) || 10, 50))
  const normalizedPage = Math.max(1, Number(page) || 1)

  let comments = pickComments(resolvedDeal, parentId)
  comments = sortComments(comments, sort)

  const total = comments.length
  const totalPages = Math.ceil(total / normalizedLimit)
  const start = (normalizedPage - 1) * normalizedLimit
  const results = comments.slice(start, start + normalizedLimit).map((comment) => {
    const likes = Array.isArray(comment.likes) ? comment.likes : []
    const myLike = viewerId
      ? likes.some((l) => Number(l.userId) === Number(viewerId))
      : false
    return {
      ...comment,
      myLike,
    }
  })

  return { page: normalizedPage, total, totalPages, results }
}

async function addCommentToRedis(comment, { ttlSeconds = DEFAULT_TTL_SECONDS } = {}) {
  const deal = await getOrCacheDeal(comment.dealId, { ttlSeconds })
  if (!deal) return { added: false }

  const comments = Array.isArray(deal.comments) ? deal.comments : []
  const newComment = {
    id: comment.id,
    dealId: comment.dealId,
    userId: comment.userId,
    text: comment.text,
    createdAt: comment.createdAt instanceof Date ? comment.createdAt.toISOString() : comment.createdAt,
    parentId: normalizeParentId(comment.parentId),
    likeCount: Number.isFinite(comment.likeCount) ? comment.likeCount : 0,
    repliesCount: Number.isFinite(comment.repliesCount) ? comment.repliesCount : 0,
    deletedAt: null,
    user: comment.user
      ? {
          id: comment.user.id,
          username: comment.user.username,
          avatarUrl: comment.user.avatarUrl ?? null,
        }
      : null,
    likes: [],
  }

  comments.unshift(newComment)

  if (newComment.parentId) {
    const parent = comments.find((c) => Number(c.id) === Number(newComment.parentId))
    if (parent) {
      parent.repliesCount = Number.isFinite(parent.repliesCount) ? parent.repliesCount + 1 : 1
    }
  }

  const commentCount = Number.isFinite(deal.commentCount) ? deal.commentCount + 1 : 1
  await updateDealCommentsInRedis(comment.dealId, comments, commentCount)
  await ensureMinDealTtl(comment.dealId, { minSeconds: DEFAULT_TTL_SECONDS })
  const redis = createRedisClient()
  try {
    await redis.hset(COMMENT_LOOKUP_KEY, String(comment.id), String(comment.dealId))
    await redis.sadd(COMMENT_IDS_KEY, String(comment.id))
  } catch {
    // ignore cache failures
  } finally {}
  return { added: true }
}

async function removeCommentFromRedis({ commentId, dealId }) {
  const deal = await getOrCacheDeal(dealId, { ttlSeconds: DEFAULT_TTL_SECONDS })
  if (!deal) return { removed: false }

  const comments = Array.isArray(deal.comments) ? deal.comments : []
  const target = comments.find((c) => Number(c.id) === Number(commentId))
  if (!target) return { removed: false }
  if (target.deletedAt) return { removed: false, alreadyDeleted: true }

  target.deletedAt = new Date().toISOString()
  const commentCount = Math.max(0, Number(deal.commentCount || 0) - 1)

  if (target.parentId) {
    const parent = comments.find((c) => Number(c.id) === Number(target.parentId))
    if (parent && Number.isFinite(parent.repliesCount)) {
      parent.repliesCount = Math.max(0, parent.repliesCount - 1)
    }
  }

  await updateDealCommentsInRedis(dealId, comments, commentCount)
  await ensureMinDealTtl(dealId, { minSeconds: DEFAULT_TTL_SECONDS })
  return { removed: true }
}

async function updateCommentLikeInRedisByDeal({ dealId, commentId, userId, like }) {
  const deal = await getOrCacheDeal(dealId, { ttlSeconds: DEFAULT_TTL_SECONDS })
  if (!deal) return { liked: Boolean(like), delta: 0, likeCount: 0 }

  const comments = Array.isArray(deal.comments) ? deal.comments : []
  const target = comments.find((c) => Number(c.id) === Number(commentId))
  if (!target || target.deletedAt) return { liked: Boolean(like), delta: 0, likeCount: 0 }

  let likes = Array.isArray(target.likes) ? target.likes : []
  const exists = likes.some((l) => Number(l.userId) === Number(userId))

  let delta = 0
  if (like) {
    if (!exists) {
      likes = [...likes, { userId: Number(userId) }]
      delta = 1
    }
  } else if (exists) {
    likes = likes.filter((l) => Number(l.userId) !== Number(userId))
    delta = -1
  }

  target.likes = likes
  if (delta !== 0) {
    target.likeCount = Math.max(0, Number(target.likeCount || 0) + delta)
  } else {
    target.likeCount = Number.isFinite(target.likeCount) ? target.likeCount : likes.length
  }

  await updateDealCommentsInRedis(dealId, comments)
  await ensureMinDealTtl(dealId, { minSeconds: DEFAULT_TTL_SECONDS })
  return { liked: Boolean(like), delta, likeCount: Number(target.likeCount || 0) }
}

async function updateCommentLikeInRedis({ commentId, userId, like }) {
  const dealId = await getDealIdByCommentId(commentId)
  if (!dealId) return { liked: Boolean(like), delta: 0, likeCount: 0 }
  return updateCommentLikeInRedisByDeal({ dealId, commentId, userId, like })
}

module.exports = {
  getCommentsForDeal,
  addCommentToRedis,
  removeCommentFromRedis,
  updateCommentLikeInRedis,
  updateCommentLikeInRedisByDeal,
}
