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

function createRedisClient() {
  return getRedisClient()
}

function normalizeIds(ids = []) {
  return Array.from(
    new Set(
      (Array.isArray(ids) ? ids : [])
        .map((id) => Number(id))
        .filter((id) => Number.isInteger(id) && id > 0)
    )
  )
}

function buildTagFilter(field, values = []) {
  const list = (Array.isArray(values) ? values : [])
    .map((v) => String(v).trim())
    .filter(Boolean)
  if (!list.length) return null
  return `@${field}:{${list.join("|")}}`
}

function buildSaleTypeQuery(values = []) {
  const list = (Array.isArray(values) ? values : [])
    .map((v) => String(v).trim().toUpperCase())
    .filter(Boolean)
  if (!list.length) return null

  const hasCode = list.includes("CODE")
  const others = list.filter((v) => v !== "CODE")

  if (!hasCode) {
    return `@saletype:{${list.join("|")}}`
  }

  const codePart = "(@saletype:{CODE} @hasCouponCode:[1 1])"
  if (!others.length) return codePart
  const otherPart = `@saletype:{${others.join("|")}}`
  return `(${codePart} | ${otherPart})`
}

function buildNumericRange(field, min, max) {
  if (min == null && max == null) return null
  const lower = min == null ? "-inf" : String(min)
  const upper = max == null ? "+inf" : String(max)
  return `@${field}:[${lower} ${upper}]`
}

function buildNumericOrList(field, ids = []) {
  const list = normalizeIds(ids)
  if (!list.length) return null
  if (list.length === 1) return `@${field}:[${list[0]} ${list[0]}]`
  if (list.length <= 6) {
    return `(${list.map((id) => `@${field}:[${id} ${id}]`).join("|")})`
  }

  // compress into contiguous ranges to shorten query
  const sorted = [...list].sort((a, b) => a - b)
  const ranges = []
  let start = sorted[0]
  let prev = sorted[0]
  for (let i = 1; i < sorted.length; i += 1) {
    const current = sorted[i]
    if (current === prev + 1) {
      prev = current
      continue
    }
    ranges.push([start, prev])
    start = current
    prev = current
  }
  ranges.push([start, prev])

  if (ranges.length === 1) {
    return `@${field}:[${ranges[0][0]} ${ranges[0][1]}]`
  }
  return `(${ranges.map((r) => `@${field}:[${r[0]} ${r[1]}]`).join("|")})`
}

function buildDealSearchQuery({
  statuses,
  categoryIds,
  sellerIds,
  saleTypes,
  minPrice,
  maxPrice,
  minScore,
  maxScore,
} = {}) {
  const parts = []
  const statusFilter = buildTagFilter("status", statuses)
  if (statusFilter) parts.push(statusFilter)

  const saleTypeFilter = buildSaleTypeQuery(saleTypes)
  if (saleTypeFilter) parts.push(saleTypeFilter)

  const categoryFilter = buildNumericOrList("categoryId", categoryIds)
  if (categoryFilter) parts.push(categoryFilter)

  const sellerFilter = buildNumericOrList("sellerId", sellerIds)
  if (sellerFilter) parts.push(sellerFilter)

  const priceFilter = buildNumericRange("price", minPrice, maxPrice)
  if (priceFilter) parts.push(priceFilter)

  const scoreFilter = buildNumericRange("score", minScore, maxScore)
  if (scoreFilter) parts.push(scoreFilter)

  return parts.length ? parts.join(" ") : "*"
}

function escapeRedisSearchText(input = "") {
  return String(input)
    .replace(/\\/g, "\\\\")
    .replace(/["'@\\-]/g, "\\$&")
    .replace(/[{}()[\]|<>~*?:]/g, "\\$&")
}

function buildTextSearchQuery(term) {
  const trimmed = String(term || "").trim()
  if (!trimmed) return null
  const tokens = trimmed.split(/\s+/).filter(Boolean).map(escapeRedisSearchText)
  if (!tokens.length) return null
  const query = tokens.join(" ")
  return `(@title:(${query}) | @description:(${query}))`
}

function buildPrefixTextQuery(term) {
  const trimmed = String(term || "").trim()
  if (!trimmed) return null
  const tokens = trimmed
    .split(/\s+/)
    .filter(Boolean)
    .map(escapeRedisSearchText)
    .map((token) => `${token}*`)
  if (!tokens.length) return null
  const query = tokens.join(" ")
  return `(@title:(${query}) | @description:(${query}))`
}

function buildFuzzyTextQuery(term) {
  const trimmed = String(term || "").trim()
  if (!trimmed) return null
  const tokens = trimmed
    .split(/\s+/)
    .filter(Boolean)
    .map(escapeRedisSearchText)
    .map((token) => `%${token}%`)
  if (!tokens.length) return null
  const query = tokens.join(" ")
  return `(@title:(${query}) | @description:(${query}))`
}

function buildTitlePrefixQuery(term) {
  const trimmed = String(term || "").trim()
  if (!trimmed) return null
  const tokens = trimmed.split(/\s+/).filter(Boolean).map(escapeRedisSearchText)
  if (!tokens.length) return null
  const titleQuery = tokens.map((t) => `${t}*`).join(" ")
  return `@status:{ACTIVE} @title:(${titleQuery})`
}

function resolveSort({ sortBy, sortDir } = {}) {
  const field = String(sortBy || "createdAtTs").toLowerCase()
  const dir = String(sortDir || "desc").toUpperCase() === "ASC" ? "ASC" : "DESC"
  if (field === "score") return { field: "score", dir }
  if (field === "price") return { field: "price", dir }
  if (field === "createdat" || field === "createdatts") return { field: "createdAtTs", dir }
  return { field: "createdAtTs", dir }
}

async function aggregatePriceRange(query) {
  const redis = createRedisClient()
  try {
    const results = await redis.call(
      "FT.AGGREGATE",
      "idx:data:deals",
      query || "*",
      "GROUPBY",
      "0",
      "REDUCE",
      "MIN",
      "1",
      "@price",
      "AS",
      "minPrice",
      "REDUCE",
      "MAX",
      "1",
      "@price",
      "AS",
      "maxPrice",
      "DIALECT",
      "3"
    )

    if (!Array.isArray(results) || results.length < 2) {
      return { minPrice: null, maxPrice: null }
    }

    const row = results[1]
    if (!Array.isArray(row)) return { minPrice: null, maxPrice: null }
    const data = {}
    for (let i = 0; i < row.length; i += 2) {
      data[row[i]] = row[i + 1]
    }
    const min = data.minPrice != null ? Number(data.minPrice) : null
    const max = data.maxPrice != null ? Number(data.maxPrice) : null
    return {
      minPrice: Number.isFinite(min) ? min : null,
      maxPrice: Number.isFinite(max) ? max : null,
    }
  } finally {}
}

async function searchDeals({
  query,
  page = 1,
  limit = 20,
  sortBy = "createdAtTs",
  sortDir = "DESC",
  includeMinMax = false,
} = {}) {
  const normalizedPage = Math.max(1, Number(page) || 1)
  const normalizedLimit = Math.max(1, Math.min(Number(limit) || 20, 50))
  const offset = (normalizedPage - 1) * normalizedLimit
  const sort = resolveSort({ sortBy, sortDir })

  const redis = createRedisClient()
  try {
    const range = includeMinMax ? await aggregatePriceRange(query) : { minPrice: null, maxPrice: null }
    const results = await redis.call(
      "FT.SEARCH",
      "idx:data:deals",
      query || "*",
      "SORTBY",
      sort.field,
      sort.dir,
      "LIMIT",
      String(offset),
      String(normalizedLimit),
      "RETURN",
      "0",
      "DIALECT",
      "3"
    )

    const total = Number(results?.[0] || 0)
    const ids = Array.isArray(results) ? results.slice(1) : []
    const dealIds = ids
      .map((key) => {
        const parts = String(key).split(":")
        return Number(parts[2])
      })
      .filter((id) => Number.isInteger(id) && id > 0)

    return {
      total,
      page: normalizedPage,
      totalPages: Math.ceil(total / normalizedLimit),
      dealIds,
      minPrice: range.minPrice,
      maxPrice: range.maxPrice,
    }
  } finally {}
}

module.exports = {
  buildDealSearchQuery,
  searchDeals,
  buildTitlePrefixQuery,
  buildTextSearchQuery,
  buildPrefixTextQuery,
  buildFuzzyTextQuery,
}
