const dealDB = require("../../db/deal.db")
const dealAnalyticsDb = require("../../db/dealAnalytics.db")
const categoryDB = require("../../db/category.db")
const { findSellers } = require("../../db/seller.db")
const { getRedisClient } = require("./client")
const { setUsersPublicInRedis } = require("./userPublicCache.service")
const { setBadgesInRedis } = require("./badgeCache.service")
const badgeDb = require("../../db/badge.db")

const DEAL_KEY_PREFIX = "deals:cache:"
const DEAL_ANALYTICS_TOTAL_PREFIX = "deals:analytics:total:"
const COMMENT_LOOKUP_KEY = "comments:lookup"
const COMMENT_IDS_KEY = "comments:ids"
const SELLERS_KEY = "data:sellers"
const SELLER_DOMAINS_KEY = "data:seller:domains"
const CATEGORIES_KEY = "data:categories"

function createRedisClient() {
  return getRedisClient()
}

function toIso(value) {
  return value instanceof Date ? value.toISOString() : value ?? null
}

function toEpochMs(value) {
  if (value instanceof Date) return value.getTime()
  const date = new Date(value)
  return Number.isNaN(date.getTime()) ? null : date.getTime()
}

function mapDealToRedisJson(deal) {
  const tags =
    Array.isArray(deal.dealTags) && deal.dealTags.length
      ? deal.dealTags
          .map((dt) => dt?.tag)
          .filter(Boolean)
          .map((tag) => ({
            id: tag.id,
            slug: tag.slug,
            name: tag.name,
          }))
      : []

  const commentsRaw = Array.isArray(deal.comments) ? deal.comments : []
  const repliesCountByParent = new Map()
  commentsRaw.forEach((comment) => {
    if (!comment.parentId) return
    if (comment.deletedAt) return
    repliesCountByParent.set(
      comment.parentId,
      (repliesCountByParent.get(comment.parentId) || 0) + 1
    )
  })

  const comments = commentsRaw.length
    ? commentsRaw.map((comment) => ({
        id: comment.id,
        dealId: comment.dealId,
        text: comment.text,
        userId: comment.userId,
        createdAt: toIso(comment.createdAt),
        parentId: comment.parentId ?? null,
        likeCount: Number.isFinite(comment.likeCount) ? comment.likeCount : 0,
        repliesCount: repliesCountByParent.get(comment.id) || 0,
        deletedAt: toIso(comment.deletedAt),
        user: comment.user
          ? {
              id: comment.user.id,
              username: comment.user.username,
              avatarUrl: comment.user.avatarUrl ?? null,
            }
          : null,
        likes: Array.isArray(comment.likes)
          ? comment.likes.map((like) => ({ userId: like.userId }))
          : [],
      }))
    : []

  return {
    id: deal.id,
    title: deal.title,
    description: deal.description ?? null,
    url: deal.url ?? null,
    price: deal.price ?? null,
    originalPrice: deal.originalPrice ?? null,
    shippingPrice: deal.shippingPrice ?? null,
    percentOff: deal.percentOff ?? null,
    couponCode: deal.couponCode ?? null,
    hasCouponCode: deal.couponCode ? 1 : 0,
    location: deal.location ?? null,
      discountType: deal.discountType ?? null,
      discountValue: deal.discountValue ?? null,
      barcodeId: deal.barcodeId ?? null,
      maxNotifiedMilestone: Number.isFinite(deal.maxNotifiedMilestone)
      ? deal.maxNotifiedMilestone
      : 0,
    userId: deal.userId ?? null,
    score: deal.score ?? 0,
    commentCount: deal.commentCount ?? 0,
    status: deal.status ?? null,
    saletype: deal.saletype ?? null,
    affiliateType: deal.affiliateType ?? null,
    sellerId: deal.sellerId ?? null,
    customSeller: deal.customSeller ?? null,
    categoryId: deal.categoryId ?? null,
    createdAt: toIso(deal.createdAt),
    updatedAt: toIso(deal.updatedAt),
    createdAtTs: toEpochMs(deal.createdAt),
    updatedAtTs: toEpochMs(deal.updatedAt),
    images: Array.isArray(deal.images)
      ? deal.images.map((img) => ({
          id: img.id,
          imageUrl: img.imageUrl,
          order: img.order,
        }))
      : [],
    tags,
    comments,
    aiReview: deal.aiReview
      ? {
          bestCategoryId: deal.aiReview.bestCategoryId,
          tags: Array.isArray(deal.aiReview.tags) ? deal.aiReview.tags : [],
          needsReview: deal.aiReview.needsReview,
          hasIssue: deal.aiReview.hasIssue,
          issueType: deal.aiReview.issueType,
          issueReason: deal.aiReview.issueReason ?? null,
        }
      : null,
  }
}

async function seedRecentDealsToRedis({ days = 30, ttlDays = 31, batchSize = 200 } = {}) {
  const redis = createRedisClient()
  const cutoff = new Date(Date.now() - Number(days) * 24 * 60 * 60 * 1000)
  const ttlWindowMs = Math.max(1, Number(ttlDays)) * 24 * 60 * 60 * 1000

  try {
    const deals = await dealDB.findDeals(
      { createdAt: { gte: cutoff } },
      {
        orderBy: { createdAt: "desc" },
        include: {
          user: {
            select: {
              id: true,
              username: true,
              avatarUrl: true,
              userBadges: {
                orderBy: { earnedAt: "desc" },
                select: {
                  earnedAt: true,
                  badge: { select: { id: true, name: true, iconUrl: true, description: 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,
              tags: true,
              needsReview: true,
              hasIssue: true,
              issueType: true,
              issueReason: true,
            },
          },
        },
      }
    )

    const dealIds = deals.map((deal) => deal.id)
    await dealAnalyticsDb.ensureTotalsForDealIds(dealIds)
    const totals = await dealAnalyticsDb.getTotalsByDealIds(dealIds)
    const totalsById = new Map(
      (Array.isArray(totals) ? totals : []).map((t) => [
        t.dealId,
        {
          impressions: Number(t.impressions) || 0,
          views: Number(t.views) || 0,
          clicks: Number(t.clicks) || 0,
        },
      ])
    )

    const userTtlById = {}
    const users = []
    const seenUsers = new Set()
    deals.forEach((deal) => {
      const user = deal?.user
      if (user && user.id && !seenUsers.has(user.id)) {
        users.push(user)
        seenUsers.add(user.id)
      }
      const createdAt = deal?.createdAt instanceof Date ? deal.createdAt : new Date(deal?.createdAt)
      const ageMs = Number.isNaN(createdAt?.getTime()) ? 0 : Date.now() - createdAt.getTime()
      const ttlMs = Math.max(1, ttlWindowMs - Math.max(0, ageMs))
      const ttlSeconds = Math.ceil(ttlMs / 1000)
      if (user?.id) {
        userTtlById[user.id] = Math.max(userTtlById[user.id] || 0, ttlSeconds)
      }
    })

    let created = 0
    for (let i = 0; i < deals.length; i += batchSize) {
      const chunk = deals.slice(i, i + batchSize)
      const pipeline = redis.pipeline()
      const setCommands = []
      let cmdIndex = 0

      for (const deal of chunk) {
        try {
          const key = `${DEAL_KEY_PREFIX}${deal.id}`
          const payload = JSON.stringify(mapDealToRedisJson(deal))
          pipeline.call("JSON.SET", key, "$", payload, "NX")
          setCommands.push({ deal, index: cmdIndex })
          cmdIndex += 1
          const totals = totalsById.get(deal.id) || { impressions: 0, views: 0, clicks: 0 }
          pipeline.hset(
            `${DEAL_ANALYTICS_TOTAL_PREFIX}${deal.id}`,
            "impressions",
            String(totals.impressions || 0),
            "views",
            String(totals.views || 0),
            "clicks",
            String(totals.clicks || 0)
          )
          cmdIndex += 1
          if (Array.isArray(deal.comments) && deal.comments.length) {
            deal.comments.forEach((comment) => {
              pipeline.hset(COMMENT_LOOKUP_KEY, String(comment.id), String(deal.id))
              pipeline.sadd(COMMENT_IDS_KEY, String(comment.id))
              cmdIndex += 1
            })
          }
        } catch (err) {
          console.error("Redis seed skip deal:", deal?.id, err?.message || err)
        }
      }

      const results = await pipeline.exec()

      for (const entry of setCommands) {
        const deal = entry.deal
        const createdAt = deal?.createdAt instanceof Date ? deal.createdAt : new Date(deal?.createdAt)
        const ageMs = Number.isNaN(createdAt?.getTime()) ? 0 : Date.now() - createdAt.getTime()
        const ttlMs = Math.max(1, ttlWindowMs - Math.max(0, ageMs))
        const ttlSeconds = Math.ceil(ttlMs / 1000)

        const dealKey = `${DEAL_KEY_PREFIX}${deal.id}`
        const analyticsKey = `${DEAL_ANALYTICS_TOTAL_PREFIX}${deal.id}`
        const dealTtl = await redis.ttl(dealKey)
        if (dealTtl === -1) {
          await redis.expire(dealKey, ttlSeconds)
        }
        const analyticsTtl = await redis.ttl(analyticsKey)
        if (analyticsTtl === -1) {
          await redis.expire(analyticsKey, ttlSeconds)
        }
        if (results?.[entry.index]?.[1] === "OK") {
          created += 1
        }
      }
    }

    if (users.length) {
      await setUsersPublicInRedis(users, { ttlSecondsById: userTtlById })
    }

    console.log(`✅ Redis seeded deals: ${created} added (last ${days} days)`)
  } finally {}
}

async function seedSellersToRedis(redis, sellers = []) {
  if (!sellers.length) return 0
  const pipeline = redis.pipeline()
  sellers.forEach((seller) => {
    pipeline.hset(
      SELLERS_KEY,
      String(seller.id),
      JSON.stringify({
        id: seller.id,
        name: seller.name,
        url: seller.url ?? null,
        sellerLogo: seller.sellerLogo ?? null,
        isActive: Boolean(seller.isActive),
      })
    )
  })
  await pipeline.exec()
  return sellers.length
}

async function seedSellerDomainsToRedis(redis, sellers = []) {
  if (!sellers.length) return 0
  const pipeline = redis.pipeline()
  sellers.forEach((seller) => {
    const domains = Array.isArray(seller.domains) ? seller.domains : []
    domains.forEach((entry) => {
      if (!entry?.domain) return
      pipeline.hset(SELLER_DOMAINS_KEY, String(entry.domain).toLowerCase(), String(seller.id))
    })
  })
  await pipeline.exec()
  return sellers.length
}

async function seedCategoriesToRedis(redis, categories = []) {
  if (!categories.length) return 0
  const pipeline = redis.pipeline()
  categories.forEach((cat) => {
    pipeline.hset(
      CATEGORIES_KEY,
      String(cat.id),
      JSON.stringify({
        id: cat.id,
        name: cat.name,
        slug: cat.slug,
        parentId: cat.parentId ?? null,
        isActive: cat.isActive !== undefined ? Boolean(cat.isActive) : true,
        description: cat.description ?? "",
      })
    )
  })
  await pipeline.exec()
  return categories.length
}

async function seedReferenceDataToRedis() {
  const [sellers, categories, badges] = await Promise.all([
    findSellers({}, { include: { domains: { select: { domain: true } } } }),
    categoryDB.listCategories({ select: { id: true, name: true, slug: true, parentId: true, isActive: true, description: true } }),
    badgeDb.listBadges(),
  ])

  const redis = createRedisClient()
  try {
    await seedSellersToRedis(redis, sellers)
    await seedSellerDomainsToRedis(redis, sellers)
    await seedCategoriesToRedis(redis, categories)
    if (badges.length) await setBadgesInRedis(badges)
    console.log(
      `✅ Redis seeded reference data: sellers=${sellers.length} categories=${categories.length} badges=${badges.length}`
    )
  } finally {}
}

module.exports = { seedRecentDealsToRedis, seedReferenceDataToRedis, mapDealToRedisJson }

