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 = "sub-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 assertSubCategoryNameUnique(categoryId, name, excludeId) {
  const exactNameRx = new RegExp(`^${escapeRegex(name)}$`, "i");
  const filter = {
    category: categoryId,
    name: exactNameRx,
  };
  if (excludeId) filter._id = { $ne: excludeId };
  const exists = await SubCategory.findOne(filter).select("_id").lean();
  if (exists) throw new AppError("Sub-category name already exists in this category", 409);
}

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

  const filter = {};
  if (status) {
    if (!ALLOWED_STATUS.has(String(status))) throw new AppError("Invalid status filter", 400);
    filter.status = String(status);
  }
  if (category) {
    assertObjectId(category, "Invalid category id");
    filter.category = category;
  }

  const searchOr = searchFilter(search, ["name", "description"]);
  if (searchOr) Object.assign(filter, searchOr);

  const [subCategories, total] = await Promise.all([
    SubCategory.find(filter)
      .populate("category", "name status")
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .lean(),
    SubCategory.countDocuments(filter),
  ]);

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

exports.getSubCategoryById = asyncHandler(async (req, res) => {
  assertObjectId(req.params.id);
  const subCategory = await SubCategory.findById(req.params.id)
    .populate("category", "name status")
    .lean();
  if (!subCategory) throw new AppError("Sub-category not found", 404);
  res.json({ subCategory });
});

exports.createSubCategory = 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 category = normalizeRequired(req.body.category);
  const status = normalizeOptional(req.body.status) || "active";

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

  const parent = await Category.findById(category).select("_id").lean();
  if (!parent) {
    deleteUploadFileByPublicUrl(imageFromFile);
    throw new AppError("Category not found", 404);
  }

  try {
    await assertSubCategoryNameUnique(category, name);

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

    const populated = await SubCategory.findById(subCategory._id)
      .populate("category", "name status")
      .lean();
    res.status(201).json({ message: "Sub-category created", subCategory: populated });
  } catch (error) {
    deleteUploadFileByPublicUrl(imageFromFile);
    throw error;
  }
});

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

  let nextCategoryId = String(subCategory.category);

  if (Object.prototype.hasOwnProperty.call(req.body, "category")) {
    const category = normalizeRequired(req.body.category);
    assertObjectId(category, "Invalid category id");
    const parent = await Category.findById(category).select("_id").lean();
    if (!parent) throw new AppError("Category not found", 404);
    nextCategoryId = category;
    subCategory.category = category;
  }

  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 assertSubCategoryNameUnique(nextCategoryId, name, subCategory._id);
    subCategory.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);
    subCategory.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);
    }
    subCategory.image = image;
  }

  if (req.file) {
    const uploadedImage = publicUploadPathFromFile(req, UPLOAD_FOLDER);
    deleteUploadFileByPublicUrl(subCategory.image);
    subCategory.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);
    }
    subCategory.status = status;
  }

  await subCategory.save();
  const populated = await SubCategory.findById(subCategory._id)
    .populate("category", "name status")
    .lean();
  res.json({ message: "Sub-category updated", subCategory: populated });
});

exports.deleteSubCategory = asyncHandler(async (req, res) => {
  assertObjectId(req.params.id);
  const subCategory = await SubCategory.findById(req.params.id);
  if (!subCategory) throw new AppError("Sub-category not found", 404);
  deleteUploadFileByPublicUrl(subCategory.image);
  await SubCategory.findByIdAndDelete(subCategory._id);
  res.json({ message: "Sub-category deleted" });
});
