const Category = require("../../models/product/category");
const SubCategory = require("../../models/product/subCategory");
const AppError = require("../../utils/AppError");
const { asyncHandler } = require("../../utils/asyncHandler");
const { assertObjectId } = require("../../utils/assertObjectId");
const { getPagination, searchFilter } = require("../../utils/listQuery");
const { deleteUploadFileByPublicUrl } = require("../../utils/deleteUploadFile");
const { publicUploadPathFromFile } = require("../../utils/publicUploadPath");

const ALLOWED_STATUS = new Set(["active", "inactive"]);
const UPLOAD_FOLDER = "category";

function normalizeRequired(value) {
  return String(value ?? "").trim();
}

function normalizeOptional(value) {
  if (value === undefined || value === null) return undefined;
  return String(value).trim();
}

function escapeRegex(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

async function assertCategoryNameUnique(name, excludeId) {
  const exactNameRx = new RegExp(`^${escapeRegex(name)}$`, "i");
  const filter = { name: exactNameRx };
  if (excludeId) filter._id = { $ne: excludeId };
  const exists = await Category.findOne(filter).select("_id").lean();
  if (exists) throw new AppError("Category name already exists", 409);
}

exports.listCategories = asyncHandler(async (req, res) => {
  const { page, limit, skip } = getPagination(req.query);
  const { status, search } = req.query;

  const filter = {};
  if (status) {
    if (!ALLOWED_STATUS.has(String(status))) throw new AppError("Invalid status filter", 400);
    filter.status = String(status);
  }
  const searchOr = searchFilter(search, ["name", "description"]);
  if (searchOr) Object.assign(filter, searchOr);

  const [categories, total] = await Promise.all([
    Category.find(filter).sort({ createdAt: -1 }).skip(skip).limit(limit).lean(),
    Category.countDocuments(filter),
  ]);

  res.json({
    categories,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit) || 1,
    },
  });
});

exports.getCategoryById = asyncHandler(async (req, res) => {
  assertObjectId(req.params.id);
  const category = await Category.findById(req.params.id).lean();
  if (!category) throw new AppError("Category not found", 404);
  res.json({ category });
});

exports.createCategory = asyncHandler(async (req, res) => {
  const name = normalizeRequired(req.body.name);
  const description = normalizeRequired(req.body.description);
  const imageFromFile = publicUploadPathFromFile(req, UPLOAD_FOLDER);
  const image = normalizeRequired(imageFromFile ?? req.body.image);
  const status = normalizeOptional(req.body.status) || "active";

  if (!name || !description || !image) {
    deleteUploadFileByPublicUrl(imageFromFile);
    throw new AppError("Name, description, and image are required", 400);
  }
  if (!ALLOWED_STATUS.has(status)) {
    deleteUploadFileByPublicUrl(imageFromFile);
    throw new AppError("Invalid status", 400);
  }

  try {
    await assertCategoryNameUnique(name);

    const category = await Category.create({
      name,
      description,
      image,
      status,
    });

    res.status(201).json({ message: "Category created", category });
  } catch (error) {
    deleteUploadFileByPublicUrl(imageFromFile);
    throw error;
  }
});

exports.updateCategory = asyncHandler(async (req, res) => {
  assertObjectId(req.params.id);
  const category = await Category.findById(req.params.id);
  if (!category) throw new AppError("Category not found", 404);

  if (Object.prototype.hasOwnProperty.call(req.body, "name")) {
    const name = normalizeRequired(req.body.name);
    if (!name) throw new AppError("Name cannot be empty", 400);
    await assertCategoryNameUnique(name, category._id);
    category.name = name;
  }

  if (Object.prototype.hasOwnProperty.call(req.body, "description")) {
    const description = normalizeRequired(req.body.description);
    if (!description) throw new AppError("Description cannot be empty", 400);
    category.description = description;
  }

  if (Object.prototype.hasOwnProperty.call(req.body, "image")) {
    const image = normalizeRequired(req.body.image);
    if (!image) {
      deleteUploadFileByPublicUrl(publicUploadPathFromFile(req, UPLOAD_FOLDER));
      throw new AppError("Image cannot be empty", 400);
    }
    category.image = image;
  }

  if (req.file) {
    const uploadedImage = publicUploadPathFromFile(req, UPLOAD_FOLDER);
    deleteUploadFileByPublicUrl(category.image);
    category.image = uploadedImage;
  }

  if (Object.prototype.hasOwnProperty.call(req.body, "status")) {
    const status = normalizeRequired(req.body.status);
    if (!ALLOWED_STATUS.has(status)) {
      deleteUploadFileByPublicUrl(publicUploadPathFromFile(req, UPLOAD_FOLDER));
      throw new AppError("Invalid status", 400);
    }
    category.status = status;
  }

  await category.save();
  res.json({ message: "Category updated", category });
});

exports.deleteCategory = asyncHandler(async (req, res) => {
  assertObjectId(req.params.id);
  const category = await Category.findById(req.params.id);
  if (!category) throw new AppError("Category not found", 404);

  const linkedSubCategories = await SubCategory.countDocuments({ category: category._id });
  if (linkedSubCategories > 0) {
    throw new AppError("Cannot delete category with existing sub-categories", 409);
  }

  deleteUploadFileByPublicUrl(category.image);
  await Category.findByIdAndDelete(category._id);
  res.json({ message: "Category deleted" });
});
