/**
 * DataSourceFactory 单元测试
 * 测试 DataSource 工厂模式的核心功能
 */

import { DataSourceFactory, BaseDataSource, BaseMockDataSource } from './dataSourceFactory';

// 测试接口
interface TestItem {
  id: string;
  name: string;
  value: number;
}

interface TestQueryParams {
  name?: string;
  value?: number | null;
}

// 测试 API DataSource
class TestApiDataSource extends BaseDataSource<TestItem, TestQueryParams> {
  constructor() {
    super('/api/test');
  }

  // 自定义方法
  async customMethod(): Promise<string> {
    return 'API custom method';
  }
}

// 测试 Mock DataSource
class TestMockDataSource extends BaseMockDataSource<TestItem, TestQueryParams> {
  readonly __MOCK_NAME__ = 'TestMockDataSource';
  protected mockData: TestItem[] = [
    { id: '1', name: 'Test 1', value: 100 },
    { id: '2', name: 'Test 2', value: 200 },
  ];

  // 自定义方法
  async customMethod(): Promise<string> {
    return 'Mock custom method';
  }
}

// 测试辅助类，用于访问 protected 方法
class TestBaseDataSource extends BaseDataSource<TestItem, TestQueryParams> {
  constructor() {
    super('/api/test');
  }
  
  // 暴露 protected 方法用于测试
  public testBuildQueryParams(params?: TestQueryParams): string {
    return this.buildQueryParams(params);
  }
}

class TestBaseMockDataSource extends BaseMockDataSource<TestItem, TestQueryParams> {
  readonly __MOCK_NAME__ = 'TestBaseMockDataSource';
  protected mockData: TestItem[] = [];
  
  // 暴露 protected 方法用于测试
  public testDelay(ms: number): Promise<void> {
    return this.delay(ms);
  }
}

describe('DataSourceFactory', () => {
  describe('create', () => {
    it('should create DataSource instance', () => {
      const dataSource = DataSourceFactory.create(
        TestApiDataSource,
        TestMockDataSource
      );
      expect(dataSource).toBeDefined();
      expect(typeof dataSource.list).toBe('function');
      expect(typeof dataSource.detail).toBe('function');
      expect(typeof dataSource.create).toBe('function');
      expect(typeof dataSource.update).toBe('function');
      expect(typeof dataSource.delete).toBe('function');
    });
  });

  describe('createWithMethods', () => {
    it('should create DataSource instance with custom methods', () => {
      const dataSource = DataSourceFactory.createWithMethods<
        TestItem,
        {
          customMethod(): Promise<string>;
        },
        TestQueryParams
      >({
        apiDataSource: TestApiDataSource,
        mockDataSource: TestMockDataSource,
      });

      expect(dataSource).toBeDefined();
      expect(typeof dataSource.list).toBe('function');
      expect(typeof dataSource.customMethod).toBe('function');
    });
  });

  describe('BaseDataSource', () => {
    let dataSource: TestBaseDataSource;

    beforeEach(() => {
      dataSource = new TestBaseDataSource();
    });

    describe('buildQueryParams', () => {
      it('should build query params from object', () => {
        const params = { name: 'test', value: 100, page: 1 };
        const query = dataSource.testBuildQueryParams(params);
        expect(query).toBe('name=test&value=100&page=1');
      });

      it('should handle undefined params', () => {
        const query = dataSource.testBuildQueryParams(undefined);
        expect(query).toBe('');
      });

      it('should handle null values', () => {
        const params = { name: 'test', value: null };
        const query = dataSource.testBuildQueryParams(params);
        expect(query).toBe('name=test');
      });
    });
  });

  describe('BaseMockDataSource', () => {
    let dataSource: TestMockDataSource;
    let testDataSource: TestBaseMockDataSource;

    beforeEach(() => {
      dataSource = new TestMockDataSource();
      testDataSource = new TestBaseMockDataSource();
    });

    describe('list', () => {
      it('should return mock data', async () => {
        const items = await dataSource.list();
        expect(items).toHaveLength(2);
        expect(items[0].id).toBe('1');
        expect(items[1].id).toBe('2');
      });
    });

    describe('detail', () => {
      it('should return item by id', async () => {
        const item = await dataSource.detail('1');
        expect(item).toBeDefined();
        expect(item.id).toBe('1');
        expect(item.name).toBe('Test 1');
      });

      it('should throw error for non-existent id', async () => {
        await expect(dataSource.detail('999')).rejects.toThrow('Item not found');
      });
    });

    describe('create', () => {
      it('should create new item', async () => {
        const newItem = await dataSource.create({ name: 'Test 3', value: 300 });
        expect(newItem).toBeDefined();
        expect(newItem.id).toBeDefined();
        expect(newItem.name).toBe('Test 3');
        expect(newItem.value).toBe(300);

        // Verify item was added to mock data
        const items = await dataSource.list();
        expect(items).toHaveLength(3);
      });
    });

    describe('update', () => {
      it('should update existing item', async () => {
        const updatedItem = await dataSource.update('1', { name: 'Updated Test 1', value: 150 });
        expect(updatedItem).toBeDefined();
        expect(updatedItem.id).toBe('1');
        expect(updatedItem.name).toBe('Updated Test 1');
        expect(updatedItem.value).toBe(150);
      });

      it('should throw error for non-existent id', async () => {
        await expect(dataSource.update('999', { name: 'Test' })).rejects.toThrow('Item not found');
      });
    });

    describe('delete', () => {
      it('should delete item', async () => {
        await dataSource.delete('1');
        const items = await dataSource.list();
        expect(items).toHaveLength(1);
        expect(items[0].id).toBe('2');
      });
    });

    describe('delay', () => {
      it('should delay execution', async () => {
        const startTime = Date.now();
        await testDataSource.testDelay(100);
        const endTime = Date.now();
        expect(endTime - startTime).toBeGreaterThanOrEqual(100);
      });
    });
  });
});