import { emitter } from 'shared/helpers/mitt';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { differenceInSeconds } from 'date-fns';
import {
  isAConversationRoute,
  isAInboxViewRoute,
  isNotificationRoute,
} from 'dashboard/helper/routeHelpers';
import ReconnectService from 'dashboard/helper/ReconnectService';

vi.mock('shared/helpers/mitt', () => ({
  emitter: {
    on: vi.fn(),
    off: vi.fn(),
    emit: vi.fn(),
  },
}));

vi.mock('date-fns', () => ({
  differenceInSeconds: vi.fn(),
}));

vi.mock('dashboard/helper/routeHelpers', () => ({
  isAConversationRoute: vi.fn(),
  isAInboxViewRoute: vi.fn(),
  isNotificationRoute: vi.fn(),
}));

const storeMock = {
  dispatch: vi.fn(),
  getters: {
    getAppliedConversationFiltersQuery: [],
    'customViews/getActiveConversationFolder': { query: {} },
    'notifications/getNotificationFilters': {},
  },
};

const routerMock = {
  currentRoute: {
    value: {
      name: '',
      params: { conversation_id: null },
    },
  },
};

describe('ReconnectService', () => {
  let reconnectService;

  beforeEach(() => {
    window.addEventListener = vi.fn();
    window.removeEventListener = vi.fn();
    Object.defineProperty(window, 'location', {
      configurable: true,
      value: { reload: vi.fn() },
    });
    reconnectService = new ReconnectService(storeMock, routerMock);
  });

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

  describe('constructor', () => {
    it('should initialize with store, router, and setup event listeners', () => {
      expect(reconnectService.store).toBe(storeMock);
      expect(reconnectService.router).toBe(routerMock);
      expect(window.addEventListener).toHaveBeenCalledWith(
        'online',
        reconnectService.handleOnlineEvent
      );
      expect(emitter.on).toHaveBeenCalledWith(
        BUS_EVENTS.WEBSOCKET_RECONNECT,
        reconnectService.onReconnect
      );
      expect(emitter.on).toHaveBeenCalledWith(
        BUS_EVENTS.WEBSOCKET_DISCONNECT,
        reconnectService.onDisconnect
      );
    });
  });

  describe('disconnect', () => {
    it('should remove event listeners', () => {
      reconnectService.disconnect();
      expect(window.removeEventListener).toHaveBeenCalledWith(
        'online',
        reconnectService.handleOnlineEvent
      );
      expect(emitter.off).toHaveBeenCalledWith(
        BUS_EVENTS.WEBSOCKET_RECONNECT,
        reconnectService.onReconnect
      );
      expect(emitter.off).toHaveBeenCalledWith(
        BUS_EVENTS.WEBSOCKET_DISCONNECT,
        reconnectService.onDisconnect
      );
    });
  });

  describe('getSecondsSinceDisconnect', () => {
    it('should return 0 if disconnectTime is null', () => {
      reconnectService.disconnectTime = null;
      expect(reconnectService.getSecondsSinceDisconnect()).toBe(0);
    });

    it('should return the number of seconds + threshold since disconnect', () => {
      reconnectService.disconnectTime = new Date();
      differenceInSeconds.mockReturnValue(100);
      expect(reconnectService.getSecondsSinceDisconnect()).toBe(100);
    });
  });

  describe('handleOnlineEvent', () => {
    it('should reload the page if disconnected for more than 3 hours', () => {
      reconnectService.getSecondsSinceDisconnect = vi
        .fn()
        .mockReturnValue(10801);
      reconnectService.handleOnlineEvent();
      expect(window.location.reload).toHaveBeenCalled();
    });

    it('should not reload the page if disconnected for less than 3 hours', () => {
      reconnectService.getSecondsSinceDisconnect = vi
        .fn()
        .mockReturnValue(10799);
      reconnectService.handleOnlineEvent();
      expect(window.location.reload).not.toHaveBeenCalled();
    });
  });

  describe('fetchConversations', () => {
    it('should update the filters with disconnected time and the threshold', async () => {
      reconnectService.getSecondsSinceDisconnect = vi.fn().mockReturnValue(100);
      await reconnectService.fetchConversations();
      expect(storeMock.dispatch).toHaveBeenCalledWith('updateChatListFilters', {
        page: null,
        updatedWithin: 115,
      });
    });

    it('should dispatch updateChatListFilters and fetchAllConversations', async () => {
      reconnectService.getSecondsSinceDisconnect = vi.fn().mockReturnValue(100);
      await reconnectService.fetchConversations();
      expect(storeMock.dispatch).toHaveBeenCalledWith('updateChatListFilters', {
        page: null,
        updatedWithin: 115,
      });
      expect(storeMock.dispatch).toHaveBeenCalledWith('fetchAllConversations');
    });

    it('should dispatch updateChatListFilters and reset updatedWithin', async () => {
      reconnectService.getSecondsSinceDisconnect = vi.fn().mockReturnValue(100);
      await reconnectService.fetchConversations();
      expect(storeMock.dispatch).toHaveBeenCalledWith('updateChatListFilters', {
        updatedWithin: null,
      });
    });
  });

  describe('fetchFilteredOrSavedConversations', () => {
    it('should dispatch fetchFilteredConversations', async () => {
      const payload = { test: 'data' };
      await reconnectService.fetchFilteredOrSavedConversations(payload);
      expect(storeMock.dispatch).toHaveBeenCalledWith(
        'fetchFilteredConversations',
        { queryData: payload, page: 1 }
      );
    });
  });

  describe('fetchConversationsOnReconnect', () => {
    it('should fetch filtered or saved conversations if query exists', async () => {
      storeMock.getters.getAppliedConversationFiltersQuery = {
        payload: [
          {
            attribute_key: 'status',
            filter_operator: 'equal_to',
            values: ['open'],
          },
        ],
      };
      const spy = vi.spyOn(
        reconnectService,
        'fetchFilteredOrSavedConversations'
      );

      await reconnectService.fetchConversationsOnReconnect();

      expect(spy).toHaveBeenCalledWith(
        storeMock.getters.getAppliedConversationFiltersQuery
      );
    });

    it('should fetch all conversations if no query exists', async () => {
      storeMock.getters.getAppliedConversationFiltersQuery = [];
      storeMock.getters['customViews/getActiveConversationFolder'] = {
        query: null,
      };

      const spy = vi.spyOn(reconnectService, 'fetchConversations');

      await reconnectService.fetchConversationsOnReconnect();

      expect(spy).toHaveBeenCalled();
    });

    it('should fetch filtered or saved conversations if active folder query exists and no applied query', async () => {
      storeMock.getters.getAppliedConversationFiltersQuery = [];
      storeMock.getters['customViews/getActiveConversationFolder'] = {
        query: { test: 'activeFolderQuery' },
      };

      const spy = vi.spyOn(
        reconnectService,
        'fetchFilteredOrSavedConversations'
      );

      await reconnectService.fetchConversationsOnReconnect();

      expect(spy).toHaveBeenCalledWith({ test: 'activeFolderQuery' });
    });
  });

  describe('fetchConversationMessagesOnReconnect', () => {
    it('should dispatch syncActiveConversationMessages if conversationId exists', async () => {
      routerMock.currentRoute.value.params.conversation_id = 1;
      await reconnectService.fetchConversationMessagesOnReconnect();
      expect(storeMock.dispatch).toHaveBeenCalledWith(
        'syncActiveConversationMessages',
        { conversationId: 1 }
      );
    });

    it('should not dispatch syncActiveConversationMessages if conversationId does not exist', async () => {
      routerMock.currentRoute.value.params.conversation_id = null;
      await reconnectService.fetchConversationMessagesOnReconnect();
      expect(storeMock.dispatch).not.toHaveBeenCalledWith(
        'syncActiveConversationMessages',
        expect.anything()
      );
    });
  });

  describe('fetchNotificationsOnReconnect', () => {
    it('should dispatch notifications/index', async () => {
      const filter = { test: 'filter' };
      await reconnectService.fetchNotificationsOnReconnect(filter);
      expect(storeMock.dispatch).toHaveBeenCalledWith('notifications/index', {
        ...filter,
        page: 1,
      });
    });
  });

  describe('revalidateCaches', () => {
    it('should dispatch revalidate actions for labels, inboxes, and teams', async () => {
      storeMock.dispatch.mockResolvedValueOnce({
        label: 'labelKey',
        inbox: 'inboxKey',
        team: 'teamKey',
      });
      await reconnectService.revalidateCaches();
      expect(storeMock.dispatch).toHaveBeenCalledWith('accounts/getCacheKeys');
      expect(storeMock.dispatch).toHaveBeenCalledWith('labels/revalidate', {
        newKey: 'labelKey',
      });
      expect(storeMock.dispatch).toHaveBeenCalledWith('inboxes/revalidate', {
        newKey: 'inboxKey',
      });
      expect(storeMock.dispatch).toHaveBeenCalledWith('teams/revalidate', {
        newKey: 'teamKey',
      });
    });
  });

  describe('handleRouteSpecificFetch', () => {
    it('should fetch conversations and messages if current route is a conversation route', async () => {
      isAConversationRoute.mockReturnValue(true);
      const spyConversations = vi.spyOn(
        reconnectService,
        'fetchConversationsOnReconnect'
      );
      const spyMessages = vi.spyOn(
        reconnectService,
        'fetchConversationMessagesOnReconnect'
      );
      await reconnectService.handleRouteSpecificFetch();
      expect(spyConversations).toHaveBeenCalled();
      expect(spyMessages).toHaveBeenCalled();
    });

    it('should fetch notifications if current route is an inbox view route', async () => {
      isAInboxViewRoute.mockReturnValue(true);
      const spy = vi.spyOn(reconnectService, 'fetchNotificationsOnReconnect');
      await reconnectService.handleRouteSpecificFetch();
      expect(spy).toHaveBeenCalled();
    });

    it('should fetch notifications if current route is a notification route', async () => {
      isNotificationRoute.mockReturnValue(true);
      const spy = vi.spyOn(reconnectService, 'fetchNotificationsOnReconnect');
      await reconnectService.handleRouteSpecificFetch();
      expect(spy).toHaveBeenCalled();
    });
  });

  describe('setConversationLastMessageId', () => {
    it('should dispatch setConversationLastMessageId if conversationId exists', async () => {
      routerMock.currentRoute.value.params.conversation_id = 1;
      await reconnectService.setConversationLastMessageId();
      expect(storeMock.dispatch).toHaveBeenCalledWith(
        'setConversationLastMessageId',
        { conversationId: 1 }
      );
    });

    it('should not dispatch setConversationLastMessageId if conversationId does not exist', async () => {
      routerMock.currentRoute.value.params.conversation_id = null;
      await reconnectService.setConversationLastMessageId();
      expect(storeMock.dispatch).not.toHaveBeenCalledWith(
        'setConversationLastMessageId',
        expect.anything()
      );
    });
  });

  describe('onDisconnect', () => {
    it('should set disconnectTime and call setConversationLastMessageId', () => {
      reconnectService.setConversationLastMessageId = vi.fn();
      reconnectService.onDisconnect();
      expect(reconnectService.disconnectTime).toBeInstanceOf(Date);
      expect(reconnectService.setConversationLastMessageId).toHaveBeenCalled();
    });
  });

  describe('onReconnect', () => {
    it('should handle route-specific fetch, revalidate caches, and emit WEBSOCKET_RECONNECT_COMPLETED event', async () => {
      reconnectService.handleRouteSpecificFetch = vi.fn();
      reconnectService.revalidateCaches = vi.fn();
      await reconnectService.onReconnect();
      expect(reconnectService.handleRouteSpecificFetch).toHaveBeenCalled();
      expect(reconnectService.revalidateCaches).toHaveBeenCalled();
      expect(emitter.emit).toHaveBeenCalledWith(
        BUS_EVENTS.WEBSOCKET_RECONNECT_COMPLETED
      );
    });
  });
});
