import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Feedback } from './feedback.model';
import mongoose, { Model } from 'mongoose';
import { CreateFeedbackDto } from './feedback.validation';
import { IFullUser } from '../users/users.interface';
import QueryBuilder from 'src/common/builder/QueryBuilder';
import { feedbackSearchableFields } from './feedback.constant';
import { ChatHistory } from '../chat-history/chat-history.model';
import { ImageHistory } from '../image-history/model/image-history.model';
import { IFeedbackStatus } from './feedback.interface';
import { COLLECTIONS } from 'src/common/config/consts';

@Injectable()
export class FeedbackService {
	constructor(
		@InjectModel(Feedback.name) private readonly feedbackModel: Model<Feedback>,
		@InjectModel(ChatHistory.name) private readonly chatHistoryModel: Model<ChatHistory>,
		@InjectModel(ImageHistory.name) private readonly imageHistoryModel: Model<ImageHistory>,
	) { }

	async getFeedbacks(query: Record<string, unknown> = {}) {
		let { page, limit, sortBy, sortOrder, searchTerm, ...rest } = query;

		// Build search query
		const search = searchTerm ? {
			$or: [
				{ feedback: { $regex: searchTerm, $options: 'i' } },
				{ tags: { $regex: searchTerm, $options: 'i' } },
				{ status: { $regex: searchTerm, $options: 'i' } }
			]
		} : {};

		// Match stage
		const match = {
			$and: [
				...searchTerm ? [search] : [],
				rest
			]
		};

		// Pagination
		const pageNo = page ? parseInt(page as string) : 1;
		const limitNo = limit ? parseInt(limit as string) : 10;
		const skip = (pageNo - 1) * limitNo;

		// Sort
		const sort: Record<string, 1 | -1> = {
			[sortBy as string || 'createdAt']: (parseInt(sortOrder as string) === 1 ? 1 : -1) as 1 | -1
		};
		// Aggregation pipeline
		const pipeline = [
			{
				$match: match
			},
			{
				$facet: {
					data: [
						{
							$lookup: {
								from: COLLECTIONS.users,
								localField: 'user',
								foreignField: '_id',
								as: 'user'
							}
						},
						{ $sort: sort },
						{ $skip: skip },
						{ $limit: limitNo },
						{
							$unwind: {
								path: '$user',
								preserveNullAndEmptyArrays: true
							}
						},
						{
							$project: {
								user: {
									_id: 1,
									name: 1,
									email: 1,
									isBlocked: 1,
									imageUrl: 1
								},
								feedback: 1,
								tags: 1,
								type: 1,
								comments: { $size: { $ifNull: ['$comments', []] } },
								status: 1,
								createdAt: 1,
								updatedAt: 1
							}
						}
					],
					metadata: [
						{ $count: "total" },
						{ $addFields: { page: pageNo, limit: limitNo } }
					]
				}
			}
		];

		const feedbacks = await this.feedbackModel.aggregate(pipeline).exec();

		return {
			data: feedbacks?.[0]?.data || [],
			meta: feedbacks?.[0]?.metadata?.[0] || { total: 0, page: 1, limit: 10 }
		};
	}

	async getChatFeedbackById(feedbackId: string) {
		const result = await this.feedbackModel.aggregate([
			{ $match: { _id: new mongoose.Types.ObjectId(feedbackId) } },
			{
				$lookup: {
					from: COLLECTIONS.users,
					localField: 'user',
					foreignField: '_id',
					as: 'user'
				}
			},
			{ $unwind: { path: '$user', preserveNullAndEmptyArrays: true } },
			{
				$lookup: {
					from: COLLECTIONS.subscriptions,
					localField: 'user._id',
					foreignField: 'user',
					as: 'subscription'
				}
			},
			{ $unwind: { path: '$subscription', preserveNullAndEmptyArrays: true } },
			{
				$lookup: {
					from: COLLECTIONS.chatHistories,
					localField: 'conversationId',
					foreignField: '_id',
					as: 'conversation'
				}
			},
			{ $unwind: { path: '$conversation', preserveNullAndEmptyArrays: true } },
			{
				$project: {
					user: { _id: 1, name: 1, email: 1, imageUrl: 1, role: 1 },
					subscription: { plan: 1 },
					messageId: 1,
					conversationId: 1,
					feedback: 1,
					tags: 1,
					type: 1,
					comments: 1,
					status: 1,
					partId: 1,
					createdAt: 1,
					updatedAt: 1,
					conversation: { $ifNull: ['$conversation', {}] }
				}
			}
		]).exec();

		const feedback = result?.[0];
		const message = (feedback?.conversation?.history || [])?.find((msg: any) => msg?._id?.toString() === feedback?.messageId?.toString());
		feedback.message = message || {};

		return feedback;
	}

	async getImageFeedbackById(feedbackId: string) {
		const result = await this.feedbackModel.aggregate([
			{ $match: { _id: new mongoose.Types.ObjectId(feedbackId) } },
			{
				$lookup: {
					from: COLLECTIONS.users,
					localField: 'user',
					foreignField: '_id',
					as: 'user'
				}
			},
			{ $unwind: { path: '$user', preserveNullAndEmptyArrays: true } },
			{
				$lookup: {
					from: COLLECTIONS.subscriptions,
					localField: 'user._id',
					foreignField: 'user',
					as: 'subscription'
				}
			},
			{ $unwind: { path: '$subscription', preserveNullAndEmptyArrays: true } },
			{
				$lookup: {
					from: COLLECTIONS.imageHistories,
					localField: 'imageHistoryId',
					foreignField: '_id',
					as: 'conversation'
				}
			},
			{ $unwind: { path: '$conversation', preserveNullAndEmptyArrays: true } },
			{
				$project: {
					user: { _id: 1, name: 1, email: 1, imageUrl: 1, role: 1 },
					imageId: 1,
					imageHistoryId: 1,
					feedback: 1,
					tags: 1,
					type: 1,
					comments: 1,
					status: 1,
					createdAt: 1,
					updatedAt: 1,
					conversation: { $ifNull: ['$conversation', {}] }
				}
			}
		]).exec();

		const feedback = result?.[0];
		const image = (feedback?.conversation?.images || [])?.find((img: any) => img?.id?.toString() === feedback?.imageId?.toString());
		feedback.image = image || {};

		return feedback;
	}

	async createFeedback(payload: CreateFeedbackDto, user: IFullUser) {

		if ((!payload?.feedback && (!payload.tags || payload.tags.length === 0))) {
			throw new BadRequestException('Either feedback or tags must be provided');
		}

		if (payload.type === 'chat') {
			const conversation = await this.chatHistoryModel.findOne({ _id: payload.conversationId });
			if (!conversation) {
				throw new BadRequestException('Conversation not found');
			}

			const message = conversation.history?.find((message) => message._id.toString() === payload.messageId);

			if (!message) {
				throw new BadRequestException('Message not found');
			}

			if (!payload.partId) {
				await this.chatHistoryModel.updateOne({ _id: payload.conversationId, 'history._id': payload.messageId }, { $set: { 'history.$.dislike': true, 'history.$.like': false } });
			} else {
				const part = message.parts?.find((part) => part._id.toString() === payload.partId);

				if (!part) {
					throw new BadRequestException('Message not found');
				}

				part.dislike = true;
				part.like = false;
				await conversation.save();
			}

			const msgIndex = conversation.history.findIndex((msg) => msg._id.toString() === payload.messageId);

			const userPrompt = (msgIndex > 0) ? conversation.history[msgIndex - 1].content : '';
			payload.prompt = userPrompt;

		} else if (payload.type === 'image') {
			if (!payload.imageId) {
				throw new BadRequestException('Image Id is required');
			}

			const imageHistory = await this.imageHistoryModel.findOne({ _id: payload.imageHistoryId });
			if (!imageHistory) {
				throw new BadRequestException('Image not found');
			}

			const image = imageHistory.images?.find((image) => image.id.toString() === payload.imageId);

			if (!image) {
				throw new BadRequestException('Image not found');
			}

			payload.prompt = imageHistory.prompt;
		}

		const feedback = new this.feedbackModel({ ...payload, user: user?._id, ...(payload?.partId ? { partId: payload.partId } : {}) });
		await feedback.save();
		return null;
	}

	async updateFeedbackStatus(feedbackId: string, body: { status: IFeedbackStatus }) {
		const { status } = body;
		await this.feedbackModel.updateOne(
			{ _id: new mongoose.Types.ObjectId(feedbackId) },
			{ $set: { status, updatedAt: new Date() } }
		).exec();
		return null;
	}

	async addComment(feedbackId: string, body: { comment: string }) {
		const { comment } = body;
		await this.feedbackModel.updateOne(
			{ _id: new mongoose.Types.ObjectId(feedbackId) },
			{
				$push: {
					comments: {
						$each: [{ comment, createdAt: new Date() }]
					}
				},
				$set: { updatedAt: new Date() }
			}
		).exec();
		return null;
	}

	async getMyFeedback(query: Record<string, unknown>) {

		const feedbackQuery = new QueryBuilder(
			this.feedbackModel.find({}).populate({
				path: 'comments',
				populate: {
					path: 'user',
					select: 'name imageUrl',
				},
			}),
			query)
			.search(feedbackSearchableFields)
			.filter()
			.sort()
			.paginate()
			.fields();

		const feedbacks = await feedbackQuery.modelQuery;
		return feedbacks;
	}

}
