import { eventChannel } from 'redux-saga';
import { put, takeEvery, all, call, select } from 'redux-saga/effects';
// from the parent
import { api } from 'store/apis';
// Action type
import { StoreAction } from 'store/utils/actions';
import { apiCall, apolloCall, genericSaga } from 'store/utils/sagas';
import { firebaseCall, fire } from 'store/utils/firebase';

import { zeus, config } from 'config/general';
import app from 'store/app';

import gql from 'graphql-tag';

import { genericQuery } from 'store/utils/apollo';
import { StoreState } from 'store/rootReducer';
import actions from './actions';
import selectors from './selectors';
import { firebaseModel, changeEmailModel } from './models';

function* handleError(e: string | {}): void {
  yield put(app.actions.dialog.error.action({ data: e.message || e }));
}

export function getAuthChannel(): any {
  const authChannel = eventChannel((emit): void => {
    const unsubscribe = fire.auth().onAuthStateChanged((user): void => emit({ user }));
    return unsubscribe;
  });
  return authChannel;
}

/**
 * Internal auth handling
 */

function* login(action: StoreAction): void {
  const payload = action.payload || {};

  if (config.loginApollo) {
    const { Zeus, $ } = zeus;

    const apiInput = {
      creds: $`creds`,
    };

    const queryObj = {
      [config.loginQueryName]: [apiInput, firebaseModel],
    };

    const zeusQuery = Zeus.query(queryObj);

    const query = gql`
      ${zeusQuery}
    `;

    yield call(apolloCall, {
      ...payload,
      query,
      noToken: true,
      objectReturn: config.loginQueryName,
      variables: {
        creds: payload.data,
      },
      actionRes: actions.login.result.action,
    });
    return;
  }

  yield call(apiCall, {
    ...payload,
    api: api.session.login,
    data: payload.data || payload,
    addLanguage: true,
    actionRes: actions.login.result.action,
  });
}

function* logout(action: StoreAction): void {
  const payload = action.payload || {};

  if (config.loginApollo) {
    const session = yield select(selectors.getSession);

    if (config.noLogoutApi) {
      yield put(app.actions.setHeaders.action(null));
      yield put(actions.logout.result.action());
      if (payload.callback) payload.callback();
      return;
    }
    yield call(
      genericQuery({
        name: 'revokeToken',
        search: {
          refreshToken: session.refreshToken,
        },
        model: {
          message: true,
        },
        actionRes: actions.logout,
      }),
      action,
    );

    yield put(app.actions.setHeaders.action(null));

    return;
  }

  yield call(apiCall, {
    ...payload,
    api: api.session.logout,
    data: payload.data || payload,
    noFeedback: true,
    actionRes: actions.logout.result.action,
  });

  yield put(app.actions.setHeaders.action(null));
}

function* changePassword(action: StoreAction): void {
  const payload = action.payload || {};

  if (config.loginApollo) {
    const { Zeus, $ } = zeus;

    const apiInput = {
      input: $`input`,
    };

    const outputModel = {
      _id: true,
    };

    const mutationObj = {
      [config.updatePasswordMutationName]: [apiInput, outputModel],
    };

    const zeusMutation = Zeus.mutation(mutationObj);

    const mutate = gql`
      ${zeusMutation}
    `;

    yield call(apolloCall, {
      ...payload,
      mutate,
      // noToken: true,
      objectReturn: config.updatePasswordMutationName,
      variables: {
        input: payload.data,
      },
      actionRes: actions.changePassword.result.action,
      addLanguage: true,
    });

    return;
  }

  yield call(apiCall, {
    ...payload,
    api: api.session.changePassword,
    data: payload.data || payload,
    actionRes: actions.changePassword.result.action,
  });
}

function* changeEmail(action: StoreAction): void {
  const payload = action.payload || {};

  const { Zeus, $ } = zeus;

  const apiInput = {
    input: $`input`,
  };

  const mutationObj = {
    [config.updateEmailMutationName]: [apiInput, changeEmailModel],
  };

  const zeusMutation = Zeus.mutation(mutationObj);

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    ...payload,
    mutate,
    // noToken: true,
    objectReturn: config.updateEmailMutationName,
    variables: {
      input: payload.data,
    },
    actionRes: actions.changeEmail?.result?.action,
    addLanguage: true,
  });
}

function* resetPassword(action: StoreAction): void {
  const payload = action.payload || {};

  if (config.loginApollo) {
    const { Zeus, $ } = zeus;

    const apiInput = {
      input: $`input`,
    };

    const outputModel = {
      message: true,
    };

    const mutationObj = {
      [config.resetPasswordMutationName]: [apiInput, outputModel],
    };

    const zeusMutation = Zeus.mutation(mutationObj);

    const mutate = gql`
      ${zeusMutation}
    `;

    yield call(apolloCall, {
      ...payload,
      mutate,
      // noToken: true,
      objectReturn: config.resetPasswordMutationName,
      variables: {
        input: payload.data,
      },
      actionRes: actions.resetPassword.result.action,
      addLanguage: true,
    });

    return;
  }

  yield call(apiCall, {
    ...payload,
    api: api.session.resetPassword,
    data: payload.data || payload,
    addLanguage: true,
    actionRes: actions.resetPassword.result.action,
  });
}

export function* refreshToken(action: StoreAction): void {
  const payload = (action && action.payload) || {};

  if (config.loginApollo) {
    const session = yield select(selectors.getSession);
    const account = yield select((state: StoreState) => state.account?.profile?.data);

    if (!session.refreshToken) {
      return;
    }

    const apiInput = {
      refreshToken: session.refreshToken,
    };
    if (config.refreshTokenApi) {
      apiInput.email = session.email || account?.email;
    }

    yield call(
      genericQuery({
        noToken: true,
        name: config.refreshTokenApi ? 'refreshToken' : config.loginQueryName,
        search: apiInput,
        actionRes: actions.refresh,
        model: firebaseModel,
      }),
      action,
    );

    return;
  }
  yield call(apiCall, {
    ...payload,
    api: api.session.refresh,
    data: payload.data || payload,
    noFeedback: true,
    actionRes: actions.refresh.result.action,
  });
}

/**
 * Firebase auth handling
 */
function* firebaseLogin(action: StoreAction): void {
  const payload = action.payload || {};

  if (!payload) {
    yield call(handleError, 'Missing payload to api call');
    return;
  }

  if (!payload.noLoading) yield put(app.actions.loader.show.action());

  const { data, success } = payload;

  if (!data.email || !data.password) {
    yield call(handleError, 'Missing values password or email');
    return;
  }

  const firstRequest = yield firebaseCall({
    action: fire.auth().signInWithEmailAndPassword(data.email, data.password),
    success,
    noLoading: true,
    noFeedback: payload.noFeedback,
  });

  if (firstRequest && firstRequest.success) {
    yield firebaseCall({
      ...payload,
      action: fire.auth().currentUser.getIdTokenResult(),
      actionRes: actions.firebase.login.result.action,
      noLoading: true,
    });
  } else {
    yield put(actions.firebase.login.result.action(null, 'rejected'));
  }
  if (!payload.noLoading) yield put(app.actions.loader.hide.action());
}

function* firebaseSignUp(action: StoreAction): void {
  const payload = action.payload || {};

  const { data, success } = payload;

  if (!payload.noLoading) yield put(app.actions.loader.show.action());

  const request = yield firebaseCall({
    action: fire.auth().createUserWithEmailAndPassword(data.email, data.password),
    noLoading: true,
    success,
  });

  if (request && (request.success || !request.error)) {
    const secondRequest = yield firebaseCall({
      ...payload,
      action: fire.auth().currentUser.getIdTokenResult(),
      noLoading: true,
      actionRes: actions.firebase.login.result.action,
    });

    // Send confirmation email or not
    if (config.emailVerification && secondRequest && !secondRequest.error) {
      yield firebaseCall({
        action: fire.auth().currentUser.sendEmailVerification(),
        noLoading: true,
      });
    }
  }
  if (!payload.noLoading) yield put(app.actions.loader.hide.action());
}

function* firebaseSendEmailVerification(action: StoreAction): void {
  const payload = action.payload || {};

  if (fire.auth().currentUser) {
    yield firebaseCall({
      ...payload,
      action: fire.auth().currentUser.sendEmailVerification(),
      actionRes: actions.firebase.sendEmailVerification.result.action,
    });
  }
}

export function* firebaseLogout(action: StoreAction): void {
  const payload = action.payload || {};

  yield firebaseCall({
    ...payload,
    action: fire.auth().signOut(),
    actionRes: actions.logout.result.action,
  });

  yield put(app.actions.setHeaders.action(null));
}

export function* refreshIdToken(): void {
  // here you must be sure that fire has been initialized
  const user = fire.auth().currentUser;

  if (!user) {
    return;
  }

  yield firebaseCall({
    action: user.getIdTokenResult(true),
    noLoading: true,
    // Store the new token in the state
    actionRes: actions.firebase.refreshIdToken.result.action,
  });
}

function* setPasswordFromEmail(action: StoreAction): void {
  const payload = action.payload || {};

  if (!payload) {
    yield call(handleError, 'Missing payload to api call');
    return;
  }

  if (!payload.code) {
    yield call(handleError, 'Missing validation code in URL');
    return;
  }

  const verifyRequest = yield firebaseCall({
    noLoading: payload.noLoading,
    action: fire.auth().verifyPasswordResetCode(payload.code),
    callbackOnError: payload.callback,
  });

  if (verifyRequest.success) {
    yield firebaseCall({
      ...payload,
      action: fire.auth().confirmPasswordReset(payload.code, payload.data.password),
      actionRes: actions.firebase.setPasswordFromEmail.result.action,
    });
  }
}

function* sendEmailResetPassword(action: StoreAction): void {
  const payload = action.payload || {};

  if (!payload) {
    yield call(handleError, 'Missing payload to api call');
    return;
  }

  yield firebaseCall({
    ...payload,
    action: fire.auth().sendPasswordResetEmail(payload.data.email),
    actionRes: actions.firebase.sendEmailResetPassword.result.action,
  });
}

function* reauthenticateWithCredential(action: StoreAction): any {
  const payload = action.payload || {};

  if (!payload || !payload.password) {
    yield call(handleError, 'Missing payload to api call');
    return false;
  }

  const user = fire.auth().currentUser;

  const firebase = window.firebase;

  if (!firebase) return false;
  const cred = firebase.auth.EmailAuthProvider.credential(user.email, payload.password);

  if (cred) {
    const req = yield firebaseCall({
      action: user.reauthenticateWithCredential(cred),
      callback: payload.callback,
    });

    return req;
  }
  return false;
}

function* firebaseChangePassword(action: StoreAction): void {
  const user = fire.auth().currentUser;
  const data = action.payload.data || action.payload;

  if (!data || !data.password || !data.currentPassword) {
    yield call(handleError, 'Missing payload to api call');
    return;
  }

  const payload = {
    password: data.currentPassword,
    callback: action.payload.callback,
  };

  const reauth = yield call(reauthenticateWithCredential, { payload });

  if (reauth && !reauth.error) {
    yield firebaseCall({
      ...action.payload,
      action: user.updatePassword(data.password),
      success: data.success || 'Your password has been correctly updated',
    });
  }
}

function* firebaseChangeEmail(action: StoreAction): void {
  const user = fire.auth().currentUser;
  const data = action.payload.data || action.payload;

  if (!data || !data.email || !data.currentEmail) {
    yield call(handleError, 'Missing payload to api call');
    return;
  }

  const payload = {
    password: data.password,
    callback: action.payload.callback,
  };

  const reauth = yield call(reauthenticateWithCredential, { payload });

  if (reauth && !reauth.error) {
    yield firebaseCall({
      ...action.payload,
      action: user.updateEmail(data.email),
      success: data.success || 'Your email has been correctly updated',
    });
  }
}

function* authWatchers(): void {
  yield all([
    // normal
    yield takeEvery(actions.login.request.constant, login),
    yield takeEvery(actions.logout.request.constant, logout),
    yield takeEvery(actions.changePassword.request.constant, changePassword),
    yield takeEvery(actions.resetPassword.request.constant, resetPassword),
    yield takeEvery(actions.refresh.request.constant, refreshToken),
    yield takeEvery(actions.changeEmail.request.constant, changeEmail),
    // Social
    yield takeEvery(actions.loginSocial.request.constant, genericSaga('session.loginSocial', actions.loginSocial.result.action)),
    yield takeEvery(actions.registerSocial.request.constant, genericSaga('session.registerSocial', actions.registerSocial.result.action)),

    // firebase
    yield takeEvery(actions.firebase.login.request.constant, firebaseLogin),
    yield takeEvery(actions.firebase.logout.request.constant, firebaseLogout),
    yield takeEvery(actions.firebase.signup.request.constant, firebaseSignUp),
    yield takeEvery(actions.firebase.refreshIdToken.request.constant, refreshIdToken),
    yield takeEvery(actions.firebase.setPasswordFromEmail.request.constant, setPasswordFromEmail),
    yield takeEvery(actions.firebase.sendEmailResetPassword.request.constant, sendEmailResetPassword),
    yield takeEvery(actions.firebase.reauthenticate.request.constant, reauthenticateWithCredential),
    yield takeEvery(actions.firebase.changePassword.request.constant, firebaseChangePassword),
    yield takeEvery(actions.firebase.changeEmail.request.constant, firebaseChangeEmail),
    yield takeEvery(actions.firebase.sendEmailVerification.request.constant, firebaseSendEmailVerification),
  ]);
}

export default function* saga(): void {
  yield call(authWatchers);
}
