import { Injectable } from '@nestjs/common';
import { CreateUserAnalyticDto, UpdateUserAnalyticDto } from './user-analytics.validation';
import { UserAnalytics } from './user-analytics.model';
import mongoose, { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { TUserAnalyticsProperty } from './user-analytics.interface';
import { DEFAULT_TIMEZONE } from 'src/common/config/timezone.config';
import moment from 'moment-timezone';

@Injectable()
export class UserAnalyticsService {

	constructor(
		@InjectModel(UserAnalytics.name) private readonly userAnalyticsModel: Model<UserAnalytics>
	) { }

	  async findAll() {
	    return await this.userAnalyticsModel.find().sort({ createdAt: -1 }).populate('user', 'name email').exec();
	  }

	// single user analytics
    async getUserAnalytics(query: { userId: string, startDate: string, endDate: string }) {
        
        const getCustomProjection = (startDate: string, endDate: string) => {
            return {
                $project: {
                    fileRead: {
                        $filter: {
                            input: '$fileRead',
                            as: 'fileRead',
                            cond: {
                                $and: [
                                    { $gte: ['$$fileRead.date', startDate] },
                                    { $lte: ['$$fileRead.date', endDate] }
                                ]
                            }
                        }
                    },
                    imageBlocked: {
                        $filter: {
                            input: '$imageBlocked',
                            as: 'imageBlocked',
                            cond: {
                                $and: [
                                    { $gte: ['$$imageBlocked.date', startDate] },
                                    { $lte: ['$$imageBlocked.date', endDate] }
                                ]
                            }
                        }
                    },
                    imageGenerated: {
                        $filter: {
                            input: '$imageGenerated',
                            as: 'imageGenerated',
                            cond: {
                                $and: [
                                    { $gte: ['$$imageGenerated.date', startDate] },
                                    { $lte: ['$$imageGenerated.date', endDate] }
                                ]
                            }
                        }
                    },
                    messageBlocked: {
                        $filter: {
                            input: '$messageBlocked',
                            as: 'messageBlocked',
                            cond: {
                                $and: [
                                    { $gte: ['$$messageBlocked.date', startDate] },
                                    { $lte: ['$$messageBlocked.date', endDate] }
                                ]
                            }
                        }
                    },
                    messageSent: {
                        $filter: {
                            input: '$messageSent',
                            as: 'messageSent',
                            cond: {
                                $and: [
                                    { $gte: ['$$messageSent.date', startDate] },
                                    { $lte: ['$$messageSent.date', endDate] }
                                ]
                            }
                        }
                    },
                    urlRead: {
                        $filter: {
                            input: '$urlRead',
                            as: 'urlRead',
                            cond: {
                                $and: [
                                    { $gte: ['$$urlRead.date', startDate] },
                                    { $lte: ['$$urlRead.date', endDate] }
                                ]
                            }
                        }
                    }
                }
            }
        }

        // Last 7 days
        const startDateOfLast7Days = moment().tz('UTC').subtract(6, 'days').format('YYYY-MM-DD')
        const endDateOfLast7Days = moment().tz('UTC').format('YYYY-MM-DD')

        // Last 30 days
        const startDateOfLast30Days = moment().tz('UTC').subtract(29, 'days').format('YYYY-MM-DD')
        const endDateOfLast30Days = moment().tz('UTC').format('YYYY-MM-DD')

        const pipeline = [
            {
                $match: {
                    user: new mongoose.Types.ObjectId(query.userId)
                }
            },
            {
                $facet: {
                    queryData: [
                        getCustomProjection(query.startDate, query.endDate),
                    ],

                    queryStats: [
                        getCustomProjection(query.startDate, query.endDate),
                        {
                            $group: {
                                _id: null,
                                fileRead: { $sum: { $sum: '$fileRead.count' } },
                                imageBlocked: { $sum: { $sum: '$imageBlocked.count' } },
                                imageGenerated: { $sum: { $sum: '$imageGenerated.count' } },
                                messageBlocked: { $sum: { $sum: '$messageBlocked.count' } },
                                messageSent: { $sum: { $sum: '$messageSent.count' } },
                                urlRead: { $sum: { $sum: '$urlRead.count' } },
                            }
                        },
                        {
                            $project: {
                                _id: 0,
                            }
                        }
                    ],

                    totalStats: [
                        // This is the total stats for the user
                        {
                            $group: {
                                _id: null,
                                fileRead: { $sum: { $sum: '$fileRead.count' } },
                                imageBlocked: { $sum: { $sum: '$imageBlocked.count' } },
                                imageGenerated: { $sum: { $sum: '$imageGenerated.count' } },
                                messageBlocked: { $sum: { $sum: '$messageBlocked.count' } },
                                messageSent: { $sum: { $sum: '$messageSent.count' } },
                                urlRead: { $sum: { $sum: '$urlRead.count' } },
                            }
                        },
                        {
                            $project: {
                                _id: 0,
                            }
                        }
                    ],
                    
                    // last 7 days stats
                    last7DaysStats: [
                        getCustomProjection(startDateOfLast7Days, endDateOfLast7Days),
                        {
                            $group: {
                                _id: null,
                                fileRead: { $sum: { $sum: '$fileRead.count' } },
                                imageBlocked: { $sum: { $sum: '$imageBlocked.count' } },
                                imageGenerated: { $sum: { $sum: '$imageGenerated.count' } },
                                messageBlocked: { $sum: { $sum: '$messageBlocked.count' } },
                                messageSent: { $sum: { $sum: '$messageSent.count' } },
                                urlRead: { $sum: { $sum: '$urlRead.count' } },
                            }
                        },
                        {
                            $project: {
                                _id: 0,
                            }
                        }
                    ],

                    // last 30 days stats
                    last30DaysStats: [
                        getCustomProjection(startDateOfLast30Days, endDateOfLast30Days),
                        {
                            $group: {
                                _id: null,
                                fileRead: { $sum: { $sum: '$fileRead.count' } },
                                imageBlocked: { $sum: { $sum: '$imageBlocked.count' } },
                                imageGenerated: { $sum: { $sum: '$imageGenerated.count' } },
                                messageBlocked: { $sum: { $sum: '$messageBlocked.count' } },
                                messageSent: { $sum: { $sum: '$messageSent.count' } },
                                urlRead: { $sum: { $sum: '$urlRead.count' } },
                            }
                        },
                        {
                            $project: {
                                _id: 0,
                            }
                        }
                    ],
                }
            }
        ]

        const res = await this.userAnalyticsModel.aggregate(pipeline).exec();

        const queryDataFromDb = res?.[0].queryData || []

        const mergedData = this.mergeData(queryDataFromDb)

        const rangeDatesArray = this.prePopulateDateArray(query.startDate, query.endDate)

        // Merge the pre-populated date array with the data
        const mergedDataWithPrePopulatedDate = []


        // Merge the pre-populated date array with the data
        rangeDatesArray?.forEach(date => {
            const found = mergedData.find(d => d.date === date.date);
            if (found) {
                mergedDataWithPrePopulatedDate.push({
                    date: date.date,
                    fileRead: found?.fileRead || 0,
                    imageBlocked: found?.imageBlocked || 0,
                    imageGenerated: found?.imageGenerated || 0,
                    messageBlocked: found?.messageBlocked || 0,
                    messageSent: found?.messageSent || 0,
                    urlRead: found?.urlRead || 0,
                });
            } else {
                mergedDataWithPrePopulatedDate.push({
                    date: date.date,
                    fileRead: 0,
                    imageBlocked: 0,
                    imageGenerated: 0,
                    messageBlocked: 0,
                    messageSent: 0,
                    urlRead: 0,
                });
            }
        });

        const queryData = mergedDataWithPrePopulatedDate
        const queryStats = res?.[0].queryStats?.[0] || {}
        const totalStats = res?.[0].totalStats?.[0] || {}
        const last7DaysStats = res?.[0].last7DaysStats?.[0] || {}
        const last30DaysStats = res?.[0].last30DaysStats?.[0] || {}
        
        return {
            queryData,
            queryStats,
            totalStats,
            last7DaysStats,
            last30DaysStats,
        }

    }

    // pre populate dates in YYYY-MM-DD based on start and end date
    private prePopulateDateArray(startDate: string, endDate: string) {
        // make a date range array for predefined date range (startDate, endDate)
        const rangeDatesArray = []; // [{ date: 'YYYY-MM-DD', fileRead: 0, imageBlocked: 0, imageGenerated: 0, messageBlocked: 0, messageSent: 0, urlRead: 0, userRegistered: 0, userSubscribed: 0, subscriptionExpired: 0 }]
        let fromRangeDate = moment(startDate);
        while (fromRangeDate.isSameOrBefore(endDate, 'day')) {
            rangeDatesArray.push(
                {
                    date: fromRangeDate.format('YYYY-MM-DD'),
                    fileRead: 0,
                    imageBlocked: 0,
                    imageGenerated: 0,
                    messageBlocked: 0,
                    messageSent: 0,
                    urlRead: 0,
                    userRegistered: 0,
                    userSubscribed: 0,
                    subscriptionExpired: 0
                }
            );
            fromRangeDate.add(1, 'day');
        }
        return rangeDatesArray
    }

    private mergeData(data: any[]) {
        const mergedData = {};

        // Loop through the input data
        data.forEach(entry => {
            // Loop through each type of action (fileRead, imageBlocked, ...)
            Object.keys(entry)?.forEach(actionType => {
                // if _id then skip
                if (actionType === '_id') {
                    return;
                }
                // Loop through each date for the specific action type
                entry[actionType]?.forEach((action: { date: string, count: number }) => {
                    const date = action.date;
                    const count = action.count;

                    // If the date is not in mergedData, add it; otherwise, update the count
                    if (!mergedData[date]) {
                        mergedData[date] = { date, [actionType]: count };
                    } else {
                        mergedData[date][actionType] = (mergedData[date][actionType] || 0) + count;
                    }
                });
            });
        });

        // Convert mergedData object to an array of objects
        const resultArray: any[] = Object.values(mergedData);

        return resultArray;
    }

	async findOneByUser(userId: string) {
		return await this.userAnalyticsModel.findOne({ user: userId }).populate('user', 'name email').exec();
	}

	async updateByuser(userId: string, updateUserAnalyticDto: UpdateUserAnalyticDto) {
		return await this.userAnalyticsModel.findOneAndUpdate({ user: userId }, updateUserAnalyticDto, { new: true }).exec();
	}


	async findById(id: string) {
		return await this.userAnalyticsModel.findById(id).populate('user', 'name email').exec();
	}

	async updateById(id: string, updateUserAnalyticDto: UpdateUserAnalyticDto) {
		return await this.userAnalyticsModel.findByIdAndUpdate(id, updateUserAnalyticDto, { new: true }).exec();
	}




	async incrementAnalytics(property: TUserAnalyticsProperty, userid: string, session?: mongoose.ClientSession) {
		const today = moment().tz(DEFAULT_TIMEZONE).format('YYYY-MM-DD');

		// Check if the document with year and month exists
		const existingDocument = await this.userAnalyticsModel.findOne({
			user: userid,
			[`${property}.date`]: today
		});

		if (existingDocument) {
			// If the document exists, increment the count for the specified date
			await this.userAnalyticsModel.findOneAndUpdate(
				{
					user: userid,
					[`${property}.date`]: today,
				},
				{ $inc: { [`${property}.$.count`]: 1 } },
				{ new: true, ...(session ? { session } : {}) }
			);

			return { success: true, message: 'Analytics updated' }
		}

		// If the document doesn't exist, create a new one with count 1
		await this.userAnalyticsModel.findOneAndUpdate(
			{
				user: userid
			},
			{ $addToSet: { [property]: { date: today, count: 1 } } },
			{ new: true, upsert: true, ...(session ? { session } : {}) }
		);

		return { success: true, message: 'Analytics updated' }
	}
}
