import { Store, Dispatch } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { Reporter } from 'tachyon-spade-reporter';

import { SpadeEvent, Tracker } from 'mweb/common/tracking/tracker';
import { RootState } from 'mweb/common/reducers/root';
import { logger } from 'tachyon-logger';
import {
  TRACKING_CLICK_TRACKED_ACTION_TYPE,
  TRACKING_SUBSCRIBE_TRACKED_ACTION_TYPE,
  TRACKING_LATENCY_TRACKED_ACTION_TYPE,
  TRACKING_PAGE_VIEW_TRACKED_ACTION_TYPE,
  TRACKING_PROFILE_PAGE_VIEW_TRACKED_ACTION_TYPE,
} from 'mweb/common/actions/tracking';
import {
  EVENT_DETAILS_PAGE_EVENT_SHARED_ACTION_TYPE,
  EVENT_DETAILS_PAGE_NOTIFICATION_REQUESTED_ACTION_TYPE,
} from 'mweb/common/actions/pages/eventDetails';
import { CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE } from 'mweb/common/actions/data/channels';
import { GAMES_DATA_PAGE_LOADED_ACTION_TYPE } from 'mweb/common/actions/data/games';
import { EXPERIMENTS_RECORD_DECISION_ACTION_TYPE } from 'mweb/common/actions/experiments';
import { Action } from 'mweb/common/actions/root';
import {
  getGamesPageLoadedSpadeData,
  getChannelsPageLoadedSpadeData,
} 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';

type UnknownAction<R, S, E> = ThunkAction<R, S, E> | Action;

export const trackerMiddleware = (eventReporter: Reporter) => <R, S, E>(
  store: Store<S>,
): Dispatch<S> => {
  let pendingActions: Action[] = [];
  let tracker: Tracker | undefined;

  return (next: any) => (action: UnknownAction<R, S, E>) => {
    const result = next(action as ThunkAction<R, S, E>);
    const state = (store.getState() as any) as RootState;
    tracker = tracker || buildTracker(state, eventReporter);

    if (isTrackableAction(action)) {
      if (tracker) {
        tracker.trackEvent(mapActionToEvent(action, state));

        while (pendingActions.length > 0) {
          tracker.trackEvent(mapActionToEvent(pendingActions.shift()!, state));
        }
      } else {
        pendingActions.push(action);
      }
    }

    return result;
  };
};

function buildTracker(
  state: RootState,
  eventReporter: Reporter,
): Tracker | undefined {
  if (state.app.gitHash && state.device.deviceID && state.device.sessionID) {
    return new Tracker(
      eventReporter,
      state.app.gitHash,
      state.device.deviceID,
      state.device.sessionID,
    );
  }
}

function isTrackableAction<R, S, E>(
  action: UnknownAction<R, S, E>,
): action is Action {
  return (<Action>action).type !== undefined;
}

export function mapActionToEvent(
  action: Action,
  state: RootState,
): SpadeEvent | undefined {
  switch (action.type) {
    case EXPERIMENTS_RECORD_DECISION_ACTION_TYPE:
      return {
        event: 'experiment_branch',
        properties: getExperimentBranchSpadeData(state, action.payload),
      };
    case TRACKING_PAGE_VIEW_TRACKED_ACTION_TYPE:
      return {
        event: 'pageview',
        properties: getPageViewSpadeData(state, action.payload.referrer),
      };
    case TRACKING_PROFILE_PAGE_VIEW_TRACKED_ACTION_TYPE:
      return {
        event: 'profile_page_view',
        properties: getProfileMetricsSpadeData(state),
      };
    case GAMES_DATA_PAGE_LOADED_ACTION_TYPE:
      return {
        event: 'DIRECTORY_DATA_RECEIVED_EVENT_TYPE',
        properties: getGamesPageLoadedSpadeData(state, {
          gamesLoaded: Object.keys(action.payload.gameDetails).length,
        }),
      };
    case CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE:
      return {
        event: 'DIRECTORY_DATA_RECEIVED_EVENT_TYPE',
        properties: getChannelsPageLoadedSpadeData(state, {
          game: action.payload.game
            ? action.payload.game.name
            : action.payload.gameAliasUsed,
          channelsLoaded: Object.keys(action.payload.channelDetails).length,
        }),
      };
    case EVENT_DETAILS_PAGE_NOTIFICATION_REQUESTED_ACTION_TYPE:
      return {
        event: 'oracle_user_notification_client',
        properties: getEventNotificationRequestedSpadeData(
          state,
          action.payload,
        ),
      };
    case EVENT_DETAILS_PAGE_EVENT_SHARED_ACTION_TYPE:
      return {
        event: 'oracle_event_share',
        properties: getEventSharedSpadeData(state, action.payload),
      };
    case TRACKING_LATENCY_TRACKED_ACTION_TYPE:
      return action.payload;
    case TRACKING_CLICK_TRACKED_ACTION_TYPE:
      return {
        event: 'ui_interaction',
        properties: getClickTrackedSpadeData(state, action.payload),
      };
    case TRACKING_SUBSCRIBE_TRACKED_ACTION_TYPE:
      const properties = getSubscribeButtonData(state);
      if (properties) {
        return {
          event: 'subscribe_button',
          properties,
        };
      }
      logger.debug([
        'Subscribe called with invalid location:',
        state.app.location,
      ]);
      break;
    default:
      if (process.env.NODE_ENV !== 'production') {
        logger.debug({
          message: 'No mapping to event for this action',
          action,
        });
      }
  }
}
