/**
 * Sync Engine Tests
 *
 * @module @bytelyst/sync/engine.test
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { createSyncEngine, InMemoryAdapter } from './index.js';
import type { ApiClient, ApiResult } from '@bytelyst/api-client';

// ─────────────────────────────────────────────────────────────────────────────
// Mock API Client
// ─────────────────────────────────────────────────────────────────────────────

function createMockApiClient(): ApiClient & {
  getRequests: () => { path: string; options?: RequestInit }[];
} {
  const requests: { path: string; options?: RequestInit }[] = [];

  return {
    fetch: async <T>(path: string, options?: RequestInit): Promise<T> => {
      requests.push({ path, options });
      return { items: [] } as unknown as T;
    },
    safeFetch: async <T>(path: string, options?: RequestInit): Promise<ApiResult<T>> => {
      requests.push({ path, options });
      return { data: { items: [] } as unknown as T, error: null };
    },
    getRequests: () => requests,
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// Tests
// ─────────────────────────────────────────────────────────────────────────────

describe('Sync Engine', () => {
  let storage: InMemoryAdapter;
  let apiClient: ReturnType<typeof createMockApiClient>;

  beforeEach(() => {
    storage = new InMemoryAdapter();
    apiClient = createMockApiClient();
  });

  describe('createSyncEngine', () => {
    it('creates a sync engine with default config', () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      expect(engine).toBeDefined();
      expect(engine.push).toBeDefined();
      expect(engine.pull).toBeDefined();
      expect(engine.fullSync).toBeDefined();
    });
  });

  describe('push', () => {
    it('adds item to queue', async () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      await engine.push('tasks', { title: 'Test Task' });

      const status = engine.getStatus();
      expect(status.status).toBe('idle');
    });

    it('deduplicates items for same entity', async () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      await engine.push('tasks', { id: '1', title: 'Task 1' });
      await engine.push('tasks', { id: '1', title: 'Task 1 Updated' });

      // Queue should have 1 item (deduplicated)
      const result = await engine.fullSync();
      expect(result.pushed).toBe(1);
    });
  });

  describe('delete', () => {
    it('creates delete operation', async () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      await engine.delete('tasks', 'task-123');

      const requests = apiClient.getRequests();
      // Delete is queued but not flushed until sync
      expect(requests.length).toBe(0);
    });
  });

  describe('fullSync', () => {
    it('pushes queued items', async () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      await engine.push('tasks', { title: 'Test Task' });
      const result = await engine.fullSync();

      expect(result.pushed).toBe(1);

      const requests = apiClient.getRequests();
      expect(requests).toHaveLength(2); // Pull + Push
    });

    it('pulls remote changes', async () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      const result = await engine.fullSync();
      expect(result.pulled).toBe(0); // Mock returns empty
    });
  });

  describe('status and monitoring', () => {
    it('returns initial status', () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      const status = engine.getStatus();
      expect(status.status).toBe('idle');
    });

    it('notifies status changes', async () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      const statuses: string[] = [];
      engine.onStatusChange(status => {
        statuses.push(status.status);
      });

      await engine.push('tasks', { title: 'Test' });

      // Status changes during push
      expect(statuses.length).toBeGreaterThanOrEqual(0);
    });
  });

  describe('clearQueue', () => {
    it('removes all queued items', async () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      await engine.push('tasks', { title: 'Task 1' });
      await engine.push('tasks', { title: 'Task 2' });
      await engine.clearQueue();

      const result = await engine.fullSync();
      expect(result.pushed).toBe(0);
    });
  });

  describe('reprocessFailed', () => {
    it('resets retry count on failed items', async () => {
      const engine = createSyncEngine({
        productId: 'test',
        entities: {
          tasks: { endpoint: '/tasks', partitionKey: 'userId', conflictStrategy: 'server-wins' },
        },
        storage,
        apiClient,
      });

      await engine.push('tasks', { title: 'Test' });
      await engine.reprocessFailed();

      // Items should be reprocessed
      const result = await engine.fullSync();
      expect(result.pushed).toBe(1);
    });
  });
});

describe('Storage Adapters', () => {
  describe('InMemoryAdapter', () => {
    it('stores and retrieves items', () => {
      const storage = new InMemoryAdapter();
      storage.setItem('key1', { value: 123 });

      const retrieved = storage.getItem<{ value: number }>('key1');
      expect(retrieved).toEqual({ value: 123 });
    });

    it('returns null for missing keys', () => {
      const storage = new InMemoryAdapter();
      const retrieved = storage.getItem('missing');
      expect(retrieved).toBeNull();
    });

    it('lists all keys', () => {
      const storage = new InMemoryAdapter();
      storage.setItem('key1', 'value1');
      storage.setItem('key2', 'value2');

      const keys = storage.keys();
      expect(keys).toContain('key1');
      expect(keys).toContain('key2');
    });

    it('removes items', () => {
      const storage = new InMemoryAdapter();
      storage.setItem('key1', 'value1');
      storage.removeItem('key1');

      expect(storage.getItem('key1')).toBeNull();
    });

    it('clears all items', () => {
      const storage = new InMemoryAdapter();
      storage.setItem('key1', 'value1');
      storage.setItem('key2', 'value2');
      storage.clear();

      expect(storage.keys()).toHaveLength(0);
    });
  });
});
