import { uniqueId } from "lodash";
import { call, put, select, takeEvery } from "redux-saga/effects";

import {
  rulesEngineSelectors,
  characterActionTypes,
  characterActions,
  syncTransactionActions,
  syncTransactionSelectors,
  ApiAdapterUtils,
  ApiRequests,
  UnpackMakeApiResponseData,
  ruleDataActions,
  SagaHelpers,
  characterEnvSelectors,
  Constants,
  RaceUtils,
} from "@dndbeyond/character-rules-engine/es";

import { BuilderMethod, RouteKey } from "~/subApps/builder/constants";

import { appEnvActions } from "../../Shared/actions";
import { appEnvSelectors } from "../../Shared/selectors";
import appConfig from "../../config";
import {
  builderActions,
  builderActionTypes,
  BuilderAction,
  BuilderMethodSetAction,
  StepBuildRequestAction,
  SuggestedNamesRequestAction,
} from "../actions";
import * as navConfig from "../config/navigation";
import { getRouteDefPath } from "../config/navigation";
import { builderSelectors } from "../selectors";
import { NavigationUtils } from "../utils";

const syncActions = [
  builderActionTypes.CHARACTER_LOAD_REQUEST,
  builderActionTypes.STEP_BUILD_REQUEST,
  builderActionTypes.BUILDER_METHOD_SET,
  builderActionTypes.SUGGESTED_NAMES_REQUEST,
];

export default function* saga() {
  yield takeEvery(syncActions, syncFilter);
  yield takeEvery(
    Object.keys(characterActionTypes).map((key) => characterActionTypes[key]),
    characterTypesFilter
  );
  yield takeEvery(
    Object.keys(builderActionTypes).map((key) => builderActionTypes[key]),
    filter
  );
}

function* characterTypesFilter(action: any) {
  switch (action.type) {
    case characterActionTypes.RACE_CHOOSE_POST_ACTION: {
      // yield put(push(getRouteDefPath(RouteKey.RACE_MANAGE)));
      const characterId = yield select((state: any) =>
        rulesEngineSelectors.getId(state)
      );
      const navigate = yield select(
        (state: any) => state.routeControls.navigate
      );
      navigate(
        getRouteDefPath(RouteKey.RACE_MANAGE).replace(
          ":characterId",
          characterId
        )
      );
      break;
    }

    case characterActionTypes.CLASS_ADD_POST_ACTION: {
      // yield put(push(getRouteDefPath(RouteKey.CLASS_MANAGE)));
      const characterId = yield select((state: any) =>
        rulesEngineSelectors.getId(state)
      );
      const navigate = yield select(
        (state: any) => state.routeControls.navigate
      );
      navigate(
        getRouteDefPath(RouteKey.CLASS_MANAGE).replace(
          ":characterId",
          characterId
        )
      );
      break;
    }

    case characterActionTypes.CLASS_REMOVE_POST_ACTION: {
      let classes = yield select(rulesEngineSelectors.getClasses);
      if (classes.length === 0) {
        // yield put(push(getRouteDefPath(RouteKey.CLASS_CHOOSE)));
        const characterId = yield select((state: any) =>
          rulesEngineSelectors.getId(state)
        );
        const navigate = yield select(
          (state: any) => state.routeControls.navigate
        );
        navigate(
          getRouteDefPath(RouteKey.CLASS_CHOOSE).replace(
            ":characterId",
            characterId
          )
        );
      }
      break;
    }

    case characterActionTypes.CHARACTER_LOAD_POST_ACTION: {
      yield put(builderActions.characterLoadingSetCommit(false));
      yield put(builderActions.characterLoadedSetCommit(true));

      const builderMethod: ReturnType<
        typeof builderSelectors.getBuilderMethod
      > = yield select(builderSelectors.getBuilderMethod);
      const context: ReturnType<typeof characterEnvSelectors.getContext> =
        yield select(characterEnvSelectors.getContext);

      if (
        builderMethod === BuilderMethod.STEP_BY_STEP &&
        context === Constants.AppContextTypeEnum.BUILDER
      ) {
        const configuration: ReturnType<
          typeof rulesEngineSelectors.getCharacterConfiguration
        > = yield select(rulesEngineSelectors.getCharacterConfiguration);
        const stepByStepInitialPage = configuration.showHelpText
          ? RouteKey.HOME_HELP
          : RouteKey.HOME_BASIC_INFO;

        const isCurrentRouteInRoutePathing = yield select(
          NavigationUtils.checkIsCurrentRouteInRoutePathing
        );

        // TODO routes this needs fixed
        // yield put(push(navConfig.getRouteDefPath(stepByStepInitialPage)));
        if (!isCurrentRouteInRoutePathing) {
          const navigate = yield select(
            (state: any) => state.routeControls.navigate
          );
          navigate(
            navConfig
              .getRouteDefPath(stepByStepInitialPage)
              .replace(":characterId", action.payload.characterId)
          );
        }
        // window.location = navConfig.getRouteDefPath(stepByStepInitialPage).replace(':characterId', action.payload.characterId);
        // }
      }
      break;
    }
  }
}

function* filter(action: BuilderAction) {
  switch (action.type) {
    case builderActionTypes.SHOW_HELP_TEXT_SET:
      yield call(ApiRequests.putCharacterHelpText, action.payload);
      yield put(characterActions.showHelpTextSet(action.payload.showHelpText));

      if (action.payload.showHelpText) {
        const currentSectionHelpRoutePath = yield select(
          NavigationUtils.getCurrentSectionHelpRoutePath
        );
        if (currentSectionHelpRoutePath !== null) {
          const characterId = yield select((state: any) =>
            rulesEngineSelectors.getId(state)
          );
          const navigate = yield select(
            (state: any) => state.routeControls.navigate
          );
          navigate(
            currentSectionHelpRoutePath.replace(":characterId", characterId)
          );
          // yield put(push(currentSectionHelpRoutePath));
        }
      } else {
        const availablePathRedirect = yield select(
          NavigationUtils.getAvailablePathRedirect
        );
        if (availablePathRedirect !== null) {
          const characterId = yield select((state: any) =>
            rulesEngineSelectors.getId(state)
          );
          const navigate = yield select(
            (state: any) => state.routeControls.navigate
          );
          navigate(availablePathRedirect.replace(":characterId", characterId));
          // yield put(push(availablePathRedirect));
        }
      }
      break;
  }
}

function* syncFilter(action: BuilderAction) {
  const isSyncTransactionActive = yield select(
    syncTransactionSelectors.getActive
  );
  let transactionInitiatorId;

  if (!isSyncTransactionActive) {
    transactionInitiatorId = uniqueId();
    yield put(syncTransactionActions.activate(transactionInitiatorId));
  }

  switch (action.type) {
    case builderActionTypes.STEP_BUILD_REQUEST:
      yield call(handleStepBuildRequest, action);
      break;

    case builderActionTypes.CHARACTER_LOAD_REQUEST:
      yield call(handleLoadCharacterRequest, action);
      break;

    case builderActionTypes.BUILDER_METHOD_SET:
      yield call(handleBuilderMethodSet, action);
      break;

    case builderActionTypes.SUGGESTED_NAMES_REQUEST:
      yield call(handleSuggestedNamesRequest, action);
      break;
  }

  let syncTransactionInitiator = yield select(
    syncTransactionSelectors.getInitiator
  );
  if (syncTransactionInitiator === transactionInitiatorId) {
    yield put(syncTransactionActions.deactivate());
  }
}

function* handleBuilderMethodSet(action: BuilderMethodSetAction) {
  const response = yield call(ApiRequests.getCharacterRuleData, {
    params: { v: appConfig.version },
  });
  const data: UnpackMakeApiResponseData<
    typeof ApiRequests.getCharacterRuleData
  > | null = ApiAdapterUtils.getResponseData(response);

  if (data !== null) {
    yield put(ruleDataActions.dataSet(data));
  }
  yield put(builderActions.builderMethodSetCommit(action.payload));
}

function* handleStepBuildRequest(action: StepBuildRequestAction) {
  yield put(builderActions.characterLoadingSetCommit(true));

  //TODO fix with common refactor for populating character
  const characterId: UnpackMakeApiResponseData<
    typeof ApiRequests.postCharacterBuilderStandardBuild
  > = yield call(
    SagaHelpers.getApiRequestData,
    ApiRequests.postCharacterBuilderStandardBuild,
    action.payload
  );

  yield put(appEnvActions.dataSet({ characterId }));
  yield put(
    builderActions.builderMethodSetCommit({
      method: BuilderMethod.STEP_BY_STEP,
    })
  );
  yield select(appEnvSelectors.getUsername);
  /* eslint-disable-next-line */
  yield put(
    characterActions.characterLoad(characterId, { v: appConfig.version })
  );
}

function* handleLoadCharacterRequest(action: BuilderAction) {
  yield put(builderActions.characterLoadingSetCommit(true));
  const characterId: ReturnType<typeof appEnvSelectors.getCharacterId> =
    yield select(appEnvSelectors.getCharacterId);

  if (characterId === null) {
    // TODO do something
    return;
  }

  try {
    yield put(
      builderActions.builderMethodSetCommit({
        method: BuilderMethod.STEP_BY_STEP,
      })
    );
    yield put(
      characterActions.characterLoad(characterId, { v: appConfig.version })
    );
  } catch (e) {
    // TODO handle failure in builder init process
  }
}

/**
 *
 * @param action
 */
export function* handleSuggestedNamesRequest(
  action: SuggestedNamesRequestAction
) {
  const species: ReturnType<typeof rulesEngineSelectors.getRace> = yield select(
    rulesEngineSelectors.getRace
  );
  let reqParams: ApiRequests.GetSuggestedNames_RequestData = {
    count: 5,
  };

  if (species !== null) {
    reqParams = {
      ...reqParams,
      raceDefinitionKey: RaceUtils.getDefinitionKey(species),
    };
  }

  const names: UnpackMakeApiResponseData<typeof ApiRequests.getSuggestedNames> =
    yield call(SagaHelpers.getApiRequestData, ApiRequests.getSuggestedNames, {
      params: reqParams,
    });
  yield put(builderActions.suggestedNamesSet(names));
}
