import { ToastAndroid } from 'react-native';
import { Allow, createLogic, Done, Reject } from 'redux-logic';
import { AuthActions, OfferActions } from '../actions';
import {
  Application,
  ApplicationData,
  ApplicationStatus,
  Dispatch,
  Offer,
  RootState,
  UserRole,
} from '../models';
import { Firestore, LogicDeps } from './';

const validateNewApplicationLogic = createLogic({
  type: OfferActions.ActionKey.NEW_APPLICATION_REQUEST,
  validate({ action, getState }: LogicDeps & { getState: () => RootState, action: OfferActions.NewApplicationRequest },
    allow: Allow, reject: Reject) {
    const user = getState().user;
    if (user.role === UserRole.EMPLOYER) {
      reject({ type: 'invalid user role' });
    }
    if (action.offer.dateEnd <= Date.now()) {
      reject(OfferActions.newApplicationFail(action.offer.id, 'Oferta straciła ważność'));
      ToastAndroid.show('Oferta straciła ważność', ToastAndroid.SHORT);
    } else {
      allow(action);
    }
  },
});

const newApplicationLogic = createLogic({
  type: OfferActions.ActionKey.NEW_APPLICATION_REQUEST,
  process({ getState, action }: LogicDeps & { getState: () => RootState, action: OfferActions.NewApplicationRequest },
    dispatch: Dispatch, done: Done) {
    const user = getState().user;
    const employeeId = user.body.id;
    const employeeDisplayName = user.body.name;
    const employerId = action.offer.ownerId;
    const offerId = action.offer.id;
    const offerName = action.offer.title;
    const status = ApplicationStatus.PENDING;
    const id = `${employeeId}@${offerId}`;
    const doc = Firestore.collection('applications').doc(id);
    doc.set({ employeeId, offerId, status, employerId, id, employeeDisplayName, offerName })
      .then((ref) => dispatch(OfferActions.newApplicationSuccess(offerId)))
      .catch((error) => {
        console.error(error);
        return dispatch(OfferActions.newApplicationFail(offerId, 'Failed to add appliction'));
      })
      .then(() => done());
  },
});

const acceptApplicationLogic = createLogic({
  type: OfferActions.ActionKey.APPLICATION_ACCEPTED_REQUESTED,
  async process({ action }: LogicDeps & { action: OfferActions.ApplicationAcceptedRequested },
    dispatch: Dispatch, done: Done) {
    const offerId = action.application.offerId;
    try {
      const offerDoc = await Firestore.collection('offers').doc(offerId).get();
      const { freeSlots } = offerDoc.data() as Offer;
      if (freeSlots > 0) {
        await offerDoc.ref.set({ freeSlots: freeSlots - 1 }, { merge: true });
      }
      await Firestore.collection('applications')
        .doc(action.application.id)
        .update({
          status: ApplicationStatus.ACCEPTED,
        });
      dispatch(OfferActions.applicationAcceptedSuccess({
        ...action.application,
        status: ApplicationStatus.ACCEPTED,
      }));
    } catch (error) {
      console.error(error);
      dispatch(OfferActions.ApplicationAcceptedFail(error));
    }
    done();
  },
});

const rejectApplicationLogic = createLogic({
  type: OfferActions.ActionKey.APPLICATION_REJECTED_REQUESTED,
  process({ action }: LogicDeps & { action: OfferActions.ApplicationRejectedRequested },
    dispatch: Dispatch, done: Done) {
    Firestore
      .collection('applications')
      .doc(action.application.id)
      .update({
        status: ApplicationStatus.REJECTED,
      })
      .then(() => dispatch(OfferActions.applicationRejectedSuccess({
        ...action.application,
        status: ApplicationStatus.REJECTED,
      })))
      .catch((error) => {
        console.error(error);
        return dispatch(OfferActions.ApplicationRejectedFail('Failed to reject application'));
      })
      .then(() => done());
  },
});

const isOfferCached: (cache: Array<Application>, offerId: string, status: string) => boolean =
  (cache, offerId, status) => {
    const cachedItem = cache.find((app: Application) => app.offerId === offerId);
    return cachedItem && cachedItem.status === status;
  };

const listenForEmployeeApplications = createLogic({
  type: AuthActions.ActionKey.INITIALIZE_USER_FLOW,
  warnTimeout: 0,
  processOptions: {
    dispatchMultiple: true,
  },
  process({ getState, action }: LogicDeps & { getState: () => RootState, action: AuthActions.InitializeUserFlow },
    dispatch: Dispatch) {
    if (action.userRole === UserRole.EMPLOYER) {
      return;
    } else if (action.userRole === UserRole.EMPLOYEE) {
      const cachedOffers = getState().employeeData.applications;
      Firestore
        .collection('applications')
        .where('employeeId', '==', action.uid)
        .onSnapshot((ref) => {
          const userApplications = ref.docs.map((doc) => doc.data());
          userApplications.forEach((app: any) => {
            if (!isOfferCached(cachedOffers, app.offerId, app.status)) {
              Firestore
                .collection('offers')
                .doc(app.offerId)
                .get()
                .then((doc) => {
                  const fullApplication: Application = {
                    ...app,
                    offer: doc.data(),
                  };
                  return dispatch(OfferActions.employeeApplicationFetched(fullApplication));
                })
                .catch((error) => console.error(error));
            }
          });
        });
    }
  },
});
const listenForEmployerApplications = createLogic({
  type: AuthActions.ActionKey.INITIALIZE_USER_FLOW,
  warnTimeout: 0,
  processOptions: {
    dispatchMultiple: true,
  },
  process({ getState, action }: LogicDeps & { getState: () => RootState, action: AuthActions.InitializeUserFlow },
    dispatch: Dispatch) {
    if (action.userRole === UserRole.EMPLOYEE) {
      return;
    } else if (action.userRole === UserRole.EMPLOYER) {
      Firestore
        .collection('applications')
        .where('employerId', '==', action.uid)
        .onSnapshot((ref) => {
          const userApplications = ref.docs.map((doc) => doc.data() as ApplicationData);
          return dispatch(OfferActions.employerApplicationsReceived(userApplications));
        });
    }
  },
});

export const applicationLogic = [
  validateNewApplicationLogic,
  newApplicationLogic,
  listenForEmployeeApplications,
  listenForEmployerApplications,
  acceptApplicationLogic,
  rejectApplicationLogic,
];
