const { Products, ProductDetails, ProductImages, sequelize } = require('../../models');
const { errorHandler } = require('../../util');
const { formatImage, formatCase, parseJsonSafe } = require('../../util/formatter');
const { deleteFile } = require('../../helpers');

exports.createProduct = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { name, description, price } = req.body;
		let imageCategories = parseJsonSafe(req.body.imageCategories) || [];
		let details = parseJsonSafe(req.body.details) || [];
		const colors = parseJsonSafe(req.body.colors) || null;
		const sizes = parseJsonSafe(req.body.sizes) || null;

		let combos = [];
		if (colors && sizes) {
			for (const color of colors) {
				for (const size of sizes) {
					combos.push({ color, size });
				}
			}
		} else if (colors) {
			for (const color of colors) {
				combos.push({ color, size: null });
			}
		} else if (sizes) {
			for (const size of sizes) {
				combos.push({ color: null, size });
			}
		} else {
			combos.push({ color: null, size: null });
		}

		let total_stock = 0;
		const stockEntries = [];

		for (const combo of combos) {
			let stockValue = 0;
			if (
				(combo.color !== null && (!colors || !colors.includes(combo.color))) ||
				(combo.size !== null && (!sizes || !sizes.includes(combo.size)))
			) {
				errorHandler('Invalid color or size combination', 400);
			}
			if (details && Array.isArray(details)) {
				const found = details.find(
					(s) =>
						(s.color === combo.color || (!s.color && !combo.color)) &&
						(s.size === combo.size || (!s.size && !combo.size)),
				);

				if (found) {
					stockValue = Number(found.stock) || 0;
				}
			}

			total_stock += stockValue;

			stockEntries.push({
				color: combo.color || null,
				size: combo.size || null,
				stock: stockValue,
			});
		}

		const product = await Products.create(
			{
				name,
				description,
				price: parseFloat(price),
				colors: colors ? JSON.stringify(colors) : null,
				sizes: sizes ? JSON.stringify(sizes) : null,
				total_stock,
			},
			{ transaction },
		);

		let productDetails = [];
		for (const entry of stockEntries) {
			const newStock = await ProductDetails.create(
				{
					product_id: product.id,
					color: entry.color,
					size: entry.size,
					stock: entry.stock,
				},
				{ transaction },
			);

			productDetails.push(newStock);
		}

		const { productImgs } = req.files || {};
		if (productImgs && Array.isArray(productImgs)) {
			const imagePromises = productImgs.map((image, index) => {
				return ProductImages.create(
					{
						product_id: product.id,
						image: image.filename,
						category: imageCategories[index],
					},
					{ transaction },
				);
			});
			await Promise.all(imagePromises);
		}

		await transaction.commit();
		return res.status(201).json({
			success: true,
			data: { ...product.get({ plain: true }), total_stock, productDetails, details },
			message: 'Product created successfully',
		});
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};

exports.getAllProducts = async (req, res, next) => {
	try {
		const { search, page = 1, limit = 10 } = req.query;
		const offset = (page - 1) * limit;

		const whereClause = {};
		if (search) {
			whereClause[sequelize.Op.or] = [
				{ name: { [sequelize.Op.like]: `%${search}%` } },
				{ description: { [sequelize.Op.like]: `%${search}%` } },
			];
		}

		let products = await Products.findAndCountAll({
			where: whereClause,
			include: [
				{
					model: ProductImages,
					as: 'images',
				},
				{
					model: ProductDetails,
					as: 'details',
				},
			],
			limit: Number(limit),
			offset: Number(offset),
			order: [['createdAt', 'DESC']],
		});

		products.rows = products.rows.map((product) => {
			let item = product.get({ plain: true });
			if (item.images && item.images.length > 0) {
				item.images = item.images.map((img) => ({
					...img,
					image: formatImage(img.image, 'product-uploads'),
				}));
			}
			return item;
		});

		return res.status(200).json({
			success: true,
			data: formatCase(products.rows),
			pagination: {
				currentPage: Number(page),
				totalPages: Math.ceil(products.count / limit),
				totalItems: products.count,
				itemsPerPage: Number(limit),
			},
		});
	} catch (error) {
		next(error);
	}
};

exports.getProduct = async (req, res, next) => {
	try {
		let product = await Products.findByPk(req.params.id, {
			include: [
				{
					model: ProductImages,
					as: 'images',
				},
				{
					model: ProductDetails,
					as: 'details',
				},
			],
		});

		if (!product) {
			return res.status(404).json({ success: false, message: 'Product not found' });
		}

		product = product.get({ plain: true });
		if (product.images && product.images.length > 0) {
			product.images = product.images.map((img) => ({
				...img,
				image: formatImage(img.image, 'product-uploads'),
			}));
		}

		return res.status(200).json({ success: true, data: formatCase(product) });
	} catch (error) {
		next(error);
	}
};

exports.updateProduct = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { name, description, price, colors, sizes, productId } = req.body;

		const product = await Products.findByPk(productId);
		if (!product) {
			errorHandler('Product not found', 404);
		}

		await product.update(
			{
				name: name || product.name,
				description: description || product.description,
				price: price ? parseFloat(price) : product.price,
				colors: colors !== undefined ? colors : product.colors,
				sizes: sizes !== undefined ? sizes : product.sizes,
			},
			{ transaction },
		);

		await transaction.commit();
		return res.status(200).json({ success: true, data: product, message: 'Product updated successfully' });
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};

exports.deleteProduct = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const product = await Products.findByPk(req.params.id, {
			include: [
				{
					model: ProductImages,
					as: 'images',
				},
			],
		});

		if (!product) {
			errorHandler('Product not found', 404);
		}

		if (product.images && product.images.length > 0) {
			for (const image of product.images) {
				await deleteFile('product-uploads', image.image);
			}
		}

		await product.destroy({ transaction });

		await transaction.commit();
		return res.status(200).json({ success: true, message: 'Product deleted successfully' });
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};

exports.addProductStock = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { color, size, stock, productId } = req.body;

		const product = await Products.findByPk(productId);
		if (!product) {
			errorHandler('Product not found', 404);
		}

		const existingStock = await ProductDetails.findOne({
			where: {
				product_id: productId,
				color: color || null,
				size: size || null,
			},
		});

		if (existingStock) {
			existingStock.stock = Number(stock);
			await existingStock.save({ transaction });
			await transaction.commit();

			return res.status(200).json({
				success: true,
				data: existingStock,
				message: 'Stock variant already exists, updated stock successfully',
			});
		}

		const productStock = await ProductDetails.create(
			{
				product_id: productId,
				color: color || null,
				size: size || null,
				stock: Number(stock),
			},
			{ transaction },
		);

		await transaction.commit();
		return res.status(201).json({ success: true, data: productStock, message: 'Stock variant added successfully' });
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};

exports.deleteProductStock = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const productStock = await ProductDetails.findByPk(req.params.stockId);
		if (!productStock) {
			errorHandler('Stock variant not found', 404);
		}

		await productStock.destroy({ transaction });

		await transaction.commit();
		return res.status(200).json({ success: true, message: 'Stock variant deleted successfully' });
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};
