import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { resolve } from 'path';
import { mkdtemp, rm } from 'fs/promises';
import { tmpdir } from 'os';
import { QueuePersistence } from './persistence.js';
import { readJSON } from '../data-store/file-ops.js';
import type { QueueItem } from '../../types/queue.js';

function makeItem(overrides: Partial<QueueItem> = {}): QueueItem {
  return {
    id: overrides.id ?? 'item-1',
    type: overrides.type ?? 'chat_response',
    priority: overrides.priority ?? 2,
    status: overrides.status ?? 'pending',
    characterId: overrides.characterId ?? 'char-1',
    payload: overrides.payload ?? {},
    retryCount: overrides.retryCount ?? 0,
    maxRetries: overrides.maxRetries ?? 3,
    createdAt: overrides.createdAt ?? '2024-01-01T00:00:00Z',
    updatedAt: overrides.updatedAt ?? '2024-01-01T00:00:00Z',
    error: overrides.error,
  };
}

describe('QueuePersistence', () => {
  let tempDir: string;
  let persistence: QueuePersistence;

  beforeEach(async () => {
    tempDir = await mkdtemp(resolve(tmpdir(), 'queue-persist-'));
    persistence = new QueuePersistence(tempDir);
    await persistence.init();
  });

  afterEach(async () => {
    await rm(tempDir, { recursive: true, force: true });
  });

  describe('init', () => {
    it('should create the queue directory', async () => {
      const fresh = new QueuePersistence(tempDir);
      await fresh.init();
      // No error means directory was created successfully
    });
  });

  describe('savePending', () => {
    it('should separate pending and failed items into different files', async () => {
      const items: QueueItem[] = [
        makeItem({ id: 'p1', status: 'pending' }),
        makeItem({ id: 'p2', status: 'processing' }),
        makeItem({ id: 'f1', status: 'failed', error: 'timeout' }),
        makeItem({ id: 'c1', status: 'completed' }),
      ];

      await persistence.savePending(items);

      const pendingFile = await readJSON<QueueItem[]>(resolve(tempDir, 'queue', 'pending.json'));
      const failedFile = await readJSON<QueueItem[]>(resolve(tempDir, 'queue', 'failed.json'));

      expect(pendingFile).toHaveLength(2);
      expect(pendingFile!.map(i => i.id).sort()).toEqual(['p1', 'p2']);

      expect(failedFile).toHaveLength(1);
      expect(failedFile![0].id).toBe('f1');
    });

    it('should write empty arrays when no matching items', async () => {
      await persistence.savePending([]);

      const pendingFile = await readJSON<QueueItem[]>(resolve(tempDir, 'queue', 'pending.json'));
      const failedFile = await readJSON<QueueItem[]>(resolve(tempDir, 'queue', 'failed.json'));

      expect(pendingFile).toEqual([]);
      expect(failedFile).toEqual([]);
    });
  });

  describe('loadPending', () => {
    it('should return empty array when no file exists', async () => {
      const items = await persistence.loadPending();
      expect(items).toEqual([]);
    });

    it('should load pending items from file', async () => {
      await persistence.savePending([
        makeItem({ id: 'a', status: 'pending' }),
        makeItem({ id: 'b', status: 'pending' }),
      ]);

      const loaded = await persistence.loadPending();
      expect(loaded).toHaveLength(2);
      expect(loaded[0].id).toBe('a');
      expect(loaded[1].id).toBe('b');
    });

    it('should reset processing items back to pending on load', async () => {
      await persistence.savePending([
        makeItem({ id: 'x', status: 'processing' }),
      ]);

      const loaded = await persistence.loadPending();
      expect(loaded).toHaveLength(1);
      expect(loaded[0].status).toBe('pending');
    });
  });

  describe('loadFailed', () => {
    it('should return empty array when no file exists', async () => {
      const items = await persistence.loadFailed();
      expect(items).toEqual([]);
    });

    it('should load failed items from file', async () => {
      await persistence.savePending([
        makeItem({ id: 'f1', status: 'failed', error: 'err1' }),
        makeItem({ id: 'f2', status: 'failed', error: 'err2' }),
      ]);

      const loaded = await persistence.loadFailed();
      expect(loaded).toHaveLength(2);
      expect(loaded[0].error).toBe('err1');
    });
  });

  describe('loadAll', () => {
    it('should combine pending and failed items', async () => {
      await persistence.savePending([
        makeItem({ id: 'p1', status: 'pending' }),
        makeItem({ id: 'f1', status: 'failed', error: 'err' }),
      ]);

      const all = await persistence.loadAll();
      expect(all).toHaveLength(2);
      const ids = all.map(i => i.id).sort();
      expect(ids).toEqual(['f1', 'p1']);
    });

    it('should return empty array when no files exist', async () => {
      const all = await persistence.loadAll();
      expect(all).toEqual([]);
    });
  });

  describe('round-trip persistence', () => {
    it('should preserve all item fields through save and load', async () => {
      const original = makeItem({
        id: 'rt-1',
        type: 'image_generation',
        priority: 3,
        status: 'pending',
        characterId: 'char-42',
        payload: { prompt: 'test prompt', model: 'flux-2' },
        retryCount: 1,
        maxRetries: 5,
        createdAt: '2024-06-15T10:30:00Z',
        updatedAt: '2024-06-15T10:31:00Z',
      });

      await persistence.savePending([original]);
      const loaded = await persistence.loadPending();

      expect(loaded).toHaveLength(1);
      expect(loaded[0]).toEqual(original);
    });
  });
});
