import expect from 'expect';

import createTestState from 'mtest/helpers/createTestState';
import { timeBoxFunction, expectTimeWithinBounds } from 'mtest/helpers/timeBox';
import {
  GAME_1,
  GAME_2,
  GAME_3,
  GAMES_GQL_LAST_CURSOR,
  GAMES_PAGE_2_GQL_LAST_CURSOR,
} from 'mtest/fetchMocks/games';
import { EVENT_MODEL, CHANNEL_DETAILS } from 'mtest/fetchMocks/event';

import { Action } from 'mweb/common/actions/root';
import { buildGamesReducer, GamesState } from 'mweb/common/reducers/data/games';
import {
  GAMES_DATA_REINITIALIZED_ACTION_TYPE,
  GAMES_DATA_PAGE_LOADED_ACTION_TYPE,
} from 'mweb/common/actions/data/games';
import { EVENTS_DATA_EVENT_LOADED_ACTION_TYPE } from 'mweb/common/actions/data/events';
import { CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE } from 'mweb/common/actions/data/channels';

describe('gamesData reducer', () => {
  it('sets defaults', () => {
    expect(buildGamesReducer()(undefined, { type: 'NOOP' })).toEqual({
      gameDetails: {},
      gameNameLookup: {},
      gamesLoadStatus: {
        lastGameCursor: null,
        lastInitTime: 0,
      },
    });
  });

  it('restores to defaults on reinitialization', () => {
    const initialState = createTestState({
      data: {
        games: {
          gameDetails: {
            fakeGame: {},
          },
          gamesLoadStatus: {
            lastGameCursor: GAMES_GQL_LAST_CURSOR,
            lastInitTime: 9001,
          },
        },
      },
    }).data.games;
    const action: Action = {
      type: GAMES_DATA_REINITIALIZED_ACTION_TYPE,
    };

    const newState = buildGamesReducer()(initialState, action);
    expect(newState.gameDetails).toEqual({});
    expect(newState.gamesLoadStatus).toEqual({
      lastGameCursor: null,
      lastInitTime: 0,
    });
  });

  describe('data loading', () => {
    it('loads data into an empty list and updates lastInitTime correctly', () => {
      const initialState = createTestState().data.games;
      const action: Action = {
        type: GAMES_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: {
          gameDetails: {
            [GAME_3.name]: GAME_3,
            [GAME_2.name]: GAME_2,
          },
          lastGameCursor: GAMES_GQL_LAST_CURSOR,
        },
      };

      const { result: newState, bounds } = timeBoxFunction<GamesState>(
        buildGamesReducer().bind(undefined, initialState, action),
      );
      expect(Object.keys(newState.gameDetails).length).toEqual(2);
      expect(newState.gameDetails[GAME_3.name]).toEqual(GAME_3);
      expect(newState.gameDetails[GAME_2.name]).toEqual(GAME_2);
      expect(newState.gamesLoadStatus.lastGameCursor).toEqual(
        GAMES_GQL_LAST_CURSOR,
      );
      expectTimeWithinBounds(newState.gamesLoadStatus.lastInitTime, bounds);
    });

    it('adds data to a list already containing a game', () => {
      const initialState = createTestState({
        data: {
          games: {
            gameDetails: {
              [GAME_3.name]: GAME_3,
              [GAME_2.name]: GAME_2,
            },
            gamesLoadStatus: {
              lastGameCursor: GAMES_GQL_LAST_CURSOR,
            },
          },
        },
      }).data.games;
      const action: Action = {
        type: GAMES_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: {
          gameDetails: {
            [GAME_1.name]: GAME_1,
          },
          lastGameCursor: GAMES_PAGE_2_GQL_LAST_CURSOR,
        },
      };

      const newState = buildGamesReducer()(initialState, action);
      expect(Object.keys(newState.gameDetails).length).toEqual(3);
      expect(newState.gameDetails[GAME_3.name]).toEqual(GAME_3);
      expect(newState.gameDetails[GAME_2.name]).toEqual(GAME_2);
      expect(newState.gameDetails[GAME_1.name]).toEqual(GAME_1);
      expect(newState.gamesLoadStatus.lastGameCursor).toEqual(
        GAMES_PAGE_2_GQL_LAST_CURSOR,
      );
    });

    it('overwrites data in list with newer data for same game', () => {
      const updatedGame3 = {
        ...GAME_3,
        viewerCount: 5555,
      };
      const initialState = createTestState({
        data: {
          games: {
            gameDetails: {
              [GAME_3.name]: GAME_3,
            },
          },
        },
      }).data.games;
      const action: Action = {
        type: GAMES_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: {
          gameDetails: {
            [GAME_3.name]: updatedGame3,
            [GAME_2.name]: GAME_2,
          },
          lastGameCursor: GAMES_GQL_LAST_CURSOR,
        },
      };

      const newState = buildGamesReducer()(initialState, action);
      expect(Object.keys(newState.gameDetails).length).toEqual(2);
      expect(newState.gameDetails[GAME_3.name]).toEqual(updatedGame3);
      expect(newState.gameDetails[GAME_2.name]).toEqual(GAME_2);
      expect(newState.gamesLoadStatus.lastGameCursor).toEqual(
        GAMES_GQL_LAST_CURSOR,
      );
    });

    it('adds games when they come with an event load', () => {
      const initialState = createTestState().data.games;
      const action: Action = {
        type: EVENTS_DATA_EVENT_LOADED_ACTION_TYPE,
        payload: {
          event: EVENT_MODEL,
          channel: CHANNEL_DETAILS,
          game: GAME_1,
          vod: undefined,
        },
      };
      const newState = buildGamesReducer()(initialState, action);
      expect(Object.keys(newState.gameDetails).length).toEqual(1);
      expect(newState.gameDetails[GAME_1.name]).toEqual(GAME_1);
    });

    it('adds games and aliases from a channels load', () => {
      const initialState = createTestState().data.games;
      const action: Action = {
        type: CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: {
          channelDetails: {},
          cursor: null,
          game: GAME_1,
          gameAliasUsed: GAME_1.name.toLowerCase(),
        },
      };
      const newState = buildGamesReducer()(initialState, action);
      expect(Object.keys(newState.gameDetails).length).toEqual(1);
      expect(newState.gameDetails[GAME_1.name]).toEqual(GAME_1);
      expect(Object.keys(newState.gameNameLookup).length).toEqual(1);
      expect(newState.gameNameLookup[GAME_1.name.toLowerCase()]).toEqual(
        GAME_1.name,
      );
    });
  });
});
