const { ObjectId } = require('mongodb');
const Quizzes = require('../models/quiz'); // Adjust the path as necessary

describe('Quizzes', () => {
    let db;
    let quizzes;
    let collection;

    beforeEach(() => {
        jest.clearAllMocks(); // Clear any previous mock calls

        // Mock the collection object
        collection = {
            findOne: jest.fn(),
            insertOne: jest.fn(),
            find: jest.fn().mockReturnValue({ toArray: jest.fn() }), // Mock the find method
            deleteOne: jest.fn(),
            deleteMany: jest.fn(),
            updateOne: jest.fn(),
            getContent: jest.fn(),
        };

        // Mock the database connection
        db = {
            connect: jest.fn(),
            getConnection: jest.fn().mockReturnValue({
                collection: jest.fn().mockReturnValue(collection),
            }),
        };

        // Initialize the Quiz model with the mocked db
        quizzes = new Quizzes(db);
    });

    describe('create', () => {
        it('should create a new quiz if it does not exist', async () => {
            const title = 'Test Quiz';
            const content = 'This is a test quiz.';
            const folderId = '507f1f77bcf86cd799439011';
            const userId = '12345';

            // Mock the database response
            collection.findOne.mockResolvedValue(null);
            collection.insertOne.mockResolvedValue({ insertedId: new ObjectId() });

            const result = await quizzes.create(title, content, folderId, userId);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.findOne).toHaveBeenCalledWith({ title, folderId, userId });
            expect(collection.insertOne).toHaveBeenCalledWith(expect.objectContaining({
                folderId,
                userId,
                title,
                content,
                created_at: expect.any(Date),
                updated_at: expect.any(Date),
            }));
            expect(result).not.toBeNull();
        });

        it('should throw exception if the quiz already exists', async () => {
            const title = 'Test Quiz';
            const content = 'This is a test quiz.';
            const folderId = '507f1f77bcf86cd799439011';
            const userId = '12345';

            // Mock the database response
            collection.findOne.mockResolvedValue({ title });

            await expect(quizzes.create(title, content, folderId, userId)).rejects.toThrow(`Quiz already exists with title: ${title}, folderId: ${folderId}, userId: ${userId}`);
        });
    });

    describe('getOwner', () => {
        it('should return the owner of the quiz', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const userId = '12345';

            // Mock the database response
            collection.findOne.mockResolvedValue({ userId });

            const result = await quizzes.getOwner(quizId);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.findOne).toHaveBeenCalledWith({ _id: ObjectId.createFromHexString(quizId) });
            expect(result).toBe(userId);
        });
    });

    describe('getContent', () => {
        it('should return the content of the quiz', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const content = 'This is a test quiz.';

            // Mock the database response
            collection.findOne.mockResolvedValue({ content });

            const result = await quizzes.getContent(quizId);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.findOne).toHaveBeenCalledWith({ _id: ObjectId.createFromHexString(quizId) });
            expect(result).toEqual({ content });
        });
    });

    describe('delete', () => {
        it('should delete the quiz', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';

            // Mock the database response
            collection.deleteOne.mockResolvedValue({deletedCount: 1});

            await quizzes.delete(quizId);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.deleteOne).toHaveBeenCalledWith({ _id: ObjectId.createFromHexString(quizId) });
        });
    
        it('should return false if the quiz does not exist', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
    
            // Mock the database response
            collection.deleteOne.mockResolvedValue({deletedCount: 0});
    
            const result = await quizzes.delete(quizId);
    
            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.deleteOne).toHaveBeenCalledWith({ _id: ObjectId.createFromHexString(quizId) });
            expect(result).toBe(false);
        });
    });

    // deleteQuizzesByFolderId
    describe('deleteQuizzesByFolderId', () => {
        it('should delete all quizzes in a folder', async () => {
            const folderId = '60c72b2f9b1d8b3a4c8e4d3b';

            // Mock the database response
            collection.deleteMany.mockResolvedValue({deletedCount: 2});

            await quizzes.deleteQuizzesByFolderId(folderId);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.deleteMany).toHaveBeenCalledWith({ folderId });
        });

        it('should return false if no quizzes are deleted', async () => {
            const folderId = '60c72b2f9b1d8b3a4c8e4d3b';

            // Mock the database response
            collection.deleteMany.mockResolvedValue({deletedCount: 0});

            const result = await quizzes.deleteQuizzesByFolderId(folderId);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.deleteMany).toHaveBeenCalledWith({ folderId });
            expect(result).toBe(false);
        });
    });

    // update
    describe('update', () => {
        it('should update the title and content of the quiz', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const newTitle = 'Updated Quiz';
            const newContent = 'This is an updated quiz.';

            // Mock the database response
            collection.updateOne.mockResolvedValue({modifiedCount: 1});

            await quizzes.update(quizId, newTitle, newContent);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.updateOne).toHaveBeenCalledWith(
                { _id: ObjectId.createFromHexString(quizId) },
                { $set: { title: newTitle, content: newContent, updated_at: expect.any(Date) } }
            );
        });

        it('should return false if the quiz does not exist', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const newTitle = 'Updated Quiz';
            const newContent = 'This is an updated quiz.';

            // Mock the database response
            collection.updateOne.mockResolvedValue({modifiedCount: 0});

            const result = await quizzes.update(quizId, newTitle, newContent);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.updateOne).toHaveBeenCalledWith(
                { _id: ObjectId.createFromHexString(quizId) },
                { $set: { title: newTitle, content: newContent, updated_at: expect.any(Date) } }
            );
            expect(result).toBe(false);
        });
    });

    // move
    describe('move', () => {
        it('should move the quiz to a new folder', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const newFolderId = '507f1f77bcf86cd799439011';

            // Mock the database response
            collection.updateOne.mockResolvedValue({modifiedCount: 1});

            await quizzes.move(quizId, newFolderId);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.updateOne).toHaveBeenCalledWith(
                { _id: ObjectId.createFromHexString(quizId) },
                { $set: { folderId: newFolderId } }
            );
        });

        it('should return false if the quiz does not exist', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const newFolderId = '507f1f77bcf86cd799439011';

            // Mock the database response
            collection.updateOne.mockResolvedValue({modifiedCount: 0});

            const result = await quizzes.move(quizId, newFolderId);

            expect(db.connect).toHaveBeenCalled();
            expect(db.getConnection).toHaveBeenCalled();
            expect(collection.updateOne).toHaveBeenCalledWith(
                { _id: ObjectId.createFromHexString(quizId) },
                { $set: { folderId: newFolderId } }
            );
            expect(result).toBe(false);
        });
    });

    // duplicate
    describe('duplicate', () => {
    
        it('should duplicate the quiz and return the new quiz ID', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const userId = '12345';
            const newQuizId = ObjectId.createFromTime(Math.floor(Date.now() / 1000)); // Corrected ObjectId creation
            const sourceQuiz = {
                title: 'Test Quiz',
                content: 'This is a test quiz.',
            };
    
            const createMock = jest.spyOn(quizzes, 'create').mockResolvedValue(newQuizId);
            // mock the findOne method
            jest.spyOn(collection, 'findOne')
                .mockResolvedValueOnce(sourceQuiz) // source quiz exists
                .mockResolvedValueOnce(null); // new name is not found

            const result = await quizzes.duplicate(quizId, userId);
    
            expect(result).toBe(newQuizId);
    
            // Ensure mocks were called correctly
            expect(createMock).toHaveBeenCalledWith(
                sourceQuiz.title + ' (1)',
                sourceQuiz.content,
                undefined,
                userId
            );
        });

        // Add test case for quizExists (name with number in parentheses)
        it('should create a new title if the quiz title already exists and ends with " (1)"', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const userId = '12345';
            const newQuizId = ObjectId.createFromTime(Math.floor(Date.now() / 1000));
            const sourceQuiz = {
                title: 'Test Quiz (1)',
                content: 'This is a test quiz.',
            };
    
            const createMock = jest.spyOn(quizzes, 'create').mockResolvedValue(newQuizId);
            // mock the findOne method
            jest.spyOn(collection, 'findOne')
                .mockResolvedValueOnce(sourceQuiz) // source quiz exists
                .mockResolvedValueOnce(null); // new name is not found
    
            const result = await quizzes.duplicate(quizId, userId);
    
            expect(result).toBe(newQuizId);
    
            // Ensure mocks were called correctly
            expect(createMock).toHaveBeenCalledWith(
                'Test Quiz (2)',
                sourceQuiz.content,
                undefined,
                userId
            );
        });

        // test case for duplication of "C (1)" but "C (2)" already exists, so it should create "C (3)"
        it('should create a new title if the quiz title already exists and ends with " (n)" but the incremented n also exists', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const userId = '12345';
            const newQuizId = ObjectId.createFromTime(Math.floor(Date.now() / 1000));
            const sourceQuiz = {
                title: 'Test Quiz (1)',
                content: 'This is a test quiz.',
            };
    
            const createMock = jest.spyOn(quizzes, 'create').mockResolvedValue(newQuizId);

            // mock the findOne method
            jest.spyOn(collection, 'findOne')
                .mockResolvedValueOnce(sourceQuiz) // source quiz exists
                .mockResolvedValueOnce({ title: 'Test Quiz (2)' }) // new name collision
                .mockResolvedValueOnce(null); // final new name is not found
    
            const result = await quizzes.duplicate(quizId, userId);
    
            expect(result).toBe(newQuizId);
    
            // Ensure mocks were called correctly
            expect(createMock).toHaveBeenCalledWith(
                'Test Quiz (3)',
                sourceQuiz.content,
                undefined,
                userId
            );
        });
        
        it('should throw an error if the quiz does not exist', async () => {
            const quizId = '60c72b2f9b1d8b3a4c8e4d3b';
            const userId = '12345';
    
            // Mock the response from getContent
            jest.spyOn(quizzes, 'getContent').mockResolvedValue(null);
    
            await expect(quizzes.duplicate(quizId, userId)).rejects.toThrow();
        });
    });
});
