// services/deal.service.js
const dealDB = require("../db/deal.db")
const { findSellerFromLink } = require("./sellerLookup.service")
const { makeDetailWebp, makeThumbWebp } = require("../utils/processImage")
const { v4: uuidv4 } = require("uuid")
const { uploadImage } = require("./uploadImage.service")
const categoryDB = require("../db/category.db")
const dealImageDB = require("../db/dealImage.db")
const { enqueueDealClassification } = require("../jobs/dealClassification.queue")

const DEFAULT_LIMIT = 20
const MAX_LIMIT = 50
const MAX_SKIP = 5000
const MS_PER_DAY = 24 * 60 * 60 * 1000

const DEAL_CARD_SELECT = {
  id: true,
  title: true,
  description: true,
  price: true,
  originalPrice: true,
  shippingPrice: true,
  score: true,
  commentCount: true,
  url: true,
  status: true,
  saletype: true,
  affiliateType: true,
  createdAt: true,
  updatedAt: true,
  customSeller: true,
  user: { select: { id: true, username: true, avatarUrl: true } },
  seller: { select: { id: true, name: true, url: true } },
  images: {
    orderBy: { order: "asc" },
    take: 1,
    select: { imageUrl: true },
  },
}

const DEAL_DETAIL_SELECT = {
  id: true,
  title: true,
  description: true,
  url: true,
  price: true,
  originalPrice: true,
  shippingPrice: true,
  score: true,
  commentCount: true,
  status: true,
  saletype: true,
  affiliateType: true,
  createdAt: true,
  updatedAt: true,
  categoryId: true,
  sellerId: true,
  customSeller: true,
  user: { select: { id: true, username: true, avatarUrl: true } },
  seller: { select: { id: true, name: true, url: true } },
  images: { orderBy: { order: "asc" }, select: { id: true, imageUrl: true, order: true } },
  notices: {
    where: { isActive: true },
    orderBy: { createdAt: "desc" },
    take: 1,
    select: {
      id: true,
      dealId: true,
      title: true,
      body: true,
      severity: true,
      isActive: true,
      createdBy: true,
      createdAt: true,
      updatedAt: true,
    },
  },
  _count: { select: { comments: true } },
}

const SIMILAR_DEAL_SELECT = {
  id: true,
  title: true,
  price: true,
  score: true,
  createdAt: true,
  categoryId: true,
  sellerId: true,
  customSeller: true,
  seller: { select: { name: true } },
  images: { take: 1, orderBy: { order: "asc" }, select: { imageUrl: true } },
}

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

function clampPagination({ page, limit }) {
  const rawPage = Number(page)
  const rawLimit = Number(limit)
  const normalizedPage = Number.isFinite(rawPage) ? Math.max(1, Math.floor(rawPage)) : 1
  let normalizedLimit = Number.isFinite(rawLimit) ? Math.max(1, Math.floor(rawLimit)) : DEFAULT_LIMIT
  normalizedLimit = Math.min(MAX_LIMIT, normalizedLimit)
  const skip = (normalizedPage - 1) * normalizedLimit
  if (skip > MAX_SKIP) {
    const err = new Error("PAGE_TOO_DEEP")
    err.statusCode = 400
    throw err
  }
  return { page: normalizedPage, limit: normalizedLimit, skip }
}

function buildSearchClause(q) {
  if (q === undefined || q === null) return null
  const normalized = String(q).trim()
  if (!normalized) return null
  return {
    OR: [
      { title: { contains: normalized, mode: "insensitive" } },
      { description: { contains: normalized, mode: "insensitive" } },
    ],
  }
}

const DEAL_STATUSES = new Set(["PENDING", "ACTIVE", "EXPIRED", "REJECTED"])
const SALE_TYPES = new Set(["ONLINE", "OFFLINE", "CODE"])
const AFFILIATE_TYPES = new Set(["AFFILIATE", "NON_AFFILIATE", "USER_AFFILIATE"])

function normalizeListInput(value) {
  if (Array.isArray(value)) {
    return value.flatMap((item) => String(item).split(","))
  }
  if (value === undefined || value === null) return []
  return String(value).split(",")
}

function parseInteger(value) {
  if (value === undefined || value === null || value === "") return null
  const num = Number(value)
  return Number.isInteger(num) ? num : null
}

function parseNumber(value) {
  if (value === undefined || value === null || value === "") return null
  const num = Number(value)
  return Number.isFinite(num) ? num : null
}

function parseBoolean(value) {
  if (typeof value === "boolean") return value
  if (typeof value === "number") return value === 1 ? true : value === 0 ? false : null
  if (typeof value === "string") {
    const normalized = value.trim().toLowerCase()
    if (["true", "1", "yes"].includes(normalized)) return true
    if (["false", "0", "no"].includes(normalized)) return false
  }
  return null
}

function parseDate(value) {
  if (value === undefined || value === null || value === "") return null
  const date = new Date(value)
  return Number.isNaN(date.getTime()) ? null : date
}

function parseIdList(value) {
  const items = normalizeListInput(value)
  const ids = items
    .map((item) => parseInteger(String(item).trim()))
    .filter((item) => item && item > 0)
  return ids.length ? Array.from(new Set(ids)) : null
}

function parseEnumList(value, allowedSet) {
  const items = normalizeListInput(value)
  const filtered = items
    .map((item) => String(item).trim().toUpperCase())
    .filter((item) => allowedSet.has(item))
  return filtered.length ? Array.from(new Set(filtered)) : null
}

function buildFilterWhere(rawFilters = {}, { allowStatus = false } = {}) {
  if (!rawFilters || typeof rawFilters !== "object") return null

  const clauses = []
  const categoryIds = parseIdList(rawFilters.categoryId ?? rawFilters.categoryIds)
  if (categoryIds?.length) {
    clauses.push({ categoryId: { in: categoryIds } })
  }

  const sellerIds = parseIdList(rawFilters.sellerId ?? rawFilters.sellerIds)
  if (sellerIds?.length) {
    clauses.push({ sellerId: { in: sellerIds } })
  }

  const saleTypes = parseEnumList(rawFilters.saleType, SALE_TYPES)
  if (saleTypes?.length) {
    clauses.push({ saletype: { in: saleTypes } })
  }

  const affiliateTypes = parseEnumList(rawFilters.affiliateType, AFFILIATE_TYPES)
  if (affiliateTypes?.length) {
    clauses.push({ affiliateType: { in: affiliateTypes } })
  }

  if (allowStatus) {
    const statuses = parseEnumList(rawFilters.status, DEAL_STATUSES)
    if (statuses?.length) {
      clauses.push({ status: { in: statuses } })
    }
  }

  const minPrice = parseNumber(rawFilters.minPrice ?? rawFilters.priceMin)
  const maxPrice = parseNumber(rawFilters.maxPrice ?? rawFilters.priceMax)
  if (minPrice !== null || maxPrice !== null) {
    const price = {}
    if (minPrice !== null) price.gte = minPrice
    if (maxPrice !== null) price.lte = maxPrice
    clauses.push({ price })
  }

  const minScore = parseNumber(rawFilters.minScore)
  const maxScore = parseNumber(rawFilters.maxScore)
  if (minScore !== null || maxScore !== null) {
    const score = {}
    if (minScore !== null) score.gte = minScore
    if (maxScore !== null) score.lte = maxScore
    clauses.push({ score })
  }

  const createdAfter = parseDate(rawFilters.createdAfter ?? rawFilters.from)
  const createdBefore = parseDate(rawFilters.createdBefore ?? rawFilters.to)
  if (createdAfter || createdBefore) {
    const createdAt = {}
    if (createdAfter) createdAt.gte = createdAfter
    if (createdBefore) createdAt.lte = createdBefore
    clauses.push({ createdAt })
  }

  const hasImage = parseBoolean(rawFilters.hasImage)
  if (hasImage === true) {
    clauses.push({ images: { some: {} } })
  } else if (hasImage === false) {
    clauses.push({ images: { none: {} } })
  }

  if (!clauses.length) return null
  return clauses.length === 1 ? clauses[0] : { AND: clauses }
}

function buildPresetCriteria(preset, { viewer, targetUserId } = {}) {
  const now = new Date()
  switch (preset) {
    case "NEW":
      return { where: { status: "ACTIVE" }, orderBy: [{ createdAt: "desc" }] }
    case "HOT": {
      const cutoff = new Date(now.getTime() - 3 * MS_PER_DAY)
      return {
        where: { status: "ACTIVE", createdAt: { gte: cutoff } },
        orderBy: [{ score: "desc" }, { createdAt: "desc" }],
      }
    }
    case "TRENDING": {
      const cutoff = new Date(now.getTime() - 2 * MS_PER_DAY)
      return {
        where: { status: "ACTIVE", createdAt: { gte: cutoff } },
        orderBy: [{ score: "desc" }, { createdAt: "desc" }],
      }
    }
    case "MY": {
      if (!viewer?.userId) {
        const err = new Error("AUTH_REQUIRED")
        err.statusCode = 401
        throw err
      }
      return { where: { userId: viewer.userId }, orderBy: [{ createdAt: "desc" }] }
    }
    case "USER_PUBLIC": {
      if (!targetUserId) {
        const err = new Error("TARGET_USER_REQUIRED")
        err.statusCode = 400
        throw err
      }
      return { where: { userId: targetUserId, status: "ACTIVE" }, orderBy: [{ createdAt: "desc" }] }
    }
    case "HOT_DAY": {
      const cutoff = new Date(now.getTime() - 1 * MS_PER_DAY)
      return {
        where: { status: "ACTIVE", createdAt: { gte: cutoff } },
        orderBy: [{ score: "desc" }, { createdAt: "desc" }],
      }
    }
    case "HOT_WEEK": {
      const cutoff = new Date(now.getTime() - 7 * MS_PER_DAY)
      return {
        where: { status: "ACTIVE", createdAt: { gte: cutoff } },
        orderBy: [{ score: "desc" }, { createdAt: "desc" }],
      }
    }
    case "HOT_MONTH": {
      const cutoff = new Date(now.getTime() - 30 * MS_PER_DAY)
      return {
        where: { status: "ACTIVE", createdAt: { gte: cutoff } },
        orderBy: [{ score: "desc" }, { createdAt: "desc" }],
      }
    }
    case "RAW": {
      return { where: {}, orderBy: [{ createdAt: "desc" }] }
    }
    default: {
      const err = new Error("INVALID_PRESET")
      err.statusCode = 400
      throw err
    }
  }
}

// --------------------
// Similar deals helpers (tagsiz, lightweight)
// --------------------
function clamp(n, min, max) {
  return Math.max(min, Math.min(max, n))
}

function tokenizeTitle(title = "") {
  return String(title)
    .toLowerCase()
    .replace(/[^a-z0-9çğıöşü\s]/gi, " ")
    .split(/\s+/)
    .filter(Boolean)
    .filter((w) => w.length >= 3)
}

function titleOverlapScore(aTitle, bTitle) {
  const a = tokenizeTitle(aTitle)
  const b = tokenizeTitle(bTitle)
  if (!a.length || !b.length) return 0

  const aset = new Set(a)
  const bset = new Set(b)
  let hit = 0
  for (const w of bset) if (aset.has(w)) hit++

  const denom = Math.min(aset.size, bset.size) || 1
  return hit / denom // 0..1
}

/**
 * SimilarDeals: DealCard değil, minimal summary döndürür.
 * Beklenen candidate shape:
 * - id, title, price, score, createdAt, categoryId, sellerId, customSeller
 * - seller?: { name }
 * - images?: [{ imageUrl }]
 */
async function buildSimilarDealsForDetail(targetDeal, { limit = 12 } = {}) {
  const take = clamp(Number(limit) || 12, 1, 12)

  // Bu 2 DB fonksiyonu: ACTIVE filter + images(take:1) + seller(name) getirmeli
  const [byCategory, bySeller] = await Promise.all([
    dealDB.findSimilarCandidates(
      {
        id: { not: Number(targetDeal.id) },
        status: "ACTIVE",
        categoryId: Number(targetDeal.categoryId),
      },
      { take: 80, select: SIMILAR_DEAL_SELECT }
    ),
    targetDeal.sellerId
      ? dealDB.findSimilarCandidates(
          {
            id: { not: Number(targetDeal.id) },
            status: "ACTIVE",
            sellerId: Number(targetDeal.sellerId),
          },
          { take: 30, select: SIMILAR_DEAL_SELECT }
        )
      : Promise.resolve([]),
  ])

  const dedup = new Map()
  for (const d of [...byCategory, ...bySeller]) dedup.set(d.id, d)
  const candidates = Array.from(dedup.values())

  const now = Date.now()

  const scored = candidates.map((d) => {
    const sameCategory = d.categoryId === targetDeal.categoryId
    const sameSeller = Boolean(targetDeal.sellerId && d.sellerId === targetDeal.sellerId)

    const titleSim = titleOverlapScore(targetDeal.title, d.title) // 0..1
    const titlePoints = Math.round(titleSim * 25)

    const scoreVal = Number.isFinite(d.score) ? d.score : 0
    const scorePoints = clamp(Math.round(scoreVal / 10), 0, 25)

    const ageDays = Math.floor((now - new Date(d.createdAt).getTime()) / (1000 * 60 * 60 * 24))
    const recencyPoints = ageDays <= 3 ? 10 : ageDays <= 10 ? 6 : ageDays <= 30 ? 3 : 0

    const rank =
      (sameCategory ? 60 : 0) +
      (sameSeller ? 25 : 0) +
      titlePoints +
      scorePoints +
      recencyPoints

    return { d, rank }
  })

  scored.sort((a, b) => b.rank - a.rank)

  return scored.slice(0, take).map(({ d }) => ({
    id: d.id,
    title: d.title,
    price: d.price ?? null,
    score: Number.isFinite(d.score) ? d.score : 0,
    imageUrl: d.images?.[0]?.imageUrl || "",
    sellerName: d.seller?.name || d.customSeller || "Bilinmiyor",
    createdAt: formatDateAsString(d.createdAt),
    // url istersen:
    // url: d.url ?? null,
  }))
}

async function getDeals({
  preset = "NEW",
  q,
  page,
  limit,
  viewer = null,
  targetUserId = null,
  filters = null,
  baseWhere = null,
  scope = "USER",
}) {
  const pagination = clampPagination({ page, limit })
  const { where: presetWhere, orderBy: presetOrder } = buildPresetCriteria(preset, {
    viewer,
    targetUserId,
  })
  const searchClause = buildSearchClause(q)
  const allowStatus = preset === "MY" || scope === "MOD"
  const filterWhere = buildFilterWhere(filters, { allowStatus })

  const clauses = []
  if (presetWhere && Object.keys(presetWhere).length > 0) clauses.push(presetWhere)
  if (baseWhere && Object.keys(baseWhere).length > 0) clauses.push(baseWhere)
  if (searchClause) clauses.push(searchClause)
  if (filterWhere) clauses.push(filterWhere)

  const finalWhere = clauses.length === 0 ? {} : clauses.length === 1 ? clauses[0] : { AND: clauses }
  const orderBy = presetOrder ?? [{ createdAt: "desc" }]

  const [deals, total] = await Promise.all([
    dealDB.findDeals(finalWhere, {
      skip: pagination.skip,
      take: pagination.limit,
      orderBy,
      select: DEAL_CARD_SELECT,
    }),
    dealDB.countDeals(finalWhere),
  ])

  const dealIds = deals.map((d) => d.id)
  const voteByDealId = new Map()

  if (viewer?.userId && dealIds.length > 0) {
    const votes = await dealDB.findVotes(
      { userId: viewer.userId, dealId: { in: dealIds } },
      { select: { dealId: true, voteType: true } }
    )
    votes.forEach((vote) => voteByDealId.set(vote.dealId, vote.voteType))
  }

  const enriched = deals.map((deal) => ({
    ...deal,
    myVote: voteByDealId.get(deal.id) ?? 0,
  }))

  return {
    page: pagination.page,
    total,
    totalPages: Math.ceil(total / pagination.limit),
    results: enriched,
  }
}

async function getDealById(id, viewer = null) {
  const deal = await dealDB.findDeal(
    { id: Number(id) },
    {
      select: DEAL_DETAIL_SELECT,
    }
  )

  if (!deal) return null

  const [breadcrumb, similarDeals, userStatsAgg] = await Promise.all([
    categoryDB.getCategoryBreadcrumb(deal.categoryId, { includeUndefined: false }),
    buildSimilarDealsForDetail(
      {
        id: deal.id,
        title: deal.title,
        categoryId: deal.categoryId,
        sellerId: deal.sellerId ?? null,
      },
      { limit: 12 }
    ),
    deal.user?.id ? dealDB.aggregateDeals({ userId: deal.user.id }) : Promise.resolve(null),
  ])

  const userStats = {
    totalLikes: userStatsAgg?._sum?.score ?? 0,
    totalDeals: userStatsAgg?._count?._all ?? 0,
  }

  return {
    ...deal,
    comments: [],
    breadcrumb,
    similarDeals,
    userStats,
  }
}

async function createDeal(dealCreateData, files = []) {
  if (dealCreateData.url) {
    const seller = await findSellerFromLink(dealCreateData.url)
    if (seller) {
      dealCreateData.seller = { connect: { id: seller.id } }
      dealCreateData.customSeller = null
    }
  }

  const deal = await dealDB.createDeal(dealCreateData)

  const rows = []
  for (let i = 0; i < files.length && i < 5; i++) {
    const file = files[i]
    const order = i
    const key = uuidv4()
    const basePath = `deals/${deal.id}/${key}`
    const detailPath = `${basePath}_detail.webp`
    const thumbPath = `${basePath}_thumb.webp`
    const BUCKET = "deal"

    const detailBuffer = await makeDetailWebp(file.buffer)
    const detailUrl = await uploadImage({
      bucket: BUCKET,
      path: detailPath,
      fileBuffer: detailBuffer,
      contentType: "image/webp",
    })

    if (order === 0) {
      const thumbBuffer = await makeThumbWebp(file.buffer)
      await uploadImage({
        bucket: BUCKET,
        path: thumbPath,
        fileBuffer: thumbBuffer,
        contentType: "image/webp",
      })
    }

    rows.push({ dealId: deal.id, order, imageUrl: detailUrl })
  }

  if (rows.length > 0) {
    await dealImageDB.createManyDealImages(rows)
  }

  await enqueueDealClassification({ dealId: deal.id })

  return getDealById(deal.id)
}

module.exports = {
  getDeals,
  getDealById,
  createDeal,
}
