import { combineReducers } from 'redux';
import omit from 'lodash-es/omit';
import omitBy from 'lodash-es/omitBy';

import { Action } from 'mweb/common/actions/root';
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 { VODS_DATA_VOD_LOADED_ACTION_TYPE } from 'mweb/common/actions/data/vods';
import { EVENTS_DATA_EVENT_LOADED_ACTION_TYPE } from 'mweb/common/actions/data/events';
import { logger } from 'tachyon-logger';
import { ChannelDataPayload } from 'mweb/common/fetch/channels';

export const ALL_CHANNELS = 'CHANNEL_DIRECTORY_FOR_ALL_GAMES';
export const EMPTY_PAGE_URL = 'EMPTY_PAGE_URL';

export enum ChannelOnlineStatus {
  Online = 'online',
  Offline = 'offline',
  Unknown = 'unknown',
}

/**
 * Assertion for ChannelOnlineStatus switches.
 */
export function assertChannelOnlineStatus(_: never): never {
  throw 'Invalid value for ChannelOnlineStatus';
}

export enum ChannelSubscribableStatus {
  CanSubscribe = 'canSubscribe',
  CannotSubscribe = 'cannotSubscribe',
  Unknown = 'unknown',
}

export interface ChannelDetails {
  readonly displayName: string;
  readonly id: string;
  readonly name: string;
  readonly onlineStatus: ChannelOnlineStatus;
  readonly viewerCount: number;
  readonly subscribableStatus: ChannelSubscribableStatus;
  readonly followerCount: number;
  readonly lifetimeViewerCount: number;
  readonly preview: string | undefined;
  readonly status: string | undefined;
  readonly logoURL: string | undefined;
  readonly game: string | undefined;
  readonly hostedChannel: string | undefined;
  readonly description: string | undefined;
  readonly bannerImageURL: string | undefined;
}

export interface MutableChannelDetailsMap {
  [channelName: string]: ChannelDetails;
}

export type ChannelDetailsMap = Readonly<MutableChannelDetailsMap>;

export interface ChannelsByGameLoadStatus {
  readonly lastInitTime: number;
  readonly lastChannelCursor: string | null;
  readonly pagesLoaded: number;
}

export interface ChannelsByGameLoadStatusMap {
  readonly [game: string]: ChannelsByGameLoadStatus;
}

export interface ChannelsState {
  readonly channelDetails: ChannelDetailsMap;
  readonly channelsByGameLoadStatus: ChannelsByGameLoadStatusMap;
}

function channelDetails(
  state: ChannelDetailsMap = {},
  action: Action,
): ChannelDetailsMap {
  switch (action.type) {
    case CHANNELS_DATA_REINITIALIZED_GAME_ACTION_TYPE:
      return omitBy(
        state,
        channel => channel.game === action.payload.game,
      ) as ChannelDetailsMap;
    case CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE:
      return {
        ...state,
        ...action.payload.channelDetails,
      };
    case CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE:
    case VODS_DATA_VOD_LOADED_ACTION_TYPE:
    case EVENTS_DATA_EVENT_LOADED_ACTION_TYPE:
      if (!action.payload.channel) {
        return state;
      }
      const hostedChannel = (action.payload as ChannelDataPayload)
        .hostedChannel;
      if (hostedChannel) {
        return {
          ...state,
          [action.payload.channel.name]: {
            ...action.payload.channel,
          },
          [hostedChannel.name]: {
            ...hostedChannel,
          },
        };
      }
      return {
        ...state,
        [action.payload.channel.name]: {
          ...action.payload.channel,
        },
      };
    case CHANNELS_DATA_CHANNEL_HOSTING_STATUS_UPDATED_ACTION_TYPE:
      const channelDetailsForHosting = state[action.payload.channel];
      if (channelDetailsForHosting) {
        return {
          ...state,
          [action.payload.channel]: {
            ...channelDetailsForHosting,
            hostedChannel: action.payload.hostedChannel,
          },
        };
      }
      logger.error('Received hosting status update for un-loaded channel');
      return state;
    default:
      return state;
  }
}

export const CHANNEL_DIRECTORY_CACHE_TTL = 60 * 1000; // 1 minute
function channelsByGameLoadStatus(
  state: ChannelsByGameLoadStatusMap = {},
  action: Action,
): ChannelsByGameLoadStatusMap {
  switch (action.type) {
    case CHANNELS_DATA_REINITIALIZED_GAME_ACTION_TYPE:
      return omit(state, action.payload.game) as ChannelsByGameLoadStatusMap;
    case CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE:
      const gameName = action.payload.game
        ? action.payload.game.name
        : action.payload.gameAliasUsed;
      return {
        ...state,
        [gameName]: {
          lastInitTime:
            (state[gameName] && state[gameName].lastInitTime) ||
            new Date().valueOf(),
          lastChannelCursor: action.payload.cursor,
          pagesLoaded:
            (state[gameName] && state[gameName].pagesLoaded + 1) || 1,
        },
      };
    default:
      return state;
  }
}

export function buildChannelsReducer(): (
  state: ChannelsState | undefined,
  action: Action,
) => ChannelsState {
  return combineReducers<ChannelsState>({
    channelDetails,
    channelsByGameLoadStatus,
  });
}
