/**
 * 乐观更新工具函数测试
 */

import { optimisticUpdate, simpleOptimisticUpdate, createOptimisticUpdaterFactory } from '../optimisticUpdate';

// 模拟数据
interface TestState {
  posts: Array<{ id: string; likes: number; isLiked: boolean }>;
  users: Array<{ id: string; isFollowing: boolean }>;
}

const initialState: TestState = {
  posts: [
    { id: '1', likes: 10, isLiked: false },
    { id: '2', likes: 5, isLiked: true },
  ],
  users: [
    { id: 'user1', isFollowing: false },
    { id: 'user2', isFollowing: true },
  ],
};

describe('optimisticUpdate', () => {
  let currentState: TestState;

  beforeEach(() => {
    currentState = JSON.parse(JSON.stringify(initialState));
  });

  describe('成功场景', () => {
    it('应该执行乐观更新并在API成功后保持更新', async () => {
      const mockApiCall = jest.fn().mockResolvedValue({ success: true, likes: 11 });

      const result = await optimisticUpdate<TestState, { success: boolean; likes: number }>({
        getState: () => currentState,
        optimisticUpdate: (state) => ({
          ...state,
          posts: state.posts.map((p) =>
            p.id === '1' ? { ...p, likes: p.likes + 1, isLiked: true } : p
          ),
        }),
        apiCall: mockApiCall,
        onSuccess: (result, current) => ({
          ...current,
          posts: current.posts.map((p) =>
            p.id === '1' ? { ...p, likes: result.likes } : p
          ),
        }),
      });

      // 验证API被调用
      expect(mockApiCall).toHaveBeenCalledTimes(1);
      // 验证返回结果
      expect(result).toEqual({ success: true, likes: 11 });
    });

    it('应该在乐观更新后立即改变状态', async () => {
      const mockApiCall = jest.fn().mockImplementation(() => {
        // 在API调用期间验证状态已被乐观更新
        expect(currentState.posts[0].isLiked).toBe(true);
        expect(currentState.posts[0].likes).toBe(11);
        return Promise.resolve({ success: true });
      });

      await optimisticUpdate<TestState, any>({
        getState: () => currentState,
        optimisticUpdate: (state) => {
          const newState = {
            ...state,
            posts: state.posts.map((p) =>
              p.id === '1' ? { ...p, likes: p.likes + 1, isLiked: true } : p
            ),
          };
          currentState = newState; // 模拟状态更新
          return newState;
        },
        apiCall: mockApiCall,
      });
    });
  });

  describe('失败回滚场景', () => {
    it('应该在API失败时回滚到原始状态', async () => {
      const mockApiCall = jest.fn().mockRejectedValue(new Error('Network error'));
      const onError = jest.fn((error, original) => original);

      const result = await optimisticUpdate<TestState, any>({
        getState: () => currentState,
        optimisticUpdate: (state) => ({
          ...state,
          posts: state.posts.map((p) =>
            p.id === '1' ? { ...p, likes: p.likes + 1, isLiked: true } : p
          ),
        }),
        apiCall: mockApiCall,
        onError,
        errorMessage: '点赞失败',
      });

      // 验证API被调用
      expect(mockApiCall).toHaveBeenCalledTimes(1);
      // 验证onError被调用
      expect(onError).toHaveBeenCalledTimes(1);
      expect(onError).toHaveBeenCalledWith(
        expect.any(Error),
        initialState
      );
      // 验证返回undefined（因为失败了）
      expect(result).toBeUndefined();
    });

    it('应该在静默模式下不抛出错误', async () => {
      const mockApiCall = jest.fn().mockRejectedValue(new Error('Network error'));

      // 不应该抛出错误
      await expect(
        optimisticUpdate<TestState, any>({
          getState: () => currentState,
          optimisticUpdate: (state) => state,
          apiCall: mockApiCall,
          silent: true,
        })
      ).resolves.toBeUndefined();
    });

    it('应该在非静默模式下抛出错误', async () => {
      const mockApiCall = jest.fn().mockRejectedValue(new Error('Network error'));

      // 应该抛出错误
      await expect(
        optimisticUpdate<TestState, any>({
          getState: () => currentState,
          optimisticUpdate: (state) => state,
          apiCall: mockApiCall,
          silent: false,
        })
      ).rejects.toThrow('Network error');
    });
  });

  describe('错误处理', () => {
    it('应该记录错误日志', async () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      const mockApiCall = jest.fn().mockRejectedValue(new Error('API Error'));

      await optimisticUpdate<TestState, any>({
        getState: () => currentState,
        optimisticUpdate: (state) => state,
        apiCall: mockApiCall,
        errorMessage: '自定义错误消息',
        silent: true,
      });

      expect(consoleSpy).toHaveBeenCalledWith('自定义错误消息:', expect.any(Error));

      consoleSpy.mockRestore();
    });
  });
});

describe('simpleOptimisticUpdate', () => {
  let currentState: TestState;
  let setStateMock: jest.Mock;

  beforeEach(() => {
    currentState = JSON.parse(JSON.stringify(initialState));
    setStateMock = jest.fn((newState) => {
      currentState = newState;
    });
  });

  it('应该成功执行乐观更新', async () => {
    const mockApiCall = jest.fn().mockResolvedValue(undefined);

    await simpleOptimisticUpdate<TestState>({
      getState: () => currentState,
      setState: setStateMock,
      optimisticUpdate: (state) => ({
        ...state,
        posts: state.posts.map((p) =>
          p.id === '1' ? { ...p, likes: p.likes + 1 } : p
        ),
      }),
      rollbackState: (original) => original,
      apiCall: mockApiCall,
    });

    expect(mockApiCall).toHaveBeenCalledTimes(1);
    expect(setStateMock).toHaveBeenCalledTimes(1);
  });

  it('应该在失败时回滚状态', async () => {
    const mockApiCall = jest.fn().mockRejectedValue(new Error('Error'));
    const rollbackMock = jest.fn((original) => original);

    await simpleOptimisticUpdate<TestState>({
      getState: () => currentState,
      setState: setStateMock,
      optimisticUpdate: (state) => ({
        ...state,
        posts: state.posts.map((p) =>
          p.id === '1' ? { ...p, likes: p.likes + 1 } : p
        ),
      }),
      rollbackState: rollbackMock,
      apiCall: mockApiCall,
      silent: true,
    });

    // 验证乐观更新和回滚都被调用
    expect(setStateMock).toHaveBeenCalledTimes(2);
    expect(rollbackMock).toHaveBeenCalledWith(initialState);
  });
});

describe('createOptimisticUpdaterFactory', () => {
  interface StoreState {
    posts: Array<{ id: string; likes: number }>;
    count: number;
  }

  let storeState: StoreState;
  let setMock: jest.Mock;
  let getMock: jest.Mock;

  beforeEach(() => {
    storeState = {
      posts: [{ id: '1', likes: 10 }],
      count: 0,
    };
    setMock = jest.fn((fn) => {
      storeState = fn(storeState);
    });
    getMock = jest.fn(() => storeState);
  });

  it('应该创建乐观更新器并正常工作', async () => {
    const optimisticUpdater = createOptimisticUpdaterFactory<StoreState>(setMock, getMock);
    const mockApiCall = jest.fn().mockResolvedValue(undefined);

    await optimisticUpdater({
      key: 'posts',
      optimisticUpdate: (posts) =>
        posts.map((p) => (p.id === '1' ? { ...p, likes: p.likes + 1 } : p)),
      rollbackState: (original) => original,
      apiCall: mockApiCall,
    });

    expect(mockApiCall).toHaveBeenCalledTimes(1);
    expect(setMock).toHaveBeenCalledTimes(1);
    expect(storeState.posts[0].likes).toBe(11);
  });

  it('应该在API失败时回滚状态', async () => {
    const optimisticUpdater = createOptimisticUpdaterFactory<StoreState>(setMock, getMock);
    const mockApiCall = jest.fn().mockRejectedValue(new Error('Error'));

    await optimisticUpdater({
      key: 'posts',
      optimisticUpdate: (posts) =>
        posts.map((p) => (p.id === '1' ? { ...p, likes: p.likes + 1 } : p)),
      rollbackState: (original) => original,
      apiCall: mockApiCall,
      silent: true,
    });

    // 乐观更新 + 回滚 = 2次set调用
    expect(setMock).toHaveBeenCalledTimes(2);
    // 最终状态应该和初始状态一样
    expect(storeState.posts[0].likes).toBe(10);
  });

  it('应该处理不同的state key', async () => {
    const optimisticUpdater = createOptimisticUpdaterFactory<StoreState>(setMock, getMock);
    const mockApiCall = jest.fn().mockResolvedValue(undefined);

    await optimisticUpdater({
      key: 'count',
      optimisticUpdate: (count) => count + 1,
      rollbackState: (original) => original,
      apiCall: mockApiCall,
    });

    expect(storeState.count).toBe(1);
  });
});
