import expect from 'expect';
import fetchMock from 'fetch-mock';

import createTestStore from 'mtest/helpers/createTestStore';
import expectToReturnType from 'mtest/helpers/expectToReturnType';
import {
  ALL_GAMES_CHANNELS_DATA_PAYLOAD,
  GAME_CHANNELS_DATA_PAYLOAD,
  GAME_PAGE_2_CHANNELS_DATA_PAYLOAD,
  mockChannelsForGame,
  mockChannelsForGame404,
  mockChannelsAllGames,
  mockChannelsForGamePage2,
} from 'mtest/fetchMocks/channels';
import {
  makeFakeChannel,
  CHANNEL,
  CHANNEL_DATA_PAYLOAD,
  mockChannel,
  mockStatusNotFound,
  mockChannelBanned,
  NOT_CHANNEL,
} from 'mtest/fetchMocks/channel';
import { GAME_1 } from 'mtest/fetchMocks/games';

import {
  CHANNELS_DATA_CHANNEL_FAILED_ACTION_TYPE,
  CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE,
  CHANNELS_DATA_PAGE_FAILED_ACTION_TYPE,
  CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
  CHANNELS_DATA_REINITIALIZED_GAME_ACTION_TYPE,
  channelsDataBuildUpdateChannelHostingStatusAction,
  channelsDataFetchChannel,
  channelsDataFetchPage,
  channelsDataGetPage,
  channelsDataLoadChannel,
  channelsDataLoadPage,
  channelsDataReinitializeForGame,
  channelsDataUpdateChannelHostingStatus,
  errorsContain404,
  UNEXPECTED_404_ERROR_MESSAGES,
} from 'mweb/common/actions/data/channels';
import { ALL_CHANNELS } from 'mweb/common/reducers/data/channels';

describe('channelsData actions', () => {
  afterEach(() => {
    expect(fetchMock.done()).toEqual(true);
    fetchMock.restore();
  });

  describe('channelsDataGetPage', () => {
    it('loads data without renit if alias cannot be resolved to a game name even if with some channels', () => {
      const store = createTestStore({
        data: {
          channels: {
            channelDetails: {
              channel: makeFakeChannel({ name: 'channel', game: GAME_1.name }),
            },
            channelsByGameLoadStatus: {
              notGame: {
                lastChannelCursor: 'blah',
                lastInitTime: 0,
                pagesLoaded: 1,
              },
            },
          },
        },
      });
      mockChannelsForGame();
      return store.dispatch(channelsDataGetPage(GAME_1.name)).then(() => {
        const actions = store.getActions();
        expect(actions.shift()).toEqual(
          channelsDataLoadPage(GAME_CHANNELS_DATA_PAYLOAD),
        );
      });
    });

    it('reinitializes if there is no load status for the current game even with some channels', () => {
      const store = createTestStore({
        data: {
          games: {
            gameNameLookup: {
              [GAME_1.name]: GAME_1.name,
            },
          },
          channels: {
            channelDetails: {
              channel: makeFakeChannel({ name: 'channel', game: GAME_1.name }),
            },
            channelsByGameLoadStatus: {
              notGame: {
                lastChannelCursor: 'blah',
                lastInitTime: 0,
                pagesLoaded: 1,
              },
            },
          },
        },
      });
      mockChannelsForGame();
      return store.dispatch(channelsDataGetPage(GAME_1.name)).then(() => {
        const actions = store.getActions();
        expect(actions.shift()).toEqual(
          channelsDataReinitializeForGame(GAME_1.name),
        );
        expect(actions.shift()).toEqual(
          channelsDataLoadPage(GAME_CHANNELS_DATA_PAYLOAD),
        );
      });
    });

    it('reinitializes if there are no channels for current game even with loadStatus and lastInitTime is fresh', () => {
      const store = createTestStore({
        data: {
          games: {
            gameNameLookup: {
              [GAME_1.name]: GAME_1.name,
            },
          },
          channels: {
            channelsByGameLoadStatus: {
              [GAME_1.name]: {
                lastInitTime: new Date().valueOf(),
                lastChannelCursor: 'chiblbl',
                pagesLoaded: 1,
              },
            },
          },
        },
      });
      mockChannelsForGame();

      return store.dispatch(channelsDataGetPage(GAME_1.name)).then(() => {
        const actions = store.getActions();
        expect(actions.shift()).toEqual(
          channelsDataReinitializeForGame(GAME_1.name),
        );
        expect(actions.shift()).toEqual(
          channelsDataLoadPage(GAME_CHANNELS_DATA_PAYLOAD),
        );
      });
    });

    it('reinitializes if there are the channels for current game and load status but lastInitTime is stale', () => {
      const store = createTestStore({
        data: {
          games: {
            gameNameLookup: {
              [GAME_1.name]: GAME_1.name,
            },
          },
          channels: {
            channelDetails: {
              channel: makeFakeChannel({ name: 'channel', game: GAME_1.name }),
            },
            channelsByGameLoadStatus: {
              [GAME_1.name]: {
                lastInitTime: 0,
                lastChannelCursor: 'blij',
                pagesLoaded: 1,
              },
            },
          },
        },
      });
      mockChannelsForGame();

      return store.dispatch(channelsDataGetPage(GAME_1.name)).then(() => {
        const actions = store.getActions();
        expect(actions.shift()).toEqual(
          channelsDataReinitializeForGame(GAME_1.name),
        );
        expect(actions.shift()).toEqual(
          channelsDataLoadPage(GAME_CHANNELS_DATA_PAYLOAD),
        );
      });
    });

    it('does not request more channels if it has some, does not need reinit, and one page opt is passed', () => {
      const store = createTestStore({
        data: {
          games: {
            gameNameLookup: {
              [GAME_1.name]: GAME_1.name,
            },
          },
          channels: {
            channelDetails: {
              channel: makeFakeChannel({ name: 'channel', game: GAME_1.name }),
            },
            channelsByGameLoadStatus: {
              [GAME_1.name]: {
                lastInitTime: new Date().valueOf(),
                lastChannelCursor: 'jbl',
                pagesLoaded: 1,
              },
            },
          },
        },
      });

      return store
        .dispatch(channelsDataGetPage(GAME_1.name, { isOnePageEnough: true }))
        .then(() => {
          expect(store.getActions().length).toEqual(0);
        });
    });

    it('loads next page when there are channels for the game and lastInitTime is fresh', () => {
      const store = createTestStore({
        data: {
          games: {
            gameNameLookup: {
              [GAME_1.name]: GAME_1.name,
            },
          },
          channels: {
            channelDetails: {
              channel: makeFakeChannel({ name: 'channel', game: GAME_1.name }),
            },
            channelsByGameLoadStatus: {
              [GAME_1.name]: {
                lastInitTime: new Date().valueOf(),
                lastChannelCursor: 'foobl',
                pagesLoaded: 1,
              },
            },
          },
        },
      });
      mockChannelsForGamePage2();

      return store.dispatch(channelsDataGetPage(GAME_1.name)).then(() => {
        const actions = store.getActions();
        expect(actions.shift()).toEqual(
          channelsDataLoadPage(GAME_PAGE_2_CHANNELS_DATA_PAYLOAD),
        );
      });
    });

    it('loads data for ALL_CHANNELS page', () => {
      const store = createTestStore();
      mockChannelsAllGames();

      return store.dispatch(channelsDataGetPage(ALL_CHANNELS)).then(() => {
        const actions = store.getActions();
        expect(actions.shift()).toEqual(
          channelsDataReinitializeForGame(ALL_CHANNELS),
        );
        expect(actions.shift()).toEqual(
          channelsDataLoadPage(ALL_GAMES_CHANNELS_DATA_PAYLOAD),
        );
      });
    });
  });

  describe('channelsDataFetchPage', () => {
    it('loads fetched data', () => {
      const store = createTestStore();
      const CURSOR = 'Y3Vyc29y';
      mockChannelsForGame();

      return store
        .dispatch(channelsDataFetchPage(GAME_1.name, CURSOR))
        .then(() => {
          const actions = store.getActions();
          expect(actions.shift()).toEqual(
            channelsDataLoadPage(GAME_CHANNELS_DATA_PAYLOAD),
          );
        });
    });

    it('signals a loading error on 404 from API', () => {
      const store = createTestStore();
      mockChannelsForGame404();

      return store
        .dispatch(channelsDataFetchPage('Alec in Space', null))
        .then(() => {
          const actions = store.getActions();
          expect(actions.shift().type).toEqual(
            CHANNELS_DATA_PAGE_FAILED_ACTION_TYPE,
          );
        });
    });
  });

  describe('channelsDataFetchChannel', () => {
    it('resolves channel details on success', () => {
      const store = createTestStore();
      mockChannel();

      return store.dispatch(channelsDataFetchChannel(CHANNEL)).then(() => {
        const actions = store.getActions();
        expect(actions.shift()).toEqual(
          channelsDataLoadChannel(CHANNEL_DATA_PAYLOAD),
        );
      });
    });

    it('rejects a not found user', () => {
      const store = createTestStore();
      mockStatusNotFound();

      return store.dispatch(channelsDataFetchChannel(NOT_CHANNEL)).then(() => {
        const actions = store.getActions();
        expect(actions.shift().type).toEqual(
          CHANNELS_DATA_CHANNEL_FAILED_ACTION_TYPE,
        );
      });
    });

    it('rejects with a banned channel', () => {
      const store = createTestStore();
      mockChannelBanned();

      return store.dispatch(channelsDataFetchChannel(NOT_CHANNEL)).then(() => {
        const actions = store.getActions();
        expect(actions.shift().type).toEqual(
          CHANNELS_DATA_CHANNEL_FAILED_ACTION_TYPE,
        );
      });
    });

    it('shortcuts a 404 for an invalid channel', () => {
      const store = createTestStore();
      // NOTE: No call to mock a fetch response.

      return store.dispatch(channelsDataFetchChannel('will.i.am')).then(() => {
        const actions = store.getActions();
        expect(actions.shift().type).toEqual(
          CHANNELS_DATA_CHANNEL_FAILED_ACTION_TYPE,
        );
      });
    });
  });

  describe('channelsDataLoadPage', () => {
    it('builds the correct payload from the JSON', () => {
      expect(channelsDataLoadPage(GAME_CHANNELS_DATA_PAYLOAD)).toEqual({
        type: CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: GAME_CHANNELS_DATA_PAYLOAD,
      });
    });
  });

  describe('channelsDataReinitializeForGame', () => {
    it('builds the correct payload', () => {
      expect(channelsDataReinitializeForGame(GAME_1.name)).toEqual({
        type: CHANNELS_DATA_REINITIALIZED_GAME_ACTION_TYPE,
        payload: {
          game: GAME_1.name,
        },
      });
    });
  });

  describe('channelsDataUpdateChannelHostingStatus', () => {
    it('fetches channel when hosting started', () => {
      const store = createTestStore();
      mockChannel();

      return store
        .dispatch(channelsDataUpdateChannelHostingStatus('voxel', CHANNEL))
        .then(() => {
          const actions = store.getActions();
          expect(actions.shift()).toEqual(
            channelsDataLoadChannel(CHANNEL_DATA_PAYLOAD),
          );
          expect(actions.shift()).toEqual(
            channelsDataBuildUpdateChannelHostingStatusAction('voxel', CHANNEL),
          );
        });
    });

    it('does not fetch when hosting ends', () => {
      const store = createTestStore();

      return store
        .dispatch(channelsDataUpdateChannelHostingStatus('voxel'))
        .then(() => {
          const actions = store.getActions();
          expect(actions.shift()).toEqual(
            channelsDataBuildUpdateChannelHostingStatusAction('voxel', ''),
          );
        });
    });
  });

  describe('channelsDataLoadChannel', () => {
    expectToReturnType(
      channelsDataLoadChannel,
      CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE,
    );
  });
});

describe('channelsData helpers', () => {
  describe('errorsContain404', () => {
    it('returns false when there are no messages', () => {
      expect(errorsContain404([])).toBeFalsy();
    });

    it('returns false when no messages match', () => {
      expect(
        errorsContain404([
          {
            path: 'a.path',
            message: 'Ya ha deedle deedle, bubba bubba deedle deedle dum.',
          },
        ]),
      ).toBeFalsy();
    });

    UNEXPECTED_404_ERROR_MESSAGES.forEach(message => {
      it(`returns true when ${message} is in error list`, () => {
        expect(
          errorsContain404([
            {
              path: 'some.path',
              message,
            },
          ]),
        ).toBeTruthy();
      });
    });
  });
});
