import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { BatchProcessor } from '../src/db/batchProcessor.js';

describe('BatchProcessor', () => {
  let dbManager;
  let config;
  let batchProcessor;

  beforeEach(() => {
    vi.useFakeTimers();
    dbManager = {
      insertRows: vi.fn().mockResolvedValue(true)
    };
    config = {
      db: { schema: 'test_schema', table: 'test_table' }
    };
    batchProcessor = new BatchProcessor(dbManager, config, { batchSize: 3, flushInterval: 1000 });
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('should buffer items and not flush until batch size is reached', async () => {
    const p1 = batchProcessor.add({ rows: ['r1'] });
    const p2 = batchProcessor.add({ rows: ['r2'] });

    expect(dbManager.insertRows).not.toHaveBeenCalled();

    const p3 = batchProcessor.add({ rows: ['r3'] });
    
    // Wait for microtasks
    await Promise.resolve();
    
    expect(dbManager.insertRows).toHaveBeenCalledTimes(1);
    expect(dbManager.insertRows).toHaveBeenCalledWith({
      schema: 'test_schema',
      table: 'test_table',
      rows: ['r1', 'r2', 'r3']
    });

    await expect(p1).resolves.toBe(1);
    await expect(p2).resolves.toBe(1);
    await expect(p3).resolves.toBe(1);
  });

  it('should flush when timer expires', async () => {
    const p1 = batchProcessor.add({ rows: ['r1'] });

    expect(dbManager.insertRows).not.toHaveBeenCalled();

    vi.advanceTimersByTime(1000);
    
    // Wait for microtasks
    await Promise.resolve();

    expect(dbManager.insertRows).toHaveBeenCalledTimes(1);
    expect(dbManager.insertRows).toHaveBeenCalledWith({
      schema: 'test_schema',
      table: 'test_table',
      rows: ['r1']
    });

    await expect(p1).resolves.toBe(1);
  });

  it('should handle db error and reject all pending promises', async () => {
    dbManager.insertRows.mockRejectedValue(new Error('DB Fail'));

    const p1 = batchProcessor.add({ rows: ['r1'] });
    const p2 = batchProcessor.add({ rows: ['r2'] });
    const p3 = batchProcessor.add({ rows: ['r3'] }); // Triggers flush

    await expect(p1).rejects.toThrow('DB Fail');
    await expect(p2).rejects.toThrow('DB Fail');
    await expect(p3).rejects.toThrow('DB Fail');
  });

  it('should handle mixed batch sizes', async () => {
    // 3 items with different row counts
    const p1 = batchProcessor.add({ rows: ['r1', 'r2'] });
    const p2 = batchProcessor.add({ rows: [] }); // Empty rows
    const p3 = batchProcessor.add({ rows: ['r3'] });

    await Promise.resolve();

    expect(dbManager.insertRows).toHaveBeenCalledWith({
      schema: 'test_schema',
      table: 'test_table',
      rows: ['r1', 'r2', 'r3']
    });

    await expect(p1).resolves.toBe(2);
    await expect(p2).resolves.toBe(0);
    await expect(p3).resolves.toBe(1);
  });
});
