const fs = require('fs');
const GroupRepository = require('../../src/repositories/GroupRepository');
const DeletionLogRepository = require('../../src/repositories/DeletionLogRepository');
const GroupCleanupService = require('../../src/services/GroupCleanupService');

describe('GroupCleanupService', () => {
    beforeEach(() => {
        jest.clearAllMocks();
    });

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

    describe('getDaysUntilDeletion', () => {
        const NOW = new Date('2024-01-10T00:00:00Z');

        beforeAll(() => {
            jest.useFakeTimers();
            jest.setSystemTime(NOW);
        });

        afterAll(() => {
            jest.useRealTimers();
        });

        it('returns remaining days when future deletion date is ahead', () => {
            const days = GroupCleanupService.getDaysUntilDeletion(new Date('2024-01-05T00:00:00Z'));
            expect(days).toBe(2);
        });

        it('clamps negative differences to zero', () => {
            const days = GroupCleanupService.getDaysUntilDeletion(new Date('2023-12-01T00:00:00Z'));
            expect(days).toBe(0);
        });
    });

    describe('deletePhysicalFiles', () => {
        it('counts successful deletions and ignores missing files', async () => {
            const unlinkMock = jest.spyOn(fs.promises, 'unlink');
            unlinkMock
                .mockResolvedValueOnce()
                .mockRejectedValueOnce(Object.assign(new Error('missing'), { code: 'ENOENT' }))
                .mockRejectedValueOnce(new Error('boom'))
                .mockResolvedValueOnce();

            const result = await GroupCleanupService.deletePhysicalFiles([
                { file_path: 'images/one.jpg', preview_path: 'previews/one.jpg' },
                { file_path: 'images/two.jpg', preview_path: 'previews/two.jpg' }
            ]);

            expect(result).toEqual({ success: 2, failed: 1 });
            expect(unlinkMock).toHaveBeenCalledTimes(4);
        });
    });

    describe('findGroupsForDeletion', () => {
        it('fetches unapproved groups older than default threshold', async () => {
            const groups = [{ group_id: 'abc' }];
            const findSpy = jest
                .spyOn(GroupRepository, 'findUnapprovedGroupsOlderThan')
                .mockResolvedValue(groups);

            const result = await GroupCleanupService.findGroupsForDeletion();

            expect(findSpy).toHaveBeenCalledWith(GroupCleanupService.CLEANUP_DAYS);
            expect(result).toBe(groups);
        });
    });

    describe('deleteGroupCompletely', () => {
        it('returns null when statistics are missing', async () => {
            jest.spyOn(GroupRepository, 'getGroupStatistics').mockResolvedValue(null);
            const deleteSpy = jest.spyOn(GroupRepository, 'deleteGroupCompletely').mockResolvedValue({});

            const result = await GroupCleanupService.deleteGroupCompletely('missing-group');

            expect(result).toBeNull();
            expect(deleteSpy).not.toHaveBeenCalled();
        });

        it('removes group, files and logs deletion', async () => {
            jest.spyOn(GroupRepository, 'getGroupStatistics').mockResolvedValue({
                groupId: 'group-1',
                year: 2024,
                imageCount: 3,
                uploadDate: '2024-01-01',
                totalFileSize: 1234
            });
            jest.spyOn(GroupRepository, 'deleteGroupCompletely').mockResolvedValue({
                imagePaths: [{ file_path: 'images/a.jpg', preview_path: 'previews/a.jpg' }],
                deletedImages: 3
            });

            const deleteFilesSpy = jest
                .spyOn(GroupCleanupService, 'deletePhysicalFiles')
                .mockResolvedValue({ success: 2, failed: 0 });
            const logSpy = jest.spyOn(GroupCleanupService, 'logDeletion').mockResolvedValue();

            const result = await GroupCleanupService.deleteGroupCompletely('group-1');

            expect(deleteFilesSpy).toHaveBeenCalledWith([{ file_path: 'images/a.jpg', preview_path: 'previews/a.jpg' }]);
            expect(logSpy).toHaveBeenCalledWith(
                expect.objectContaining({ groupId: 'group-1', imageCount: 3, totalFileSize: 1234 })
            );
            expect(result).toEqual({ groupId: 'group-1', imagesDeleted: 3, filesDeleted: 2 });
        });
    });

    describe('logDeletion', () => {
        it('swallows repository errors so cleanup continues', async () => {
            jest.spyOn(DeletionLogRepository, 'createDeletionEntry').mockRejectedValue(new Error('db down'));

            await expect(
                GroupCleanupService.logDeletion({ groupId: 'g1', year: 2024, imageCount: 1, uploadDate: '2024-01-01' })
            ).resolves.toBeUndefined();
        });
    });

    describe('performScheduledCleanup', () => {
        it('returns early when there is nothing to delete', async () => {
            const findSpy = jest.spyOn(GroupCleanupService, 'findGroupsForDeletion').mockResolvedValue([]);

            const result = await GroupCleanupService.performScheduledCleanup();

            expect(findSpy).toHaveBeenCalled();
            expect(result).toEqual({
                success: true,
                deletedGroups: 0,
                message: 'No groups to delete'
            });
        });

        it('keeps track of successes and failures', async () => {
            const findSpy = jest
                .spyOn(GroupCleanupService, 'findGroupsForDeletion')
                .mockResolvedValue([{ group_id: 'g1' }, { group_id: 'g2' }]);
            const deleteSpy = jest
                .spyOn(GroupCleanupService, 'deleteGroupCompletely')
                .mockResolvedValueOnce()
                .mockRejectedValueOnce(new Error('boom'));

            const result = await GroupCleanupService.performScheduledCleanup();

            expect(findSpy).toHaveBeenCalled();
            expect(deleteSpy).toHaveBeenCalledTimes(2);
            expect(result.success).toBe(true);
            expect(result.deletedGroups).toBe(1);
            expect(result.failedGroups).toBe(1);
            expect(result.duration).toBeDefined();
        });
    });
});
