const { Task, Notification, User } = require('../../../../models');
const { checkDueTasks } = require('../../../../modules/tasks/dueTaskService');
const bcrypt = require('bcrypt');

describe('dueTaskService', () => {
    let user;

    beforeEach(async () => {
        user = await User.create({
            email: 'test@example.com',
            password_digest: await bcrypt.hash('password123', 10),
            notification_preferences: {
                inApp: {
                    task_due_soon: true,
                    task_overdue: true,
                },
            },
        });
    });

    describe('checkDueTasks', () => {
        describe('notification deduplication', () => {
            it('should delete existing unread notification before creating a new one', async () => {
                const tomorrow = new Date();
                tomorrow.setDate(tomorrow.getDate() + 1);

                const task = await Task.create({
                    name: 'Test Task',
                    user_id: user.id,
                    due_date: tomorrow,
                    status: Task.STATUS.NOT_STARTED,
                });

                // Create initial notification
                const firstNotification = await Notification.createNotification(
                    {
                        userId: user.id,
                        type: 'task_due_soon',
                        title: 'Task due soon',
                        message: 'Your task "Test Task" is due tomorrow',
                        data: {
                            taskUid: task.uid,
                            taskName: task.name,
                            dueDate: task.due_date,
                            isOverdue: false,
                        },
                    }
                );

                expect(firstNotification.read_at).toBeFalsy();

                // Run the check again (simulating the next day's cron job)
                await checkDueTasks();

                // First notification should be deleted
                const deletedNotification = await Notification.findByPk(
                    firstNotification.id
                );
                expect(deletedNotification).toBeNull();

                // New notification should exist
                const notifications = await Notification.findAll({
                    where: {
                        user_id: user.id,
                        type: 'task_due_soon',
                    },
                });

                expect(notifications.length).toBe(1);
                expect(notifications[0].id).not.toBe(firstNotification.id);
                expect(notifications[0].data.taskUid).toBe(task.uid);
            });

            it('should not create duplicate if previous notification was read', async () => {
                const tomorrow = new Date();
                tomorrow.setDate(tomorrow.getDate() + 1);

                const task = await Task.create({
                    name: 'Test Task',
                    user_id: user.id,
                    due_date: tomorrow,
                    status: Task.STATUS.NOT_STARTED,
                });

                // Create and mark as read
                const firstNotification = await Notification.createNotification(
                    {
                        userId: user.id,
                        type: 'task_due_soon',
                        title: 'Task due soon',
                        message: 'Your task "Test Task" is due tomorrow',
                        data: {
                            taskUid: task.uid,
                            taskName: task.name,
                            dueDate: task.due_date,
                            isOverdue: false,
                        },
                    }
                );

                await firstNotification.markAsRead();

                // Run the check again
                const result = await checkDueTasks();

                // Should not create a new notification
                const notifications = await Notification.findAll({
                    where: {
                        user_id: user.id,
                        type: 'task_due_soon',
                    },
                });

                expect(notifications.length).toBe(1);
                expect(notifications[0].id).toBe(firstNotification.id);
                expect(result.notificationsCreated).toBe(0);
            });

            it('should not create duplicate if previous notification was dismissed', async () => {
                const tomorrow = new Date();
                tomorrow.setDate(tomorrow.getDate() + 1);

                const task = await Task.create({
                    name: 'Test Task',
                    user_id: user.id,
                    due_date: tomorrow,
                    status: Task.STATUS.NOT_STARTED,
                });

                // Create and dismiss
                const firstNotification = await Notification.createNotification(
                    {
                        userId: user.id,
                        type: 'task_due_soon',
                        title: 'Task due soon',
                        message: 'Your task "Test Task" is due tomorrow',
                        data: {
                            taskUid: task.uid,
                            taskName: task.name,
                            dueDate: task.due_date,
                            isOverdue: false,
                        },
                    }
                );

                await firstNotification.dismiss();

                // Run the check again
                const result = await checkDueTasks();

                // Should not create a new notification
                const notifications = await Notification.findAll({
                    where: {
                        user_id: user.id,
                        type: 'task_due_soon',
                    },
                });

                expect(notifications.length).toBe(1);
                expect(notifications[0].id).toBe(firstNotification.id);
                expect(result.notificationsCreated).toBe(0);
            });

            it('should handle transition from task_due_soon to task_overdue', async () => {
                const yesterday = new Date();
                yesterday.setDate(yesterday.getDate() - 1);

                const task = await Task.create({
                    name: 'Test Task',
                    user_id: user.id,
                    due_date: yesterday,
                    status: Task.STATUS.NOT_STARTED,
                });

                // Create task_due_soon notification (from when it was due tomorrow)
                await Notification.createNotification({
                    userId: user.id,
                    type: 'task_due_soon',
                    title: 'Task due soon',
                    message: 'Your task "Test Task" is due tomorrow',
                    data: {
                        taskUid: task.uid,
                        taskName: task.name,
                        dueDate: task.due_date,
                        isOverdue: false,
                    },
                });

                // Run the check (task is now overdue)
                await checkDueTasks();

                // Should create task_overdue notification
                const notifications = await Notification.findAll({
                    where: {
                        user_id: user.id,
                    },
                    order: [['created_at', 'DESC']],
                });

                // Both notifications should exist (different types)
                expect(notifications.length).toBe(2);
                expect(notifications[0].type).toBe('task_overdue');
                expect(notifications[1].type).toBe('task_due_soon');
            });

            it('should only keep one notification for the same overdue task over multiple days', async () => {
                const threeDaysAgo = new Date();
                threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);

                const task = await Task.create({
                    name: 'Test Task',
                    user_id: user.id,
                    due_date: threeDaysAgo,
                    status: Task.STATUS.NOT_STARTED,
                });

                // Simulate 3 days of cron jobs
                for (let i = 0; i < 3; i++) {
                    await checkDueTasks();
                }

                // Should only have 1 notification
                const notifications = await Notification.findAll({
                    where: {
                        user_id: user.id,
                        type: 'task_overdue',
                    },
                });

                expect(notifications.length).toBe(1);
                expect(notifications[0].data.taskUid).toBe(task.uid);
            });
        });

        describe('basic functionality', () => {
            it('should create notification for task due tomorrow', async () => {
                const tomorrow = new Date();
                tomorrow.setDate(tomorrow.getDate() + 1);

                await Task.create({
                    name: 'Test Task',
                    user_id: user.id,
                    due_date: tomorrow,
                    status: Task.STATUS.NOT_STARTED,
                });

                const result = await checkDueTasks();

                expect(result.success).toBe(true);
                expect(result.notificationsCreated).toBe(1);

                const notifications = await Notification.findAll({
                    where: {
                        user_id: user.id,
                        type: 'task_due_soon',
                    },
                });

                expect(notifications.length).toBe(1);
            });

            it('should create notification for overdue task', async () => {
                const yesterday = new Date();
                yesterday.setDate(yesterday.getDate() - 1);

                await Task.create({
                    name: 'Overdue Task',
                    user_id: user.id,
                    due_date: yesterday,
                    status: Task.STATUS.NOT_STARTED,
                });

                const result = await checkDueTasks();

                expect(result.success).toBe(true);
                expect(result.notificationsCreated).toBe(1);

                const notifications = await Notification.findAll({
                    where: {
                        user_id: user.id,
                        type: 'task_overdue',
                    },
                });

                expect(notifications.length).toBe(1);
            });

            it('should not create notification for completed tasks', async () => {
                const tomorrow = new Date();
                tomorrow.setDate(tomorrow.getDate() + 1);

                await Task.create({
                    name: 'Completed Task',
                    user_id: user.id,
                    due_date: tomorrow,
                    status: Task.STATUS.DONE,
                });

                const result = await checkDueTasks();

                expect(result.notificationsCreated).toBe(0);
            });
        });

        describe('Telegram rate limiting', () => {
            const telegramNotificationService = require('../../../../modules/telegram/telegramNotificationService');

            beforeEach(() => {
                // Mock Telegram service
                jest.spyOn(
                    telegramNotificationService,
                    'isTelegramConfigured'
                ).mockReturnValue(true);
                jest.spyOn(
                    telegramNotificationService,
                    'sendTelegramNotification'
                ).mockResolvedValue({ success: true });
            });

            afterEach(() => {
                jest.restoreAllMocks();
            });

            it('should not resend Telegram if notification recreated within 24 hours', async () => {
                // Setup user with Telegram enabled
                user.telegram_bot_token =
                    '123456789:ABCdefGHIjklMNOPQRSTUVwxyz-12345678';
                user.telegram_chat_id = '123456789';
                user.notification_preferences = {
                    dueTasks: { inApp: true, telegram: true },
                    overdueTasks: { inApp: true, telegram: true },
                };
                await user.save();

                const tomorrow = new Date();
                tomorrow.setDate(tomorrow.getDate() + 1);

                const task = await Task.create({
                    name: 'Test Task',
                    user_id: user.id,
                    due_date: tomorrow,
                    status: Task.STATUS.NOT_STARTED,
                });

                const sendTelegramSpy = jest.spyOn(
                    telegramNotificationService,
                    'sendTelegramNotification'
                );

                // First check - should send Telegram
                await checkDueTasks();

                const firstCallCount = sendTelegramSpy.mock.calls.length;
                expect(firstCallCount).toBeGreaterThan(0);

                // Get the created notification
                const notification = await Notification.findOne({
                    where: {
                        user_id: user.id,
                        type: 'task_due_soon',
                    },
                    order: [['created_at', 'DESC']],
                });

                expect(notification).not.toBeNull();
                expect(notification.channel_sent_at).toBeDefined();
                expect(notification.channel_sent_at.telegram).toBeDefined();

                // Verify notification is still within 24h threshold
                expect(notification.wasChannelRecentlySent('telegram')).toBe(
                    true
                );

                sendTelegramSpy.mockClear();

                // Second check within 24h - notification will be recreated but Telegram should NOT be resent
                await checkDueTasks();

                const secondCallCount = sendTelegramSpy.mock.calls.length;
                expect(secondCallCount).toBe(0);

                // Notification should still exist (recreated in-app)
                const notifications = await Notification.findAll({
                    where: {
                        user_id: user.id,
                        type: 'task_due_soon',
                    },
                });
                expect(notifications.length).toBe(1);
            });
        });
    });
});
