const categoryDb = require("../db/category.db")
const sellerDb = require("../db/seller.db")
const { slugify } = require("../utils/slugify")
const {
  listCategoriesFromRedis,
  setCategoryInRedis,
  setCategoriesInRedis,
  getCategoryById,
} = require("./redis/categoryCache.service")
const {
  listSellersFromRedis,
  setSellerInRedis,
  setSellersInRedis,
  getSellerById,
  setSellerDomainInRedis,
} = require("./redis/sellerCache.service")
const {
  queueCategoryUpsert,
  queueSellerUpsert,
  queueSellerDomainUpsert,
} = require("./redis/dbSync.service")
const { ensureCategoryIdCounter, generateCategoryId } = require("./redis/categoryId.service")
const { ensureSellerIdCounter, generateSellerId } = require("./redis/sellerId.service")

function httpError(statusCode, message) {
  const err = new Error(message)
  err.statusCode = statusCode
  return err
}

function normalizeCategoryPayload(input = {}, fallback = {}) {
  const name = input.name !== undefined ? String(input.name || "").trim() : fallback.name
  const rawSlug = input.slug !== undefined ? String(input.slug || "").trim() : fallback.slug
  const slug = rawSlug ? slugify(rawSlug) : name ? slugify(name) : fallback.slug
  const description =
    input.description !== undefined ? String(input.description || "").trim() : fallback.description
  const parentId =
    input.parentId !== undefined && input.parentId !== null
      ? Number(input.parentId)
      : input.parentId === null
        ? null
        : fallback.parentId ?? null
  const isActive =
    input.isActive !== undefined ? Boolean(input.isActive) : Boolean(fallback.isActive ?? true)

  return { name, slug, description, parentId, isActive }
}

async function ensureCategoryParent(parentId) {
  if (parentId === null || parentId === undefined) return null
  const pid = Number(parentId)
  if (!Number.isInteger(pid) || pid < 0) throw httpError(400, "INVALID_PARENT_ID")
  if (pid === 0) return 0
  const cached = await getCategoryById(pid)
  if (cached) return pid
  const fromDb = await categoryDb.findCategoryById(pid, { select: { id: true, name: true, slug: true, parentId: true, isActive: true, description: true } })
  if (!fromDb) throw httpError(404, "CATEGORY_PARENT_NOT_FOUND")
  await setCategoryInRedis(fromDb)
  return pid
}

async function listCategoriesCached() {
  let categories = await listCategoriesFromRedis()
  if (!categories.length) {
    categories = await categoryDb.listCategories({
      select: { id: true, name: true, slug: true, parentId: true, isActive: true, description: true },
      orderBy: { id: "asc" },
    })
    if (categories.length) await setCategoriesInRedis(categories)
  }

  return categories
    .map((cat) => ({
      id: cat.id,
      name: cat.name,
      slug: cat.slug,
      parentId: cat.parentId ?? null,
      isActive: cat.isActive !== undefined ? Boolean(cat.isActive) : true,
      description: cat.description ?? "",
    }))
    .sort((a, b) => a.id - b.id)
}

async function createCategory(input = {}) {
  const payload = normalizeCategoryPayload(input)
  if (!payload.name) throw httpError(400, "CATEGORY_NAME_REQUIRED")
  if (!payload.slug) throw httpError(400, "CATEGORY_SLUG_REQUIRED")

  const categories = await listCategoriesCached()
  const duplicate = categories.find((c) => c.slug === payload.slug)
  if (duplicate) throw httpError(400, "CATEGORY_SLUG_EXISTS")

  await ensureCategoryIdCounter()
  const id = await generateCategoryId()
  const parentId = await ensureCategoryParent(payload.parentId ?? null)

  const category = {
    id,
    name: payload.name,
    slug: payload.slug,
    parentId: parentId ?? null,
    isActive: payload.isActive,
    description: payload.description ?? "",
  }

  await setCategoryInRedis(category)
  queueCategoryUpsert({
    categoryId: id,
    data: {
      name: category.name,
      slug: category.slug,
      parentId: category.parentId,
      isActive: category.isActive,
      description: category.description ?? "",
    },
    updatedAt: new Date().toISOString(),
  }).catch((err) => console.error("DB sync category create failed:", err?.message || err))

  return category
}

async function updateCategory(categoryId, input = {}) {
  const id = Number(categoryId)
  if (!Number.isInteger(id) || id < 0) throw httpError(400, "INVALID_CATEGORY_ID")

  const cached = await getCategoryById(id)
  const existing =
    cached ||
    (await categoryDb.findCategoryById(id, {
      select: { id: true, name: true, slug: true, parentId: true, isActive: true, description: true },
    }))
  if (!existing) throw httpError(404, "CATEGORY_NOT_FOUND")

  const payload = normalizeCategoryPayload(input, existing)
  if (!payload.name) throw httpError(400, "CATEGORY_NAME_REQUIRED")
  if (!payload.slug) throw httpError(400, "CATEGORY_SLUG_REQUIRED")
  if (payload.parentId !== null && Number(payload.parentId) === id) {
    throw httpError(400, "INVALID_PARENT_ID")
  }

  const categories = await listCategoriesCached()
  const duplicate = categories.find((c) => c.slug === payload.slug && Number(c.id) !== id)
  if (duplicate) throw httpError(400, "CATEGORY_SLUG_EXISTS")

  const parentId = await ensureCategoryParent(payload.parentId ?? null)
  const category = {
    id,
    name: payload.name,
    slug: payload.slug,
    parentId: parentId ?? null,
    isActive: payload.isActive,
    description: payload.description ?? "",
  }

  await setCategoryInRedis(category)
  queueCategoryUpsert({
    categoryId: id,
    data: {
      name: category.name,
      slug: category.slug,
      parentId: category.parentId,
      isActive: category.isActive,
      description: category.description ?? "",
    },
    updatedAt: new Date().toISOString(),
  }).catch((err) => console.error("DB sync category update failed:", err?.message || err))

  return category
}

function normalizeSellerPayload(input = {}, fallback = {}) {
  const name = input.name !== undefined ? String(input.name || "").trim() : fallback.name
  const url = input.url !== undefined ? String(input.url || "").trim() : fallback.url
  const sellerLogo =
    input.sellerLogo !== undefined ? String(input.sellerLogo || "").trim() : fallback.sellerLogo
  const isActive =
    input.isActive !== undefined ? Boolean(input.isActive) : Boolean(fallback.isActive ?? true)
  return { name, url: url ?? "", sellerLogo: sellerLogo ?? "", isActive }
}

async function listSellersCached() {
  let sellers = await listSellersFromRedis()
  if (!sellers.length) {
    sellers = await sellerDb.findSellers({}, {
      select: { id: true, name: true, url: true, sellerLogo: true, isActive: true },
      orderBy: { name: "asc" },
    })
    if (sellers.length) await setSellersInRedis(sellers)
  }
  return sellers.map((seller) => ({
    id: seller.id,
    name: seller.name,
    url: seller.url ?? "",
    sellerLogo: seller.sellerLogo ?? "",
    isActive: seller.isActive !== undefined ? Boolean(seller.isActive) : true,
  }))
}

async function createSeller(input = {}, { createdById } = {}) {
  const payload = normalizeSellerPayload(input)
  if (!payload.name) throw httpError(400, "SELLER_NAME_REQUIRED")
  const creatorId = Number(createdById)
  if (!Number.isInteger(creatorId) || creatorId <= 0) throw httpError(400, "CREATED_BY_REQUIRED")

  const sellers = await listSellersCached()
  const duplicate = sellers.find((s) => s.name.toLowerCase() === payload.name.toLowerCase())
  if (duplicate) throw httpError(400, "SELLER_NAME_EXISTS")

  await ensureSellerIdCounter()
  const id = await generateSellerId()
  const seller = {
    id,
    name: payload.name,
    url: payload.url ?? "",
    sellerLogo: payload.sellerLogo ?? "",
    isActive: payload.isActive,
  }

  await setSellerInRedis(seller)
  queueSellerUpsert({
    sellerId: id,
    data: {
      name: seller.name,
      url: seller.url ?? "",
      sellerLogo: seller.sellerLogo ?? "",
      isActive: seller.isActive,
      createdById: creatorId,
    },
    updatedAt: new Date().toISOString(),
  }).catch((err) => console.error("DB sync seller create failed:", err?.message || err))

  if (input.domain) {
    const domain = String(input.domain || "").trim().toLowerCase()
    if (domain) {
      await setSellerDomainInRedis(domain, id)
      queueSellerDomainUpsert({ sellerId: id, domain, createdById: creatorId }).catch((err) =>
        console.error("DB sync seller domain failed:", err?.message || err)
      )
    }
  }

  return seller
}

async function updateSeller(sellerId, input = {}, { createdById } = {}) {
  const id = Number(sellerId)
  if (!Number.isInteger(id) || id <= 0) throw httpError(400, "INVALID_SELLER_ID")
  const creatorId = Number(createdById)
  if (!Number.isInteger(creatorId) || creatorId <= 0) throw httpError(400, "CREATED_BY_REQUIRED")

  const cached = await getSellerById(id)
  const existing =
    cached ||
    (await sellerDb.findSeller({ id }, { select: { id: true, name: true, url: true, sellerLogo: true, isActive: true } }))
  if (!existing) throw httpError(404, "SELLER_NOT_FOUND")

  const payload = normalizeSellerPayload(input, existing)
  if (!payload.name) throw httpError(400, "SELLER_NAME_REQUIRED")

  const sellers = await listSellersCached()
  const duplicate = sellers.find(
    (s) => s.name.toLowerCase() === payload.name.toLowerCase() && Number(s.id) !== id
  )
  if (duplicate) throw httpError(400, "SELLER_NAME_EXISTS")

  const seller = {
    id,
    name: payload.name,
    url: payload.url ?? "",
    sellerLogo: payload.sellerLogo ?? "",
    isActive: payload.isActive,
  }

  await setSellerInRedis(seller)
  queueSellerUpsert({
    sellerId: id,
    data: {
      name: seller.name,
      url: seller.url ?? "",
      sellerLogo: seller.sellerLogo ?? "",
      isActive: seller.isActive,
    },
    updatedAt: new Date().toISOString(),
  }).catch((err) => console.error("DB sync seller update failed:", err?.message || err))

  if (input.domain) {
    const domain = String(input.domain || "").trim().toLowerCase()
    if (domain) {
      await setSellerDomainInRedis(domain, id)
      queueSellerDomainUpsert({ sellerId: id, domain, createdById: creatorId }).catch((err) =>
        console.error("DB sync seller domain failed:", err?.message || err)
      )
    }
  }

  return seller
}

module.exports = {
  listCategoriesCached,
  createCategory,
  updateCategory,
  listSellersCached,
  createSeller,
  updateSeller,
}
