const { Op } = require('sequelize');
const {
	Products,
	ProductDetails,
	ProductImages,
	ProductCarts,
	sequelize,
	ProductPurchases,
	ProductPurchaseItems,
} = require('../../models');
const { errorHandler } = require('../../util');
const { formatImage, formatCase, parseJsonSafe } = require('../../util/formatter');
const { generateReferenceNumber } = require('../../util/reference-no');

exports.getAllProducts = async (req, res, next) => {
	try {
		const { search, sortByPrice } = req.query;

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

		let order = [['createdAt', 'DESC']];
		if (sortByPrice && (sortByPrice === 'ASC' || sortByPrice === 'DESC')) {
			order = [['price', sortByPrice]];
		}

		let products = await Products.findAll({
			where: whereClause,
			attributes: {
				include: [
					[
						sequelize.literal(`(
							SELECT COALESCE(SUM(stock), 0)
							FROM product_details AS pd
							WHERE pd.product_id = Products.id
						)`),
						'total_stock',
					],
				],
			},
			include: [
				{
					model: ProductImages,
					as: 'images',
					attributes: ['image', 'category', 'type'],
				},
			],
			order,
		});

		products = products.map((product) => {
			let item = product.get({ plain: true });
			item.colors = parseJsonSafe(item.colors);
			item.sizes = parseJsonSafe(item.sizes);
			item.total_stock = Number(item.total_stock);
			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),
		});
	} catch (error) {
		next(error);
	}
};

exports.getProduct = async (req, res, next) => {
	try {
		let product = await Products.findByPk(req.params.id, {
			attributes: {
				include: [
					[
						sequelize.literal(`(
							SELECT COALESCE(SUM(stock), 0)
							FROM product_details AS pd
							WHERE pd.product_id = Products.id
						)`),
						'total_stock',
					],
				],
			},
			include: [
				{
					model: ProductImages,
					as: 'images',
					attributes: ['image', 'category', 'type'],
				},
				{
					model: ProductDetails,
					as: 'details',
					attributes: ['color', 'size', 'stock'],
				},
			],
		});

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

		product = product.get({ plain: true });

		product.total_stock = Number(product.total_stock);
		product.colors = parseJsonSafe(product.colors);
		product.sizes = parseJsonSafe(product.sizes);
		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.addToCart = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { productId, color, size, quantity = 1 } = req.body;
		const { memberId } = req;

		const product = await Products.findByPk(productId);
		if (!product) {
			errorHandler('Product not found', 404);
		}
		if (product?.colors !== null && !color) {
			errorHandler('Color must be provided for this product', 400);
		}
		if (product?.sizes !== null && !size) {
			errorHandler('Size must be provided for this product', 400);
		}

		const stockWhere = { product_id: productId };
		if (color !== undefined && color !== null) stockWhere.color = color;
		if (size !== undefined && size !== null) stockWhere.size = size;

		for (const key of ['color', 'size']) {
			if (product[key] !== null) {
				if (req.body[key] === undefined || req.body[key] === null || req.body[key] === '') {
					errorHandler(`${key.charAt(0).toUpperCase() + key.slice(1)} must be provided for this product`, 400);
				}
				stockWhere[key] = req.body[key];
			}
		}

		const productStock = await ProductDetails.findOne({
			where: stockWhere,
		});

		if (!productStock) {
			errorHandler('Product variant not available', 404);
		}

		if (productStock.stock < quantity) {
			errorHandler('Insufficient stock for this variant', 400);
		}

		const existingCartItem = await ProductCarts.findOne({
			where: {
				member_id: memberId,
				product_id: productId,
				color: color || null,
				size: size || null,
			},
		});

		if (existingCartItem) {
			const newQuantity = existingCartItem.quantity + Number(quantity);
			if (productStock.stock < newQuantity) {
				errorHandler('Insufficient stock for requested quantity', 400);
			}

			await existingCartItem.update({ quantity: newQuantity, updatedAt: new Date() }, { transaction });
		} else {
			await ProductCarts.create(
				{
					member_id: memberId,
					product_id: productId,
					color: color || null,
					size: size || null,
					quantity: Number(quantity),
				},
				{ transaction },
			);
		}

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

exports.getCartItems = async (req, res, next) => {
	try {
		const { memberId } = req;

		let cartItems = await ProductCarts.findAll({
			where: { member_id: memberId },
			include: [
				{
					model: Products,
					as: 'product',
					include: [
						{
							model: ProductImages,
							as: 'images',
							attributes: ['image', 'category'],
						},
					],
				},
			],
			attributes: { exclude: ['member_id'] },
			order: [['updatedAt', 'DESC']],
		});

		const validCartItems = [];

		for (const item of cartItems) {
			const product = item.product;
			if (!product) {
				await item.destroy();
				continue;
			}

			const stockWhere = { product_id: product.id };
			if (item.color !== null) stockWhere.color = item.color;
			if (item.size !== null) stockWhere.size = item.size;

			const productDetail = await ProductDetails.findOne({ where: stockWhere });

			if (!productDetail) {
				await item.destroy();
				continue;
			}

			if (productDetail.stock === 0) {
				await item.destroy();
				continue;
			}

			if (item.quantity > productDetail.stock) {
				await item.update({ quantity: productDetail.stock });
				await item.reload();
			}

			validCartItems.push(item);
		}

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

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

exports.updateCartItem = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { quantity, cartId } = req.body;
		const { memberId } = req;

		const cartItem = await ProductCarts.findOne({
			where: { id: cartId, member_id: memberId },
			include: [{ model: Products, as: 'product' }],
		});

		if (!cartItem) {
			errorHandler('Cart item not found', 404);
		}

		const productStock = await ProductDetails.findOne({
			where: {
				product_id: cartItem.product_id,
				color: cartItem.color,
				size: cartItem.size,
			},
		});

		if (!productStock || productStock.stock < quantity) {
			errorHandler('Insufficient stock for requested quantity', 400);
		}

		await cartItem.update({ quantity: Number(quantity), updatedAt: new Date() }, { transaction });

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

exports.removeFromCart = async (req, res, next) => {
	const transaction = await sequelize.transaction();
	try {
		const { memberId } = req;

		const cartItem = await ProductCarts.findOne({
			where: { id: req.params.id, member_id: memberId },
		});

		if (!cartItem) {
			errorHandler('Cart item not found', 404);
		}

		await cartItem.destroy({ transaction });

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

exports.purchase = async (req, res, next) => {
	const { products } = req.body;

	const transaction = await sequelize.transaction();
	try {
		const { memberId } = req;

		if (Array.isArray(products) && products.length === 0) {
			errorHandler('No products provided', 400);
		}

		let totalAmount = 0;
		const purchasedProducts = [];

		for (const item of products) {
			const { id: productId, quantity, color, size } = item;

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

			if (product.colors !== null && !color) {
				errorHandler(`Color must be provided for product ${product.name}`, 400);
			}

			if (product.sizes !== null && !size) {
				errorHandler(`Size must be provided for product ${product.name}`, 400);
			}

			const stockWhere = { product_id: productId };
			if (color) stockWhere.color = color;
			if (size) stockWhere.size = size;

			const productDetail = await ProductDetails.findOne({ where: stockWhere });
			if (!productDetail) {
				errorHandler(`Product variant not available for product ${product.name}`, 404);
			}

			if (productDetail.stock < quantity) {
				errorHandler(`Insufficient stock for product ${product.name}`, 400);
			}

			totalAmount += Number(product.price) * Number(quantity);

			purchasedProducts.push({
				product_id: productId,
				quantity,
				color: color || null,
				size: size || null,
				price: product.price,
			});
		}

		const purchase = await ProductPurchases.create(
			{
				member_id: memberId,
				reference_no: await generateReferenceNumber(ProductPurchases),
				price: totalAmount,
				total: totalAmount,
			},
			{ transaction },
		);

		const purchaseItems = [];
		for (const product of purchasedProducts) {
			const purchaseItem = await ProductPurchaseItems.create(
				{
					purchase_id: purchase.id,
					product_id: product.product_id,
					quantity: product.quantity,
					color: product.color,
					size: product.size,
					price: product.price,
				},
				{ transaction },
			);
			purchaseItems.push(purchaseItem);
		}

		for (const item of purchaseItems) {
			const purchasedProduct = await ProductDetails.findOne({
				where: {
					product_id: item.product_id,
					color: item.color,
					size: item.size,
				},
				transaction,
			});

			if (purchasedProduct) {
				await purchasedProduct.update({ stock: purchasedProduct.stock - item.quantity }, { transaction });
			}

			await ProductCarts.destroy({
				where: {
					member_id: memberId,
					product_id: item.product_id,
					color: item.color,
					size: item.size,
				},
				transaction,
			});

			if (purchasedProduct && purchasedProduct.stock - item.quantity >= 0) {
				const remainingStock = purchasedProduct.stock - item.quantity;

				if (remainingStock > 0) {
					const carts = await ProductCarts.findAll({
						where: {
							product_id: item.product_id,
							color: item.color,
							size: item.size,
							quantity: { [Op.gt]: remainingStock },
						},
						transaction,
					});

					for (const cart of carts) {
						await cart.update({ quantity: remainingStock }, { transaction });
					}
				} else {
					await ProductCarts.destroy({
						where: {
							product_id: item.product_id,
							color: item.color,
							size: item.size,
						},
						transaction,
					});
				}
			}
		}

		await transaction.commit();
		return res.status(200).json({
			success: true,
			message: 'Purchase successful',
			data: {
				purchase: formatCase(purchase.get({ plain: true })),
				items: purchaseItems.map((item) => formatCase(item.get({ plain: true }))),
			},
		});
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};

exports.payPurchase = async (req, res, next) => {
	const { purchaseId, paymentMethod, serviceFee = 0, taxes = 0 } = req.body;
	const payment = Number(req.body.payment) || 0;

	const transaction = await sequelize.transaction();
	try {
		const purchase = await ProductPurchases.findOne({
			where: { id: purchaseId },
		});

		const total = Number(purchase.total);
		const totalPayment = Number(purchase.payment) + payment;
		let payment_status = 'Unpaid';
		if (totalPayment >= total) {
			payment_status = 'Paid';
		} else if (totalPayment !== total) {
			payment_status = 'Partially Paid';
		}

		purchase.payment = payment + Number(purchase.payment);
		purchase.total = total;
		purchase.payment_status = payment_status;
		purchase.payment_method = paymentMethod;
		purchase.service_fee = Number(serviceFee);
		purchase.taxes = Number(taxes);
		await purchase.save({ transaction });
		await transaction.commit();
		return res.status(200).json({
			success: true,
			message: 'Purchase successful',
			data: {
				purchase: formatCase(purchase.get({ plain: true })),
			},
		});
	} catch (error) {
		await transaction.rollback();
		next(error);
	}
};

exports.purchases = async (req, res, next) => {
	try {
		const { memberId } = req;
		const purchases = await ProductPurchases.findAll({
			where: { member_id: memberId },
			include: [
				{
					model: ProductPurchaseItems,
					as: 'items',
					include: [
						{
							model: Products,
							as: 'product',
							include: [
								{
									model: ProductImages,
									as: 'images',
									attributes: ['image', 'category'],
								},
							],
						},
					],
				},
			],
			order: [['createdAt', 'DESC']],
		});

		const formattedPurchases = purchases.map((purchase) => {
			const plainPurchase = purchase.get({ plain: true });
			plainPurchase.items = plainPurchase.items.map((item) => {
				if (item.product && item.product.images && item.product.images.length > 0) {
					item.product.images = item.product.images.map((img) => ({
						...img,
						image: formatImage(img.image, 'product-uploads'),
					}));
				}
				return item;
			});
			return formatCase(plainPurchase);
		});

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