/**
 * Offline Service Tests
 */

import { renderHook, act } from '@testing-library/react-hooks';
import { useOfflineStore, cacheService, networkMonitor, syncManager } from '../services/offline';
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from '@react-native-community/netinfo';

describe('useOfflineStore', () => {
  beforeEach(() => {
    // Reset the store state
    useOfflineStore.setState({
      isOnline: true,
      lastSyncAt: null,
      syncQueue: [],
      isSyncing: false,
      syncError: null,
    });

    jest.clearAllMocks();
  });

  describe('initial state', () => {
    it('should have correct initial state', () => {
      const { result } = renderHook(() => useOfflineStore());

      expect(result.current.isOnline).toBe(true);
      expect(result.current.lastSyncAt).toBeNull();
      expect(result.current.syncQueue).toEqual([]);
      expect(result.current.isSyncing).toBe(false);
      expect(result.current.syncError).toBeNull();
    });
  });

  describe('setOnline', () => {
    it('should set online status', () => {
      const { result } = renderHook(() => useOfflineStore());

      act(() => {
        result.current.setOnline(false);
      });

      expect(result.current.isOnline).toBe(false);

      act(() => {
        result.current.setOnline(true);
      });

      expect(result.current.isOnline).toBe(true);
    });
  });

  describe('addToQueue', () => {
    it('should add action to sync queue', () => {
      const { result } = renderHook(() => useOfflineStore());

      act(() => {
        result.current.addToQueue({
          entity: 'partner',
          action: 'create',
          data: { name: 'Test Partner' },
        });
      });

      expect(result.current.syncQueue).toHaveLength(1);
      expect(result.current.syncQueue[0]).toMatchObject({
        entity: 'partner',
        action: 'create',
        data: { name: 'Test Partner' },
        status: 'pending',
        retries: 0,
      });
      expect(result.current.syncQueue[0].id).toBeDefined();
      expect(result.current.syncQueue[0].timestamp).toBeDefined();
    });

    it('should add multiple actions to queue', () => {
      const { result } = renderHook(() => useOfflineStore());

      act(() => {
        result.current.addToQueue({
          entity: 'partner',
          action: 'create',
          data: { name: 'Partner 1' },
        });
        result.current.addToQueue({
          entity: 'product',
          action: 'update',
          data: { id: '1', name: 'Product 1' },
        });
      });

      expect(result.current.syncQueue).toHaveLength(2);
    });
  });

  describe('removeFromQueue', () => {
    it('should remove action from queue', () => {
      const { result } = renderHook(() => useOfflineStore());

      act(() => {
        result.current.addToQueue({
          entity: 'partner',
          action: 'create',
          data: { name: 'Test' },
        });
      });

      const actionId = result.current.syncQueue[0].id;

      act(() => {
        result.current.removeFromQueue(actionId);
      });

      expect(result.current.syncQueue).toHaveLength(0);
    });
  });

  describe('updateQueueItem', () => {
    it('should update queue item', () => {
      const { result } = renderHook(() => useOfflineStore());

      act(() => {
        result.current.addToQueue({
          entity: 'partner',
          action: 'create',
          data: { name: 'Test' },
        });
      });

      const actionId = result.current.syncQueue[0].id;

      act(() => {
        result.current.updateQueueItem(actionId, {
          status: 'syncing',
          retries: 1,
        });
      });

      expect(result.current.syncQueue[0].status).toBe('syncing');
      expect(result.current.syncQueue[0].retries).toBe(1);
    });
  });

  describe('clearQueue', () => {
    it('should clear all items from queue', () => {
      const { result } = renderHook(() => useOfflineStore());

      act(() => {
        result.current.addToQueue({
          entity: 'partner',
          action: 'create',
          data: { name: 'Test 1' },
        });
        result.current.addToQueue({
          entity: 'product',
          action: 'create',
          data: { name: 'Test 2' },
        });
      });

      expect(result.current.syncQueue).toHaveLength(2);

      act(() => {
        result.current.clearQueue();
      });

      expect(result.current.syncQueue).toHaveLength(0);
    });
  });

  describe('setSyncing', () => {
    it('should set syncing state', () => {
      const { result } = renderHook(() => useOfflineStore());

      act(() => {
        result.current.setSyncing(true);
      });

      expect(result.current.isSyncing).toBe(true);

      act(() => {
        result.current.setSyncing(false);
      });

      expect(result.current.isSyncing).toBe(false);
    });
  });

  describe('setSyncError', () => {
    it('should set sync error', () => {
      const { result } = renderHook(() => useOfflineStore());

      act(() => {
        result.current.setSyncError('Network error');
      });

      expect(result.current.syncError).toBe('Network error');

      act(() => {
        result.current.setSyncError(null);
      });

      expect(result.current.syncError).toBeNull();
    });
  });

  describe('setLastSyncAt', () => {
    it('should set last sync timestamp', () => {
      const { result } = renderHook(() => useOfflineStore());
      const timestamp = Date.now();

      act(() => {
        result.current.setLastSyncAt(timestamp);
      });

      expect(result.current.lastSyncAt).toBe(timestamp);
    });
  });
});

describe('cacheService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('set and get', () => {
    it('should set and get cached data', async () => {
      const testData = { name: 'Test', value: 123 };

      await cacheService.set('test-key', testData);

      expect(AsyncStorage.setItem).toHaveBeenCalled();
    });

    it('should return null for missing key', async () => {
      (AsyncStorage.getItem as jest.Mock).mockResolvedValue(null);

      const result = await cacheService.get('missing-key');

      expect(result).toBeNull();
    });
  });

  describe('remove', () => {
    it('should remove cached item', async () => {
      await cacheService.remove('test-key');

      expect(AsyncStorage.removeItem).toHaveBeenCalled();
    });
  });

  describe('clearAll', () => {
    it('should clear all cached items', async () => {
      (AsyncStorage.getAllKeys as jest.Mock).mockResolvedValue([
        'cache_key1',
        'cache_key2',
        'other_key',
      ]);

      await cacheService.clearAll();

      expect(AsyncStorage.multiRemove).toHaveBeenCalled();
    });
  });

  describe('getStats', () => {
    it('should return cache statistics', async () => {
      (AsyncStorage.getAllKeys as jest.Mock).mockResolvedValue([
        'cache_key1',
        'cache_key2',
      ]);

      const stats = await cacheService.getStats();

      expect(stats).toHaveProperty('count');
      expect(stats).toHaveProperty('keys');
    });
  });
});

describe('networkMonitor', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('start', () => {
    it('should start monitoring network state', () => {
      networkMonitor.start();

      expect(NetInfo.addEventListener).toHaveBeenCalled();
      expect(NetInfo.fetch).toHaveBeenCalled();
    });
  });

  describe('stop', () => {
    it('should stop monitoring', () => {
      networkMonitor.start();
      networkMonitor.stop();

      // Verify the unsubscribe function was called
      // The mock returns a jest.fn() which should be called on stop
    });
  });

  describe('check', () => {
    it('should check current network state', async () => {
      (NetInfo.fetch as jest.Mock).mockResolvedValue({
        isConnected: true,
        isInternetReachable: true,
      });

      const isOnline = await networkMonitor.check();

      expect(isOnline).toBe(true);
      expect(NetInfo.fetch).toHaveBeenCalled();
    });

    it('should return false when offline', async () => {
      (NetInfo.fetch as jest.Mock).mockResolvedValue({
        isConnected: false,
        isInternetReachable: false,
      });

      const isOnline = await networkMonitor.check();

      expect(isOnline).toBe(false);
    });
  });
});

describe('syncManager', () => {
  beforeEach(() => {
    useOfflineStore.setState({
      isOnline: true,
      lastSyncAt: null,
      syncQueue: [],
      isSyncing: false,
      syncError: null,
    });

    jest.clearAllMocks();
  });

  describe('getPendingCount', () => {
    it('should return count of pending items', () => {
      useOfflineStore.setState({
        syncQueue: [
          { id: '1', entity: 'partner', action: 'create', data: {}, status: 'pending', timestamp: Date.now(), retries: 0 },
          { id: '2', entity: 'product', action: 'update', data: {}, status: 'syncing', timestamp: Date.now(), retries: 0 },
          { id: '3', entity: 'invoice', action: 'delete', data: {}, status: 'pending', timestamp: Date.now(), retries: 0 },
        ],
      });

      const count = syncManager.getPendingCount();

      expect(count).toBe(2);
    });
  });

  describe('getFailedItems', () => {
    it('should return failed items', () => {
      useOfflineStore.setState({
        syncQueue: [
          { id: '1', entity: 'partner', action: 'create', data: {}, status: 'pending', timestamp: Date.now(), retries: 0 },
          { id: '2', entity: 'product', action: 'update', data: {}, status: 'failed', timestamp: Date.now(), retries: 3, error: 'Network error' },
        ],
      });

      const failed = syncManager.getFailedItems();

      expect(failed).toHaveLength(1);
      expect(failed[0].id).toBe('2');
    });
  });

  describe('retryFailed', () => {
    it('should reset failed items to pending', () => {
      useOfflineStore.setState({
        syncQueue: [
          { id: '1', entity: 'partner', action: 'create', data: {}, status: 'failed', timestamp: Date.now(), retries: 3, error: 'Error' },
          { id: '2', entity: 'product', action: 'update', data: {}, status: 'failed', timestamp: Date.now(), retries: 3, error: 'Error' },
        ],
      });

      syncManager.retryFailed();

      const state = useOfflineStore.getState();

      expect(state.syncQueue[0].status).toBe('pending');
      expect(state.syncQueue[0].retries).toBe(0);
      expect(state.syncQueue[0].error).toBeUndefined();
      expect(state.syncQueue[1].status).toBe('pending');
    });
  });

  describe('processQueue', () => {
    it('should return early if offline', async () => {
      useOfflineStore.setState({
        isOnline: false,
        syncQueue: [
          { id: '1', entity: 'partner', action: 'create', data: {}, status: 'pending', timestamp: Date.now(), retries: 0 },
        ],
      });

      const result = await syncManager.processQueue({});

      expect(result).toEqual({ success: 0, failed: 0 });
    });

    it('should return early if already syncing', async () => {
      useOfflineStore.setState({
        isOnline: true,
        isSyncing: true,
        syncQueue: [
          { id: '1', entity: 'partner', action: 'create', data: {}, status: 'pending', timestamp: Date.now(), retries: 0 },
        ],
      });

      const result = await syncManager.processQueue({});

      expect(result).toEqual({ success: 0, failed: 0 });
    });

    it('should return early if queue is empty', async () => {
      useOfflineStore.setState({
        isOnline: true,
        isSyncing: false,
        syncQueue: [],
      });

      const result = await syncManager.processQueue({});

      expect(result).toEqual({ success: 0, failed: 0 });
    });
  });
});
