import expect from 'expect';

import createTestState, {
  RootStateDeepPartial,
} from 'mtest/helpers/createTestState';
import {
  expectTimeWithinBounds,
  timeBoxFunctionSeconds,
} from 'mtest/helpers/timeBox';
import { mockFetchStart, unMockFetchStart } from 'mtest/helpers/fetchStartMock';
import { GAME_CHANNELS_DATA_PAYLOAD } from 'mtest/fetchMocks/channels';

import { RootState } from 'mweb/common/reducers/root';
import { mapActionToEvent } from 'mweb/common/tracking/middleware';
import {
  trackingClickTrackedActionBuilder,
  trackingTrackSubscribe,
  trackingCaptureTrackingParams,
  trackingPageViewTrackedActionBuilder,
  trackingTrackProfilePageView,
} from 'mweb/common/actions/tracking';
import { channelsDataLoadPage } from 'mweb/common/actions/data/channels';
import {
  eventDetailsPageRequestNotification,
  eventDetailsPageShareEvent,
} from 'mweb/common/actions/pages/eventDetails';
import { gamesDataLoadPage } from 'mweb/common/actions/data/games';
import { Location } from 'mweb/common/reducers/app';
import {
  ExperimentGroups,
  EXPERIMENTS_RECORD_DECISION_ACTION_TYPE,
  ExperimentNames,
  ExperimentUUIDs,
} from 'mweb/common/actions/experiments';
import {
  DirectoryPageLoadedSpadeData,
  getChannelsPageLoadedSpadeData,
  getGamesPageLoadedSpadeData,
} from 'mweb/common/selectors/tracking/dataLoad';
import {
  getEventNotificationRequestedSpadeData,
  getEventSharedSpadeData,
} from 'mweb/common/selectors/tracking/events';
import { getExperimentBranchSpadeData } from 'mweb/common/selectors/tracking/experiments';
import {
  getPageViewSpadeData,
  getProfileMetricsSpadeData,
} from 'mweb/common/selectors/tracking/pageview';
import { getClickTrackedSpadeData } from 'mweb/common/selectors/tracking/interaction';
import { getSubscribeButtonData } from 'mweb/common/selectors/tracking/subscribe';
import { Action } from 'mweb/common/actions/root';
import { SpadeDataWithTime } from 'mweb/common/selectors/tracking/base';

describe('tracking middleware', () => {
  describe('mapActionToEvent', () => {
    beforeEach(mockFetchStart);
    afterEach(unMockFetchStart);

    interface ActionToEventTestCase {
      action: Action;
      eventDataGenerator: Function;
      eventName: string;
      stateSeed?: RootStateDeepPartial;
    }

    const actionToEventTestCases: ActionToEventTestCase[] = [
      {
        action: {
          type: EXPERIMENTS_RECORD_DECISION_ACTION_TYPE as typeof EXPERIMENTS_RECORD_DECISION_ACTION_TYPE,
          payload: {
            uuid: ExperimentUUIDs.Test,
            name: ExperimentNames.Test,
            group: ExperimentGroups.Control,
          },
        },
        eventDataGenerator: getExperimentBranchSpadeData,
        eventName: 'experiment_branch',
      },
      {
        action: trackingPageViewTrackedActionBuilder(),
        eventDataGenerator: (state: RootState, _: string) => {
          return getPageViewSpadeData(state, document.referrer);
        },
        eventName: 'pageview',
        stateSeed: {
          app: {
            location: Location.DirectoryMainGame,
          },
        },
      },
      {
        action: trackingTrackProfilePageView(),
        eventDataGenerator: (state: RootState, _: string) => {
          return getProfileMetricsSpadeData(state);
        },
        eventName: 'profile_page_view',
        stateSeed: {
          app: {
            location: Location.ChannelProfile,
          },
        },
      },
      {
        action: gamesDataLoadPage({
          gameDetails: {},
          lastGameCursor: 'foo',
        }),
        eventDataGenerator: (
          state: RootState,
          _: any,
        ): DirectoryPageLoadedSpadeData => {
          return getGamesPageLoadedSpadeData(state, {
            gamesLoaded: 0,
          });
        },
        eventName: 'DIRECTORY_DATA_RECEIVED_EVENT_TYPE',
      },
      {
        action: channelsDataLoadPage(GAME_CHANNELS_DATA_PAYLOAD),
        eventDataGenerator: (
          state: RootState,
          _: any,
        ): DirectoryPageLoadedSpadeData => {
          return getChannelsPageLoadedSpadeData(state, {
            channelsLoaded: Object.keys(
              GAME_CHANNELS_DATA_PAYLOAD.channelDetails,
            ).length,
            game: GAME_CHANNELS_DATA_PAYLOAD.game!.name,
          });
        },
        eventName: 'DIRECTORY_DATA_RECEIVED_EVENT_TYPE',
      },
      {
        action: eventDetailsPageRequestNotification(
          'yahoo_calendar',
          Location.EventDetails,
        ),
        eventDataGenerator: getEventNotificationRequestedSpadeData,
        eventName: 'oracle_user_notification_client',
      },
      {
        action: eventDetailsPageShareEvent('facebook', Location.EventDetails),
        eventDataGenerator: getEventSharedSpadeData,
        eventName: 'oracle_event_share',
      },
      {
        action: trackingClickTrackedActionBuilder({
          interactionMedium: 'medium',
          interactionContent: 'content',
          interactionTargetPath: '/path',
        }),
        eventDataGenerator: getClickTrackedSpadeData,
        eventName: 'ui_interaction',
      },
      {
        action: trackingTrackSubscribe({
          interactionMedium: 'medium',
          interactionContent: 'content',
          interactionTargetPath: '/subs',
          interactionSubscribe: true,
        }),
        eventDataGenerator: getSubscribeButtonData,
        eventName: 'subscribe_button',
        stateSeed: {
          app: {
            location: Location.Channel,
          },
          pages: {
            channelViewer: {
              currentChannel: 'bigandy',
            },
          },
          data: {
            channels: {
              channelDetails: {
                bigandy: {
                  name: 'bigandy',
                  id: '123',
                },
              },
            },
          },
        },
      },
    ];

    actionToEventTestCases.forEach(
      ({ action, eventDataGenerator, eventName, stateSeed }) => {
        it(`maps ${action.type}${location
          ? ` for location ${location}`
          : ''}`, () => {
          const state = createTestState(stateSeed);
          const { result, bounds } = timeBoxFunctionSeconds(() =>
            mapActionToEvent(action, state),
          );
          const expectedPropertes = eventDataGenerator(
            state,
            (action as any).payload,
          );
          if (expectedPropertes.time) {
            const time = (result!
              .properties as SpadeDataWithTime).time.valueOf();
            delete (result!.properties as any).time;
            expectTimeWithinBounds(time, bounds);
            delete expectedPropertes.time;
          }
          expect(result).toEqual({
            event: eventName,
            properties: expectedPropertes,
          });
        });
      },
    );

    interface NoMappingTestCase {
      action: Action;
      location: Location;
    }

    const noMappingTestCases: NoMappingTestCase[] = [
      {
        action: trackingCaptureTrackingParams({
          tt_medium: 'medium',
          tt_content: 'content',
        }),
        location: Location.Channel,
      },
      {
        action: trackingTrackSubscribe({
          interactionMedium: 'medium',
          interactionContent: 'content',
          interactionTargetPath: '/subs',
          interactionSubscribe: true,
        }),
        location: Location.DirectoryMainGame,
      },
    ];

    noMappingTestCases.forEach(({ action, location }) => {
      it(`does not map ${action.type}${location
        ? ` for location ${location}`
        : ''}`, () => {
        const state = createTestState({ app: { location } });
        expect(mapActionToEvent(action, state)).toEqual(undefined);
      });
    });
  });
});
