const { Notification, User, Task } = require('../../models');
const { createTestUser } = require('../helpers/testUtils');
const telegramNotificationService = require('../../modules/telegram/telegramNotificationService');

// Mock the Telegram notification service
jest.mock('../../modules/telegram/telegramNotificationService');

describe('Notification Telegram Rate Limiting', () => {
    let user;
    let sendTelegramSpy;

    beforeEach(async () => {
        user = await createTestUser({
            email: 'test@example.com',
            telegram_bot_token: '123456789:ABCdefGHIjklMNOPQRSTUVwxyz-12345678',
            telegram_chat_id: '123456789',
            notification_preferences: {
                dueTasks: {
                    inApp: true,
                    telegram: true,
                },
                overdueTasks: {
                    inApp: true,
                    telegram: true,
                },
            },
        });

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

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

    describe('First notification creation', () => {
        it('should send Telegram notification immediately', async () => {
            await Notification.createNotification({
                userId: user.id,
                type: 'task_due_soon',
                title: 'Task Due Soon',
                message: 'Your task is due tomorrow',
                sources: ['telegram'],
                level: 'info',
            });

            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);
            expect(sendTelegramSpy).toHaveBeenCalledWith(
                expect.objectContaining({
                    id: user.id,
                    telegram_bot_token: user.telegram_bot_token,
                    telegram_chat_id: user.telegram_chat_id,
                }),
                expect.objectContaining({
                    title: 'Task Due Soon',
                    message: 'Your task is due tomorrow',
                    level: 'info',
                })
            );
        });

        it('should mark telegram as sent in channel_sent_at', async () => {
            const notification = await Notification.createNotification({
                userId: user.id,
                type: 'task_due_soon',
                title: 'Task Due Soon',
                message: 'Your task is due tomorrow',
                sources: ['telegram'],
                level: 'info',
            });

            // Reload to get updated data
            await notification.reload();

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

            const sentTime = new Date(notification.channel_sent_at.telegram);
            expect(sentTime).toBeInstanceOf(Date);
            expect(sentTime.getTime()).toBeLessThanOrEqual(Date.now());
        });
    });

    describe('Delete and recreate pattern (navbar pile-up fix)', () => {
        it('should NOT resend Telegram when notification recreated within 24 hours', async () => {
            // Create initial notification
            const firstNotification = await Notification.createNotification({
                userId: user.id,
                type: 'task_overdue',
                title: 'Task Overdue',
                message: 'Your task is now overdue',
                sources: ['telegram'],
                data: { taskUid: 'test-task-123' },
                level: 'warning',
            });

            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);

            // Verify channel_sent_at was set
            await firstNotification.reload();
            expect(firstNotification.channel_sent_at).toBeDefined();
            expect(firstNotification.channel_sent_at.telegram).toBeDefined();
            expect(firstNotification.wasChannelRecentlySent('telegram')).toBe(
                true
            );

            sendTelegramSpy.mockClear();

            // Simulate delete-and-recreate pattern (what cron jobs do)
            const preservedChannelSentAt = firstNotification.channel_sent_at;
            await firstNotification.destroy();

            // Create new notification with preserved channel_sent_at (within 24h)
            // This simulates what the service layer does
            const secondNotification = await Notification.createNotification({
                userId: user.id,
                type: 'task_overdue',
                title: 'Task Overdue',
                message: 'Your task is still overdue',
                sources: ['telegram'],
                data: { taskUid: 'test-task-123' },
                level: 'warning',
                channel_sent_at: preservedChannelSentAt,
            });

            // Telegram should NOT have been sent again (rate limited)
            expect(sendTelegramSpy).toHaveBeenCalledTimes(0);

            // Channel tracking should be preserved
            expect(secondNotification.channel_sent_at).toEqual(
                preservedChannelSentAt
            );
            expect(secondNotification.wasChannelRecentlySent('telegram')).toBe(
                true
            );
        });

        it('should NOT send Telegram multiple times for same notification context', async () => {
            // Simulate the actual cron job pattern:
            // 1. Create notification
            // 2. Check for existing notification
            // 3. Delete if exists and unread
            // 4. Create new notification

            const createNotificationWithTelegramTracking = async () => {
                // Check for existing notification
                const existing = await Notification.findOne({
                    where: {
                        user_id: user.id,
                        type: 'task_due_soon',
                    },
                    order: [['created_at', 'DESC']],
                });

                let channelSentAt = null;
                if (existing && !existing.dismissed_at && !existing.read_at) {
                    // Preserve channel tracking before deletion
                    channelSentAt = existing.channel_sent_at;
                    await existing.destroy();
                }

                // Create new notification, preserving channel_sent_at
                const notification = await Notification.create({
                    user_id: user.id,
                    type: 'task_due_soon',
                    title: 'Task Due Soon',
                    message: 'Your task is due tomorrow',
                    sources: ['telegram'],
                    level: 'info',
                    sent_at: new Date(),
                    channel_sent_at: channelSentAt,
                });

                // Only send if not recently sent
                if (
                    !notification.wasChannelRecentlySent(
                        'telegram',
                        24 * 60 * 60 * 1000
                    )
                ) {
                    await telegramNotificationService.sendTelegramNotification(
                        user,
                        {
                            title: notification.title,
                            message: notification.message,
                            level: notification.level,
                        }
                    );
                    await notification.markChannelAsSent('telegram');
                }

                return notification;
            };

            // First creation - should send
            await createNotificationWithTelegramTracking();
            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);
            sendTelegramSpy.mockClear();

            // Second creation within 24h - should NOT send
            await createNotificationWithTelegramTracking();
            expect(sendTelegramSpy).toHaveBeenCalledTimes(0);

            // Third creation within 24h - should NOT send
            await createNotificationWithTelegramTracking();
            expect(sendTelegramSpy).toHaveBeenCalledTimes(0);
        });
    });

    describe('Telegram rate limit threshold', () => {
        it('should resend Telegram after 24 hours have passed', async () => {
            // Create notification with telegram sent 25 hours ago
            const moreThanADayAgo = new Date();
            moreThanADayAgo.setHours(moreThanADayAgo.getHours() - 25);

            const notification = await Notification.create({
                user_id: user.id,
                type: 'task_overdue',
                title: 'Task Overdue',
                message: 'Your task is still overdue',
                sources: ['telegram'],
                level: 'warning',
                sent_at: new Date(),
                channel_sent_at: {
                    telegram: moreThanADayAgo.toISOString(),
                },
            });

            // Channel was sent more than 24h ago
            expect(notification.wasChannelRecentlySent('telegram')).toBe(false);

            // Now create a new notification via createNotification
            // (simulating cron job running after 24h)
            await notification.destroy();

            await Notification.createNotification({
                userId: user.id,
                type: 'task_overdue',
                title: 'Task Overdue',
                message: 'Your task is still overdue',
                sources: ['telegram'],
                level: 'warning',
            });

            // Should have sent Telegram again
            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);
        });
    });

    describe('User dismisses notification', () => {
        it('should not create new notification if previous was dismissed', async () => {
            // This tests existing behavior - not related to rate limiting
            // but important for overall notification flow

            const notification = await Notification.createNotification({
                userId: user.id,
                type: 'task_due_soon',
                title: 'Task Due Soon',
                message: 'Your task is due tomorrow',
                sources: ['telegram'],
                level: 'info',
            });

            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);

            // User dismisses the notification
            await notification.dismiss();

            // Cron job should check for dismissed_at and NOT create new notification
            // (This is handled in the service layer, not model layer)
            // So Telegram won't be sent again
        });
    });

    describe('Different notification types', () => {
        it('should track telegram sends independently for different types', async () => {
            // Create due_soon notification
            const dueSoonNotif = await Notification.createNotification({
                userId: user.id,
                type: 'task_due_soon',
                title: 'Task Due Soon',
                message: 'Task due tomorrow',
                sources: ['telegram'],
                level: 'info',
            });

            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);
            sendTelegramSpy.mockClear();

            // Create overdue notification for same task
            // (different type, so different notification)
            const overdueNotif = await Notification.createNotification({
                userId: user.id,
                type: 'task_overdue',
                title: 'Task Overdue',
                message: 'Task is now overdue',
                sources: ['telegram'],
                level: 'warning',
            });

            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);

            // Both notifications should have their own channel_sent_at
            await dueSoonNotif.reload();
            await overdueNotif.reload();

            expect(dueSoonNotif.channel_sent_at.telegram).toBeDefined();
            expect(overdueNotif.channel_sent_at.telegram).toBeDefined();
        });
    });

    describe('Multiple tasks', () => {
        it('should rate limit each task notification independently', async () => {
            // Create notification for task 1
            const task1Notif = await Notification.createNotification({
                userId: user.id,
                type: 'task_overdue',
                title: 'Task 1 Overdue',
                message: 'Task 1 is overdue',
                sources: ['telegram'],
                data: { taskUid: 'task-1' },
                level: 'warning',
            });

            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);
            sendTelegramSpy.mockClear();

            // Create notification for task 2
            const task2Notif = await Notification.createNotification({
                userId: user.id,
                type: 'task_overdue',
                title: 'Task 2 Overdue',
                message: 'Task 2 is overdue',
                sources: ['telegram'],
                data: { taskUid: 'task-2' },
                level: 'warning',
            });

            // Should send for task 2 (different notification)
            expect(sendTelegramSpy).toHaveBeenCalledTimes(1);

            // Each has their own rate limiting
            expect(task1Notif.channel_sent_at).not.toBe(
                task2Notif.channel_sent_at
            );
        });
    });
});
