const dealDB = require("../db/deal.db")
const dealSaveDB = require("../db/dealSave.db")
const { getDealsByIdsFromRedis } = require("./redis/hotDealList.service")
const {
  ensureUserCache,
  getUserSavedIdsFromRedis,
  addUserSavedDeal,
  removeUserSavedDeal,
  setUserSavedDeals,
} = require("./redis/userCache.service")
const { mapDealToRedisJson } = require("./redis/dealIndexing.service")
const { getOrCacheDeal, updateDealSavesInRedis, setDealInRedis } = require("./redis/dealCache.service")
const { queueDealSaveUpdate } = require("./redis/dbSync.service")

const PAGE_LIMIT = 20
const ALLOWED_STATUSES = new Set(["ACTIVE", "EXPIRED"])

function assertPositiveInt(value, name) {
  const num = Number(value)
  if (!Number.isInteger(num) || num <= 0) throw new Error(`Gecersiz ${name}.`)
  return num
}

function normalizePage(value) {
  const num = Number(value)
  if (!Number.isInteger(num) || num < 1) return 1
  return num
}

const DEAL_CACHE_INCLUDE = {
  user: { select: { id: true, username: true, avatarUrl: true } },
  images: { orderBy: { order: "asc" }, select: { id: true, imageUrl: true, order: true } },
  dealTags: { include: { tag: { select: { id: true, slug: true, name: true } } } },
  comments: {
    orderBy: { createdAt: "desc" },
    include: {
      user: { select: { id: true, username: true, avatarUrl: true } },
      likes: { select: { userId: true } },
    },
  },
  aiReview: {
    select: {
      bestCategoryId: true,
      needsReview: true,
      hasIssue: true,
      issueType: true,
      issueReason: true,
    },
  },
}

async function saveDealForUser({ userId, dealId }) {
  const uid = assertPositiveInt(userId, "userId")
  const did = assertPositiveInt(dealId, "dealId")

  const deal = await getOrCacheDeal(did, { ttlSeconds: 15 * 60 })
    .catch(() => null)
  if (!deal) {
    const err = new Error("Deal bulunamadi.")
    err.statusCode = 404
    throw err
  }
  if (!ALLOWED_STATUSES.has(String(deal.status))) {
    const err = new Error("Bu deal kaydedilemez.")
    err.statusCode = 400
    throw err
  }

  await updateDealSavesInRedis({
    dealId: did,
    userId: uid,
    action: "SAVE",
    createdAt: new Date().toISOString(),
    minSeconds: 15 * 60,
  })
  await addUserSavedDeal(uid, did, { ttlSeconds: 60 * 60 })
  queueDealSaveUpdate({
    dealId: did,
    userId: uid,
    action: "SAVE",
    createdAt: new Date().toISOString(),
  }).catch((err) => console.error("DB sync dealSave queue failed:", err?.message || err))
  return { saved: true }
}

async function removeSavedDealForUser({ userId, dealId }) {
  const uid = assertPositiveInt(userId, "userId")
  const did = assertPositiveInt(dealId, "dealId")

  await updateDealSavesInRedis({
    dealId: did,
    userId: uid,
    action: "UNSAVE",
    createdAt: new Date().toISOString(),
    minSeconds: 15 * 60,
  })
  await removeUserSavedDeal(uid, did, { ttlSeconds: 60 * 60 })
  queueDealSaveUpdate({
    dealId: did,
    userId: uid,
    action: "UNSAVE",
    createdAt: new Date().toISOString(),
  }).catch((err) => console.error("DB sync dealSave queue failed:", err?.message || err))
  return { removed: true }
}

async function listSavedDeals({ userId, page = 1 }) {
  const uid = assertPositiveInt(userId, "userId")
  const safePage = normalizePage(page)
  const skip = (safePage - 1) * PAGE_LIMIT

  await ensureUserCache(uid, { ttlSeconds: 60 * 60 })
  const redisCache = await getUserSavedIdsFromRedis(uid)
  const redisJsonIds = redisCache?.jsonIds || []
  const savedSet = redisCache?.savedSet || new Set()
  const unsavedSet = redisCache?.unsavedSet || new Set()

  const where = {
    userId: uid,
    deal: { status: { in: Array.from(ALLOWED_STATUSES) } },
  }

  const [total, saves] = await Promise.all([
    dealSaveDB.countDealSavesByUser(uid, { where }),
    dealSaveDB.findDealSavesByUser(
      uid,
      {
        skip,
        take: PAGE_LIMIT,
        orderBy: { createdAt: "desc" },
        where,
      }
    ),
  ])

  const dbDealIds = saves.map((s) => Number(s.dealId)).filter((id) => Number.isInteger(id) && id > 0)
  const baseDb = dbDealIds.filter((id) => !unsavedSet.has(id))
  const extraSaved = Array.from(savedSet).filter((id) => !unsavedSet.has(id))

  let mergedIds = []
  if (redisJsonIds.length) {
    const filteredJson = redisJsonIds.filter((id) => !unsavedSet.has(id))
    const jsonSet = new Set(filteredJson)
    const prependSaved = extraSaved.filter((id) => !jsonSet.has(id))
    mergedIds = [...prependSaved, ...filteredJson]
    baseDb.forEach((id) => {
      if (!jsonSet.has(id)) mergedIds.push(id)
    })
  } else {
    const baseSet = new Set(baseDb)
    const prependSaved = extraSaved.filter((id) => !baseSet.has(id))
    mergedIds = [...prependSaved, ...baseDb]
  }

  await setUserSavedDeals(uid, mergedIds, { ttlSeconds: 60 * 60 })
  const pageIds = mergedIds.slice(skip, skip + PAGE_LIMIT)

  const cachedDeals = await getDealsByIdsFromRedis(pageIds, uid)
  const cachedMap = new Map(cachedDeals.map((d) => [Number(d.id), d]))
  const missingIds = pageIds.filter((id) => !cachedMap.has(id))

  if (missingIds.length) {
    const missingDeals = await dealDB.findDeals(
      { id: { in: missingIds }, status: { in: Array.from(ALLOWED_STATUSES) } },
      { include: DEAL_CACHE_INCLUDE }
    )
    const fallbackMap = new Map()
    missingDeals.forEach((deal) => {
      const payload = mapDealToRedisJson(deal)
    const myVote = 0
      fallbackMap.set(Number(deal.id), {
        ...payload,
        user: deal.user ?? null,
        seller: deal.seller ?? null,
        myVote,
        isSaved: true,
      })
    })
    await Promise.all(
      missingDeals.map((deal) => {
        const payload = mapDealToRedisJson(deal)
        return setDealInRedis(deal.id, payload, { ttlSeconds: 15 * 60 })
      })
    )
    const hydrated = await getDealsByIdsFromRedis(missingIds, uid)
    hydrated.forEach((d) => cachedMap.set(Number(d.id), d))
    if (!hydrated.length && fallbackMap.size) {
      fallbackMap.forEach((value, key) => cachedMap.set(key, value))
    }
  }

  const results = pageIds.map((id) => cachedMap.get(id)).filter(Boolean)

  return {
    page: safePage,
    total: mergedIds.length,
    totalPages: mergedIds.length ? Math.ceil(mergedIds.length / PAGE_LIMIT) : 0,
    results,
  }
}

module.exports = {
  saveDealForUser,
  removeSavedDealForUser,
  listSavedDeals,
}
