import { setActivePinia, createPinia } from 'pinia';
import { useOfflineStore, type OfflineAction, type CreateListPayload } from '../offline'; // Adjust path
import { useNotificationStore } from '@/stores/notifications';
import { apiClient } from '@/services/api';
import { ref } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach, SpyInstance } from 'vitest';

// --- Mocks ---
// Mock @vueuse/core's useStorage
const mockPendingActions = ref<OfflineAction[]>([]);
vi.mock('@vueuse/core', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@vueuse/core')>()
  return {
    ...actual,
    useStorage: vi.fn((_key, defaultValue) => {
      // If we clear mockPendingActions, it should reset to defaultValue for new instances
      if (mockPendingActions.value === undefined) {
        mockPendingActions.value = defaultValue;
      }
      return mockPendingActions;
    }),
  };
});

// Mock notifications store
vi.mock('@/stores/notifications', () => ({
  useNotificationStore: vi.fn(() => ({
    addNotification: vi.fn(),
  })),
}));

// Mock apiClient
vi.mock('@/services/api', () => ({
  apiClient: {
    post: vi.fn(),
    put: vi.fn(),
    delete: vi.fn(), // Assuming it might be used
  },
  API_ENDPOINTS: { // Provide the structure used by the store
    LISTS: {
      BASE: '/api/lists',
      BY_ID: (id: string) => `/api/lists/${id}`,
      ITEMS: (listId: string) => `/api/lists/${listId}/items`,
      ITEM: (listId: string, itemId: string) => `/api/lists/${listId}/items/${itemId}`,
    },
  },
}));

// Mock global fetch
global.fetch = vi.fn();

// Mock crypto.randomUUID
global.crypto = {
  ...global.crypto,
  randomUUID: vi.fn(() => `mock-uuid-${Math.random().toString(36).substring(2, 15)}`),
};

// Mock navigator.onLine
let mockNavigatorOnLine = true;
Object.defineProperty(navigator, 'onLine', {
  configurable: true,
  get: () => mockNavigatorOnLine,
});

// Helper to dispatch window events
const dispatchWindowEvent = (eventName: string) => {
  window.dispatchEvent(new Event(eventName));
};


describe('Offline Store', () => {
  let offlineStore: ReturnType<typeof useOfflineStore>;
  let notificationStore: ReturnType<typeof useNotificationStore>;

  beforeEach(() => {
    setActivePinia(createPinia());
    // Reset mocks and mock values before each test
    mockPendingActions.value = []; // Reset useStorage mock state
    (vi.mocked(global.crypto.randomUUID) as SpyInstance<[], string>).mockClear();
    (vi.mocked(global.fetch) as SpyInstance<[RequestInfo | URL, RequestInit | undefined], Promise<Response>>).mockClear();

    offlineStore = useOfflineStore();
    notificationStore = useNotificationStore(); // Get the mocked instance

    // Ensure navigator.onLine is reset for each test if needed
    mockNavigatorOnLine = true;
    // Clear event listeners by re-registering (simplistic; better if store offered cleanup)
    // For robust testing, store's setupNetworkListeners might need a cleanup function.
    // For now, we assume listeners are added once and test their effect.
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe('Initial State and Network Listeners', () => {
    it('initializes with isOnline reflecting navigator.onLine (true)', () => {
      mockNavigatorOnLine = true;
      // Re-initialize store to pick up new navigator.onLine state
      // This is tricky because setupNetworkListeners is called on store creation.
      // A better approach might be for setupNetworkListeners to be manually callable for tests,
      // or for the store to expose a way to update isOnline based on event listeners.
      // For this test, we assume it's true initially.
      const store = useOfflineStore(); // Create new instance for this specific initial state check
      expect(store.isOnline).toBe(true);
    });

    it('initializes with isOnline reflecting navigator.onLine (false)', () => {
      mockNavigatorOnLine = false;
      const store = useOfflineStore(); // Create new instance
      expect(store.isOnline).toBe(false);
    });

    it('initializes with empty pendingActions (from mocked useStorage)', () => {
      expect(offlineStore.pendingActions).toEqual([]);
      expect(offlineStore.hasPendingActions).toBe(false);
      expect(offlineStore.pendingActionCount).toBe(0);
    });

    it('initializes other state properties to their defaults', () => {
      expect(offlineStore.isProcessingQueue).toBe(false);
      expect(offlineStore.showConflictDialog).toBe(false);
      expect(offlineStore.currentConflict).toBeNull();
    });

    it('updates isOnline to false when window "offline" event is dispatched', () => {
      mockNavigatorOnLine = true; // Start online
      const store = useOfflineStore(); // Re-init to ensure listeners are fresh
      expect(store.isOnline).toBe(true);

      mockNavigatorOnLine = false; // Simulate going offline
      dispatchWindowEvent('offline');
      expect(store.isOnline).toBe(false);
    });

    it('updates isOnline to true and attempts to process queue when window "online" event is dispatched', () => {
      mockNavigatorOnLine = false; // Start offline
      const store = useOfflineStore();
      expect(store.isOnline).toBe(false);

      const processQueueSpy = vi.spyOn(store, 'processQueue');
      mockNavigatorOnLine = true; // Simulate going online
      dispatchWindowEvent('online');

      expect(store.isOnline).toBe(true);
      expect(processQueueSpy).toHaveBeenCalled();
    });
  });

  describe('addAction', () => {
    it('adds a new action to pendingActions with a unique ID and timestamp', () => {
      const actionPayload: CreateListPayload = { name: 'My New List' };
      const actionType = 'create_list';

      const initialTime = Date.now();
      vi.spyOn(Date, 'now').mockReturnValue(initialTime);
      vi.mocked(global.crypto.randomUUID).mockReturnValue('test-uuid-1');

      offlineStore.addAction({ type: actionType, payload: actionPayload });

      expect(offlineStore.pendingActions.length).toBe(1);
      const addedAction = offlineStore.pendingActions[0];
      expect(addedAction.id).toBe('test-uuid-1');
      expect(addedAction.timestamp).toBe(initialTime);
      expect(addedAction.type).toBe(actionType);
      expect(addedAction.payload).toEqual(actionPayload);
      expect(offlineStore.hasPendingActions).toBe(true);
      expect(offlineStore.pendingActionCount).toBe(1);

      // Add another action to ensure uniqueness and order
      const actionPayload2: CreateListPayload = { name: 'My Second List' };
      const laterTime = initialTime + 100;
      vi.spyOn(Date, 'now').mockReturnValue(laterTime);
      vi.mocked(global.crypto.randomUUID).mockReturnValue('test-uuid-2');

      offlineStore.addAction({ type: 'create_list', payload: actionPayload2 });
      expect(offlineStore.pendingActions.length).toBe(2);
      expect(offlineStore.pendingActions[1].id).toBe('test-uuid-2');
      expect(offlineStore.pendingActions[1].timestamp).toBe(laterTime);

      vi.restoreAllMocks(); // Restore Date.now
    });
  });

  describe('processQueue', () => {
    beforeEach(() => {
      // Ensure store is online for these tests unless specified
      mockNavigatorOnLine = true;
      offlineStore.isOnline = true; // Directly set for simplicity as event listeners are tricky to re-trigger per test
      offlineStore.isProcessingQueue = false;
    });

    it('does not run if isProcessingQueue is true', async () => {
      offlineStore.isProcessingQueue = true;
      const processActionSpy = vi.spyOn(offlineStore, 'processAction');
      await offlineStore.processQueue();
      expect(processActionSpy).not.toHaveBeenCalled();
    });

    it('does not run if isOnline is false', async () => {
      offlineStore.isOnline = false;
      const processActionSpy = vi.spyOn(offlineStore, 'processAction');
      await offlineStore.processQueue();
      expect(processActionSpy).not.toHaveBeenCalled();
    });

    it('processes actions from pendingActions and removes them on success', async () => {
      const action1: OfflineAction = { id: 'a1', type: 'create_list', payload: { name: 'L1' }, timestamp: Date.now() };
      const action2: OfflineAction = { id: 'a2', type: 'create_list', payload: { name: 'L2' }, timestamp: Date.now() };
      mockPendingActions.value = [action1, action2]; // Set initial actions

      const processActionSpy = vi.spyOn(offlineStore, 'processAction').mockResolvedValue(undefined); // Mock successful processing

      await offlineStore.processQueue();

      expect(processActionSpy).toHaveBeenCalledTimes(2);
      expect(processActionSpy).toHaveBeenCalledWith(action1);
      expect(processActionSpy).toHaveBeenCalledWith(action2);
      expect(offlineStore.pendingActions.length).toBe(0); // All actions removed
      expect(offlineStore.isProcessingQueue).toBe(false);
    });

    it('stops processing and keeps action if processAction throws non-conflict error', async () => {
      const action1: OfflineAction = { id: 'a1', type: 'create_list', payload: { name: 'L1' }, timestamp: Date.now() };
      const action2: OfflineAction = { id: 'a2', type: 'create_list', payload: { name: 'L2' }, timestamp: Date.now() };
      mockPendingActions.value = [action1, action2];

      const error = new Error("Network error");
      const processActionSpy = vi.spyOn(offlineStore, 'processAction')
        .mockRejectedValueOnce(error); // Fail first action

      await offlineStore.processQueue();

      expect(processActionSpy).toHaveBeenCalledTimes(1);
      expect(processActionSpy).toHaveBeenCalledWith(action1);
      expect(offlineStore.pendingActions.length).toBe(2); // Action1 should remain due to error, action2 not processed
      expect(offlineStore.pendingActions[0].id).toBe('a1');
      expect(notificationStore.addNotification).not.toHaveBeenCalled(); // No conflict notification
      expect(offlineStore.isProcessingQueue).toBe(false);
    });

    it('handles conflict error: shows notification, sets conflict data, stops queue', async () => {
      const conflictAction: OfflineAction = {
        id: 'c1',
        type: 'update_list',
        payload: { listId: 'list1', data: { name: 'Local Name' } },
        timestamp: Date.now()
      };
      mockPendingActions.value = [conflictAction];

      const serverVersionData = { id: 'list1', name: 'Server Name', version: 2, updated_at: new Date().toISOString() };
      const conflictError = { isConflict: true, serverVersionData };
      const processActionSpy = vi.spyOn(offlineStore, 'processAction')
        .mockRejectedValueOnce(conflictError);

      await offlineStore.processQueue();

      expect(processActionSpy).toHaveBeenCalledWith(conflictAction);
      expect(notificationStore.addNotification).toHaveBeenCalledWith(expect.objectContaining({
        type: 'warning',
        message: expect.stringContaining('Conflict detected'),
      }));
      expect(offlineStore.currentConflict).not.toBeNull();
      expect(offlineStore.currentConflict?.action).toEqual(conflictAction);
      expect(offlineStore.currentConflict?.localVersion.data).toEqual({ name: 'Local Name' });
      expect(offlineStore.currentConflict?.serverVersion.data).toEqual(serverVersionData);
      expect(offlineStore.showConflictDialog).toBe(true);
      expect(offlineStore.pendingActions.length).toBe(1); // Action remains
      expect(offlineStore.isProcessingQueue).toBe(false); // Processing stops, allows re-trigger
    });
  });

  // More tests would be needed for processAction (covering all action types and fetch results)
  // and handleConflictResolution (covering all resolution strategies).
  // These are complex and would significantly expand the test suite.
  // For the scope of a typical subtask, the tests above cover core mechanics.
});
