import omit from 'lodash-es/omit';

import { Action } from 'mweb/common/actions/root';

import {
  PLATFORM_SWITCHED_TO_SERVER_ACTION_TYPE,
  PLATFORM_SWITCHED_TO_CLIENT_ACTION_TYPE,
} from 'mweb/common/actions/platform';
import {
  ERROR_UNHANDLED_ERROR_OCCURRED_ACTION_TYPE,
  ERROR_ERROR_STATUS_RESET_ACTION_TYPE,
} from 'mweb/common/actions/error';
import {
  NAVIGATION_UPDATED_LOCATION_ACTION_TYPE,
  NAVIGATION_SWITCHED_TO_DESKTOP_ACTION_TYPE,
  NAVIGATION_LOADED_BRANCH_DEEPLINK_URL,
} from 'mweb/common/actions/navigation';
import {
  EVENTS_DATA_EVENT_LOADED_ACTION_TYPE,
  EVENTS_DATA_EVENT_FAILED_ACTION_TYPE,
} from 'mweb/common/actions/data/events';
import {
  CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE,
  CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE,
  CHANNELS_DATA_PAGE_FAILED_ACTION_TYPE,
  CHANNELS_DATA_CHANNEL_FAILED_ACTION_TYPE,
} from 'mweb/common/actions/data/channels';
import {
  VODS_DATA_VOD_LOADED_ACTION_TYPE,
  VODS_DATA_VOD_FAILED_ACTION_TYPE,
} from 'mweb/common/actions/data/vods';
import {
  GAMES_DATA_PAGE_LOADED_ACTION_TYPE,
  GAMES_DATA_PAGE_FAILED_ACTION_TYPE,
} from 'mweb/common/actions/data/games';
import { OptOutCookieName } from 'mweb/common/utils/cookie';
import { StatusCode } from 'mweb/common/reducers/root';
import { GQLError, GQLErrorType } from 'mweb/common/fetch/fetchGQL';

export enum Platform {
  Server = 'server',
  Client = 'client',
  Unknown = 'unknown',
}

export enum Location {
  Channel = 'channel',
  ChannelProfile = 'channel_profile',
  VOD = 'vod',
  Upsell = 'mobile_upsell',
  DirectoryMainGame = 'directory_main_game',
  DirectoryGame = 'directory_game',
  EventDetails = 'event_details',
  ChatEmbed = 'chat_embed',
  Unknown = 'unknown',
}

export type MobileOptOutState = OptOutCookieName | '';

export enum ErrorCondition {
  ApiError = 'api error',
  ApiTimeout = 'api timeout',
  Unknown = 'unknown',
}

export interface AppState {
  readonly mobileOptOut: MobileOptOutState;
  readonly location: Location;
  readonly status: number;
  readonly url: string;
  readonly platform: Platform;
  readonly gitHash: string;
  readonly errorCondition?: ErrorCondition;
  readonly branchDeeplinkURL?: string;
}

const DEFAULT_APP_STATE: AppState = {
  mobileOptOut: '',
  // Assume a success state. Failures can update this to a non-success state.
  // Clearly this needs to be refactored into something better.
  status: 200,
  location: Location.Unknown,
  url: '',
  platform: Platform.Unknown,
  gitHash: '',
};

export function app(
  state: AppState = DEFAULT_APP_STATE,
  action: Action,
): AppState {
  switch (action.type) {
    case PLATFORM_SWITCHED_TO_SERVER_ACTION_TYPE:
      return {
        ...state,
        platform: Platform.Server,
        gitHash: action.payload.gitHash,
      };
    case PLATFORM_SWITCHED_TO_CLIENT_ACTION_TYPE:
      return {
        ...state,
        platform: Platform.Client,
      };

    case ERROR_UNHANDLED_ERROR_OCCURRED_ACTION_TYPE:
      return {
        ...state,
        status: 500,
        errorCondition: ErrorCondition.Unknown,
      };
    case ERROR_ERROR_STATUS_RESET_ACTION_TYPE:
      return {
        ...omit(state, 'errorCondition'),
        status: DEFAULT_APP_STATE.status,
      } as AppState;

    case NAVIGATION_UPDATED_LOCATION_ACTION_TYPE:
      return {
        ...state,
        url: typeof window !== 'undefined' ? window.location.href : '',
        location: action.payload.location,
      };
    case NAVIGATION_SWITCHED_TO_DESKTOP_ACTION_TYPE:
      return {
        ...state,
        mobileOptOut: action.payload.cookieName,
      };
    case NAVIGATION_LOADED_BRANCH_DEEPLINK_URL:
      return {
        ...state,
        branchDeeplinkURL: action.payload.url,
      };

    case GAMES_DATA_PAGE_LOADED_ACTION_TYPE:
    case CHANNELS_DATA_PAGE_LOADED_ACTION_TYPE:
    case CHANNELS_DATA_CHANNEL_LOADED_ACTION_TYPE:
    case VODS_DATA_VOD_LOADED_ACTION_TYPE:
    case EVENTS_DATA_EVENT_LOADED_ACTION_TYPE:
      return {
        ...omit(state, 'errorCondition'),
        status: 200,
      } as AppState;
    case GAMES_DATA_PAGE_FAILED_ACTION_TYPE:
    case CHANNELS_DATA_PAGE_FAILED_ACTION_TYPE:
    case CHANNELS_DATA_CHANNEL_FAILED_ACTION_TYPE:
    case VODS_DATA_VOD_FAILED_ACTION_TYPE:
    case EVENTS_DATA_EVENT_FAILED_ACTION_TYPE:
      return {
        ...state,
        status: action.payload.status || StatusCode.INTERNAL_SERVER_ERROR,
        errorCondition: getFailedActionTypeErrorCondition(action.payload),
      };
    default:
      return state;
  }
}

/**
 * Determine if a payload is a Response or a GQLError.
 *
 * @param payload
 */
function isResponse(payload: Response | GQLError): payload is Response {
  return (payload as Response).headers !== undefined;
}

/**
 * Return an ErrorCondition for the given payload from a failed action to be
 * used by failed actions.
 *
 * @param payload The payload from the failed action type.
 */
function getFailedActionTypeErrorCondition(
  payload: Response | GQLError,
): ErrorCondition | undefined {
  if (payload.status === StatusCode.NOT_FOUND) {
    return;
  }
  const isTimeout =
    payload.type === GQLErrorType.RequestTimeout ||
    payload.type === GQLErrorType.BodyTimeout;
  if (isTimeout) {
    return ErrorCondition.ApiTimeout;
  }
  const isAPIErrorStatus = 500 <= payload.status && payload.status < 600;
  if (payload.status && (isAPIErrorStatus || !isResponse(payload))) {
    return ErrorCondition.ApiError;
  }
  return ErrorCondition.Unknown;
}
