import expect from 'expect';
import faker from 'faker';

import createTestState from 'mtest/helpers/createTestState';
import { timeBoxFunction, expectTimeWithinBounds } from 'mtest/helpers/timeBox';

import { Action } from 'mweb/common/actions/root';
import {
  buildChannelsReducer,
  ChannelOnlineStatus,
  ChannelDetails,
  ChannelSubscribableStatus,
} from 'mweb/common/reducers/data/channels';
import {
  CHANNELS_DATA_REINITIALIZED_GAME_ACTION_TYPE,
  CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
  CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE,
  CHANNELS_DATA_CHANNEL_HOSTING_STATUS_UPDATED_ACTION_TYPE,
} from 'mweb/common/actions/data/channels';
import { EventType, EventModel } from 'mweb/common/reducers/data/events';
import { EVENTS_DATA_EVENT_LOADED_ACTION_TYPE } from 'mweb/common/actions/data/events';

describe('channelsData reducer', () => {
  const CHANNEL_1_DETAILS: ChannelDetails = {
    displayName: 'Channel 1',
    game: 'game1',
    logoURL: '//channel1.logo',
    hostedChannel: undefined,
    id: '1',
    name: 'channel1',
    onlineStatus: ChannelOnlineStatus.Unknown,
    preview: undefined,
    status: undefined,
    viewerCount: 0,
    subscribableStatus: ChannelSubscribableStatus.CanSubscribe,
    followerCount: 123,
    lifetimeViewerCount: 420,
    description: 'Biggest Andys Fandies',
    bannerImageURL: '//andytime.logo',
  };

  const CHANNEL_2_DETAILS: ChannelDetails = {
    displayName: 'Channel 2',
    game: 'game2',
    logoURL: '//channel2.logo',
    hostedChannel: undefined,
    id: '2',
    name: 'channel2',
    onlineStatus: ChannelOnlineStatus.Unknown,
    preview: undefined,
    status: undefined,
    viewerCount: 0,
    subscribableStatus: ChannelSubscribableStatus.CanSubscribe,
    followerCount: 0,
    lifetimeViewerCount: 0,
    description: 'Big Andys Fandies',
    bannerImageURL: '//banner.logo',
  };

  const GAME_1_STATUS = {
    lastInitTime: 1,
    cursor: 'AA==',
    pagesLoaded: 1,
  };

  const GAME_2_STATUS = {
    lastInitTime: 1,
    cursor: 'AB==',
    pagesLoaded: 1,
  };

  const EVENT_1_MODEL: EventModel = {
    type: EventType.SingleEvent,
    id: 'e1',
    startTime: 0,
    endTime: 0,
    title: faker.company.catchPhrase(),
    coverImageTemplateURL: faker.image.imageUrl(),
    description: faker.lorem.paragraph(),
    channel: CHANNEL_1_DETAILS.name,
  };

  it('sets defaults', () => {
    expect(buildChannelsReducer()(undefined, { type: 'NOOP' })).toEqual({
      channelDetails: {},
      channelsByGameLoadStatus: {},
    });
  });

  describe('game reinitialization', () => {
    it('restores target game to defaults on reinitialization', () => {
      const initialState = createTestState({
        data: {
          channels: {
            channelDetails: {
              channel1: CHANNEL_1_DETAILS,
              channel2: CHANNEL_2_DETAILS,
            },
            channelsByGameLoadStatus: {
              game1: GAME_1_STATUS,
              game2: GAME_2_STATUS,
            },
          },
        },
      }).data.channels;
      const action: Action = {
        type: CHANNELS_DATA_REINITIALIZED_GAME_ACTION_TYPE,
        payload: {
          game: 'game1',
        },
      };

      const newState = buildChannelsReducer()(initialState, action);
      expect(newState.channelDetails).toEqual({ channel2: CHANNEL_2_DETAILS });
      expect(newState.channelsByGameLoadStatus).toEqual({
        game2: GAME_2_STATUS,
      });
    });
  });

  describe('page loading', () => {
    it('loads page data into an empty list and sets lastInitTime and pagesLoaded correctly', () => {
      const initialState = createTestState().data.channels;
      const action: Action = {
        type: CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: {
          channelDetails: {
            channel1: CHANNEL_1_DETAILS,
          },
          gameAliasUsed: 'game1',
          game: undefined,
          cursor: 'bar',
        },
      };

      const { result: newState, bounds } = timeBoxFunction(() =>
        buildChannelsReducer()(initialState, action),
      );
      expect(Object.keys(newState.channelDetails).length).toEqual(1);
      expect(newState.channelDetails.channel1).toEqual(CHANNEL_1_DETAILS);
      expect(Object.keys(newState.channelsByGameLoadStatus).length).toEqual(1);

      const game1Details = newState.channelsByGameLoadStatus.game1;
      expectTimeWithinBounds(game1Details.lastInitTime, bounds);
      expect(game1Details.lastChannelCursor).toEqual('bar');
      expect(game1Details.pagesLoaded).toEqual(1);
    });

    it('adds page data to a list already containing other games', () => {
      const initialState = createTestState({
        data: {
          channels: {
            channelDetails: {
              channel2: CHANNEL_2_DETAILS,
            },
            channelsByGameLoadStatus: {
              game2: GAME_2_STATUS,
            },
          },
        },
      }).data.channels;
      const action: Action = {
        type: CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: {
          channelDetails: {
            channel1: CHANNEL_1_DETAILS,
          },
          gameAliasUsed: 'game1',
          game: undefined,
          cursor: 'bar',
        },
      };

      const { result: newState, bounds } = timeBoxFunction(
        buildChannelsReducer().bind(undefined, initialState, action),
      );
      expect(Object.keys(newState.channelDetails).length).toEqual(2);
      expect(newState.channelDetails.channel1).toEqual(CHANNEL_1_DETAILS);
      expect(newState.channelDetails.channel2).toEqual(CHANNEL_2_DETAILS);
      expect(Object.keys(newState.channelsByGameLoadStatus).length).toEqual(2);
      expect(newState.channelsByGameLoadStatus.game2).toEqual(GAME_2_STATUS);

      const game1Details = newState.channelsByGameLoadStatus.game1;
      expect(game1Details.lastChannelCursor).toEqual('bar');
      expectTimeWithinBounds(game1Details.lastInitTime, bounds);
      expect(game1Details.pagesLoaded).toEqual(1);
    });

    it('does not overwrite an existing lastInitTime for a game', () => {
      const initialState = createTestState({
        data: {
          channels: {
            channelDetails: {
              channel1: CHANNEL_1_DETAILS,
            },
            channelsByGameLoadStatus: {
              game1: GAME_1_STATUS,
            },
          },
        },
      }).data.channels;
      const action: Action = {
        type: CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: {
          channelDetails: {
            channel1: CHANNEL_1_DETAILS,
          },
          gameAliasUsed: 'game1',
          game: undefined,
          cursor: 'AB==',
        },
      };

      const newState = buildChannelsReducer()(initialState, action);
      expect(Object.keys(newState.channelsByGameLoadStatus).length).toEqual(1);
      expect(newState.channelsByGameLoadStatus.game1.lastInitTime).toEqual(
        GAME_1_STATUS.lastInitTime,
      );
    });

    it('increments the pages loaded count with each load', () => {
      const initialState = createTestState({
        data: {
          channels: {
            channelDetails: {
              channel1: CHANNEL_1_DETAILS,
            },
            channelsByGameLoadStatus: {
              game1: GAME_1_STATUS,
            },
          },
        },
      }).data.channels;
      const action: Action = {
        type: CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
        payload: {
          channelDetails: {
            channel1: CHANNEL_1_DETAILS,
          },
          gameAliasUsed: 'game1',
          game: undefined,
          cursor: 'AA==',
        },
      };

      const newState = buildChannelsReducer()(initialState, action);
      expect(Object.keys(newState.channelsByGameLoadStatus).length).toEqual(1);
      expect(newState.channelsByGameLoadStatus.game1.pagesLoaded).toEqual(
        GAME_1_STATUS.pagesLoaded + 1,
      );
    });
  });

  describe('channel loading', () => {
    it('adds channel data to an empty list', () => {
      const initialState = createTestState().data.channels;
      const action: Action = {
        type: CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE,
        payload: {
          channel: CHANNEL_1_DETAILS,
          hostedChannel: undefined,
          videos: {},
          clips: {},
        },
      };

      const newState = buildChannelsReducer()(initialState, action);
      expect(Object.keys(newState.channelDetails).length).toEqual(1);
      expect(newState.channelDetails.channel1).toEqual(CHANNEL_1_DETAILS);
      expect(Object.keys(newState.channelsByGameLoadStatus).length).toEqual(0);
    });

    it('adds channel data to an existing list', () => {
      const initialState = createTestState({
        data: {
          channels: {
            channelDetails: {
              channel2: CHANNEL_2_DETAILS,
            },
            channelsByGameLoadStatus: {
              game2: GAME_2_STATUS,
            },
          },
        },
      }).data.channels;
      const action: Action = {
        type: CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE,
        payload: {
          channel: CHANNEL_1_DETAILS,
          hostedChannel: undefined,
          videos: {},
          clips: {},
        },
      };

      const newState = buildChannelsReducer()(initialState, action);
      expect(Object.keys(newState.channelDetails).length).toEqual(2);
      expect(newState.channelDetails.channel1).toEqual(CHANNEL_1_DETAILS);
      expect(newState.channelDetails.channel2).toEqual(CHANNEL_2_DETAILS);
      expect(Object.keys(newState.channelsByGameLoadStatus).length).toEqual(1);
    });

    it('updates an existing entry', () => {
      const channel1 = CHANNEL_1_DETAILS;
      const initialState = createTestState({
        data: {
          channels: {
            channelDetails: {
              channel1: {
                ...(channel1 as ChannelDetails),
                displayName: '🌈',
                preview: '//somewhere.under.the.rainbow',
                viewerCount: 42,
              },
            },
          },
        },
      }).data.channels;
      const action: Action = {
        type: CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE,
        payload: {
          channel: CHANNEL_1_DETAILS,
          hostedChannel: undefined,
          videos: {},
          clips: {},
        },
      };

      const newState = buildChannelsReducer()(initialState, action);
      expect(Object.keys(newState.channelDetails).length).toEqual(1);
      expect(newState.channelDetails.channel1.displayName).toEqual(
        CHANNEL_1_DETAILS.displayName,
      );
      expect(newState.channelDetails.channel1.preview).toEqual(
        CHANNEL_1_DETAILS.preview,
      );
      expect(newState.channelDetails.channel1.viewerCount).toEqual(
        CHANNEL_1_DETAILS.viewerCount,
      );
    });

    it('adds channels when they come with an event load', () => {
      const initialState = createTestState().data.channels;
      const action: Action = {
        type: EVENTS_DATA_EVENT_LOADED_ACTION_TYPE,
        payload: {
          channel: CHANNEL_1_DETAILS,
          event: EVENT_1_MODEL,
          game: undefined,
          vod: undefined,
        },
      };

      const newState = buildChannelsReducer()(initialState, action);
      expect(Object.keys(newState.channelDetails).length).toEqual(1);
      expect(newState.channelDetails.channel1).toEqual(CHANNEL_1_DETAILS);
      expect(Object.keys(newState.channelsByGameLoadStatus).length).toEqual(0);
    });
  });

  describe('hosting status updates', () => {
    it('updates the hosting status of the target channel', () => {
      const initialState = createTestState({
        data: {
          channels: {
            channelDetails: {
              channel1: CHANNEL_1_DETAILS,
              channel2: CHANNEL_2_DETAILS,
            },
          },
        },
      }).data.channels;

      const action: Action = {
        type: CHANNELS_DATA_CHANNEL_HOSTING_STATUS_UPDATED_ACTION_TYPE,
        payload: {
          channel: CHANNEL_1_DETAILS.name,
          hostedChannel: CHANNEL_2_DETAILS.name,
        },
      };

      const newState = buildChannelsReducer()(initialState, action);
      expect(newState.channelDetails.channel1.hostedChannel).toEqual(
        CHANNEL_2_DETAILS.name,
      );
      expect(newState.channelDetails.channel2.hostedChannel).toEqual(undefined);
    });
  });

  it('does not error when target channel is somehow not loaded', () => {
    const initialState = createTestState({
      data: {
        channels: {
          channelDetails: {
            channel1: CHANNEL_1_DETAILS,
          },
        },
      },
    }).data.channels;

    const action: Action = {
      type: CHANNELS_DATA_CHANNEL_HOSTING_STATUS_UPDATED_ACTION_TYPE,
      payload: {
        channel: CHANNEL_2_DETAILS.name,
        hostedChannel: CHANNEL_1_DETAILS.name,
      },
    };

    const newState = buildChannelsReducer()(initialState, action);
    expect(Object.keys(newState.channelDetails).length).toEqual(1);
    expect(newState.channelDetails.channel1).toEqual(CHANNEL_1_DETAILS);
  });
});
