import { describe, it, expect, vi, beforeEach } from 'vitest';
import { StatusBatchProcessor } from '../src/db/statusBatchProcessor.js';

// Create a mock RoomStatusManager
const createMockManager = () => ({
    upsertBatch: vi.fn().mockResolvedValue(undefined),
    testConnection: vi.fn().mockResolvedValue(true),
    close: vi.fn().mockResolvedValue(undefined)
});

describe('StatusBatchProcessor', () => {
    let mockManager;
    let processor;

    beforeEach(() => {
        mockManager = createMockManager();
        processor = new StatusBatchProcessor(mockManager, {
            flushInterval: 50000, // Long interval so we control flush manually
            maxBufferSize: 100
        });
    });

    const makeUpdate = (overrides = {}) => ({
        hotel_id: 1001,
        room_id: '8001',
        device_id: 'dev_001',
        ts_ms: 1700000000000,
        sys_lock_status: null,
        dev_loops: null,
        faulty_device_count: null,
        ...overrides
    });

    it('should accept and buffer a status update', () => {
        processor.add(makeUpdate());
        expect(processor.buffer.size).toBe(1);
    });

    it('should ignore null/undefined updates', () => {
        processor.add(null);
        processor.add(undefined);
        expect(processor.buffer.size).toBe(0);
    });

    it('should merge dev_loops for same device (different keys accumulate)', async () => {
        processor.add(makeUpdate({
            dev_loops: { '001010001': 100 }
        }));
        processor.add(makeUpdate({
            dev_loops: { '001011002': 50 }
        }));

        expect(processor.buffer.size).toBe(1);

        await processor.flush();

        expect(mockManager.upsertBatch).toHaveBeenCalledTimes(1);
        const rows = mockManager.upsertBatch.mock.calls[0][0];
        expect(rows).toHaveLength(1);
        expect(rows[0].dev_loops).toEqual({
            '001010001': 100,
            '001011002': 50
        });
    });

    it('should overwrite dev_loops for same key (newer wins)', async () => {
        processor.add(makeUpdate({
            dev_loops: { '001010001': 100 }
        }));
        processor.add(makeUpdate({
            dev_loops: { '001010001': 200 }
        }));

        await processor.flush();

        const rows = mockManager.upsertBatch.mock.calls[0][0];
        expect(rows[0].dev_loops['001010001']).toBe(200);
    });

    it('should take latest ts_ms across merges', () => {
        processor.add(makeUpdate({ ts_ms: 100 }));
        processor.add(makeUpdate({ ts_ms: 300 }));
        processor.add(makeUpdate({ ts_ms: 200 }));

        const entry = [...processor.buffer.values()][0];
        expect(entry.ts_ms).toBe(300);
    });

    it('should replace faulty_device_count with newer value', () => {
        processor.add(makeUpdate({
            faulty_device_count: [{ dev_type: 1, error_type: 1, error_data: 1 }]
        }));
        processor.add(makeUpdate({
            faulty_device_count: [{ dev_type: 2, error_type: 2, error_data: 0 }]
        }));

        const entry = [...processor.buffer.values()][0];
        expect(entry.faulty_device_count).toEqual([{ dev_type: 2, error_type: 2, error_data: 0 }]);
    });

    it('should keep different devices separate in the buffer', () => {
        processor.add(makeUpdate({ device_id: 'dev_001' }));
        processor.add(makeUpdate({ device_id: 'dev_002' }));

        expect(processor.buffer.size).toBe(2);
    });

    it('should clear buffer after flush', async () => {
        processor.add(makeUpdate());
        expect(processor.buffer.size).toBe(1);

        await processor.flush();

        expect(processor.buffer.size).toBe(0);
        expect(mockManager.upsertBatch).toHaveBeenCalledTimes(1);
    });

    it('should NOT throw when upsertBatch fails', async () => {
        mockManager.upsertBatch.mockRejectedValue(new Error('DB down'));

        processor.add(makeUpdate());

        // flush should not throw
        await expect(processor.flush()).resolves.not.toThrow();

        // Buffer should still be cleared even on error
        expect(processor.buffer.size).toBe(0);
    });

    it('should do nothing when flushing empty buffer', async () => {
        await processor.flush();
        expect(mockManager.upsertBatch).not.toHaveBeenCalled();
    });
});
