
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SyncManager } from '../../../src/services/sync-manager';

// Define hoisted mocks to be accessible inside vi.mock
const { mockAdd, mockWhere, mockUpdate, mockDelete, mockSortBy } = vi.hoisted(() => {
    const mockSortBy = vi.fn();
    const mockEquals = vi.fn(() => ({ sortBy: mockSortBy }));
    const mockWhere = vi.fn(() => ({ equals: mockEquals }));
    return {
        mockAdd: vi.fn(),
        mockWhere,
        mockUpdate: vi.fn(),
        mockDelete: vi.fn(),
        mockSortBy
    };
});

// Mock Dexie
vi.mock('../../../src/lib/db', () => ({
    db: {
        syncQueue: {
            add: mockAdd,
            where: mockWhere,
            update: mockUpdate,
            delete: mockDelete,
        },
    },
}));

describe('SyncManager Service', () => {
    beforeEach(() => {
        vi.clearAllMocks();

        // Default return for sortBy (mockPendingItems)
        mockSortBy.mockResolvedValue([]);
    });

    describe('queueAction', () => {
        it('should add action to syncQueue with pending status', async () => {
            // GIVEN: A draft payload
            const payload = { draftData: { title: 'Test', content: 'Content' } as any };
            mockAdd.mockResolvedValue(1);

            // WHEN: queueAction is called
            const id = await SyncManager.queueAction('saveDraft', payload);

            // THEN: It returns the new ID
            expect(id).toBe(1);

            // AND: It calls db.syncQueue.add with correct data
            expect(mockAdd).toHaveBeenCalledWith({
                action: 'saveDraft',
                payload,
                status: 'pending',
                createdAt: expect.any(Number),
                retries: 0,
            });
        });
    });

    describe('processQueue', () => {
        it('should process pending items in order', async () => {
            // GIVEN: Pending items in queue
            const items = [
                { id: 1, action: 'saveDraft', payload: {}, status: 'pending', retries: 0 },
                { id: 2, action: 'deleteDraft', payload: { draftId: 123 }, status: 'pending', retries: 0 },
            ];

            mockSortBy.mockResolvedValue(items);

            // WHEN: processQueue is called
            await SyncManager.processQueue();

            // THEN: Items are processed
            // Verify update to 'processing'
            expect(mockUpdate).toHaveBeenCalledWith(1, { status: 'processing' });
            expect(mockUpdate).toHaveBeenCalledWith(2, { status: 'processing' });

            // Verify deletion after success (assuming success path)
            expect(mockDelete).toHaveBeenCalledWith(1);
            expect(mockDelete).toHaveBeenCalledWith(2);
        });

        it('should handle execution errors and increment retries', async () => {
            // GIVEN: An item that will fail execution (invalid action)
            const item = {
                id: 1,
                action: 'invalidAction', // Will throw error in executeAction
                payload: {},
                status: 'pending',
                retries: 0
            };

            mockSortBy.mockResolvedValue([item]);

            // WHEN: processQueue is called
            await SyncManager.processQueue();

            // THEN: Item should be updated to processing
            expect(mockUpdate).toHaveBeenCalledWith(1, { status: 'processing' });

            // AND: Should be updated with incremented retry count (not deleted)
            expect(mockUpdate).toHaveBeenCalledWith(1, {
                status: 'pending',
                retries: 1
            });
            expect(mockDelete).not.toHaveBeenCalled();
        });
    });
});
