import _ from 'lodash';
import $ from 'jquery';
import moment from 'moment';
import { batchActions } from 'redux-batched-actions';
import * as calendarViews from 'Calendar/calendarViews';
import * as types from './actionTypes';
import { set, update } from './baseActions';
import { fetchCalendar } from './calendarActions';

const viewRanges = {
  [calendarViews.DAY]: 'day',
  [calendarViews.WEEK]: 'week',
  [calendarViews.MONTH]: 'month',
  [calendarViews.FORECAST]: 'day'
};

function getDays(start, end) {
  const startTime = moment(start);
  const endTime = moment(end);
  const difference = endTime.diff(startTime, 'days');

  // Difference is one less than the number of days we need to account for.
  return _.times(difference + 1, (i) => {
    return startTime.clone().add(i, 'days').toISOString();
  });
}

function getDates(time, view, firstDayOfWeek, dayCount) {
  const weekName = firstDayOfWeek === 0 ? 'week' : 'isoWeek';

  let start = time.clone().startOf('day');
  let end = time.clone().endOf('day');

  if (view === calendarViews.WEEK) {
    start = time.clone().startOf(weekName);
    end = time.clone().endOf(weekName);
  }

  if (view === calendarViews.FORECAST) {
    start = time.clone().subtract(1, 'day').startOf('day');
    end = time.clone().add(dayCount - 2, 'days').endOf('day');
  }

  if (view === calendarViews.MONTH) {
    start = time.clone().startOf('month').startOf(weekName);
    end = time.clone().endOf('month').endOf(weekName);
  }

  if (view === calendarViews.AGENDA) {
    start = time.clone().subtract(1, 'day').startOf('day');
    end = time.clone().add(1, 'month').endOf('day');
  }

  return {
    start: start.toISOString(),
    end: end.toISOString(),
    time: time.toISOString(),
    dates: getDays(start, end)
  };
}

function getPopulatableRange(startDate, endDate, view) {
  switch (view) {
    case calendarViews.DAY:
      return {
        start: moment(startDate).subtract(1, 'day').toISOString(),
        end: moment(endDate).add(1, 'day').toISOString()
      };
    case calendarViews.WEEK:
    case calendarViews.FORECAST:
      return {
        start: moment(startDate).subtract(1, 'week').toISOString(),
        end: moment(endDate).add(1, 'week').toISOString()
      };
    default:
      return {
        start: startDate,
        end: endDate
      };
  }
}

function isRangePopulated(start, end, state) {
  const {
    start: currentStart,
    end: currentEnd,
    view: currentView
  } = state;

  if (!currentStart || !currentEnd) {
    return false;
  }

  const {
    start: currentPopulatedStart,
    end: currentPopulatedEnd
  } = getPopulatableRange(currentStart, currentEnd, currentView);

  if (
    moment(start).isAfter(currentPopulatedStart) &&
    moment(start).isBefore(currentPopulatedEnd)
  ) {
    return true;
  }

  return false;
}

const section = 'calendar';

const calendarActionHandlers = {
  [types.FETCH_CALENDAR]: function(payload) {
    return function(dispatch, getState) {
      const state = getState();
      const unmonitored = state.calendar.unmonitored;

      const {
        time,
        view
      } = payload;

      const dayCount = state.calendar.dayCount;
      const dates = getDates(moment(time), view, state.settings.ui.item.firstDayOfWeek, dayCount);
      const { start, end } = getPopulatableRange(dates.start, dates.end, view);
      const isPrePopulated = isRangePopulated(start, end, state.calendar);

      const basesAttrs = {
        section,
        isFetching: true
      };

      const attrs = isPrePopulated ?
        {
          view,
          ...basesAttrs,
          ...dates
        } :
        basesAttrs;

      dispatch(set(attrs));

      const promise = $.ajax({
        url: '/calendar',
        data: {
          unmonitored,
          start,
          end
        }
      });

      promise.done((data) => {
        dispatch(batchActions([
          update({ section, data }),

          set({
            section,
            view,
            ...dates,
            isFetching: false,
            isPopulated: true,
            error: null
          })
        ]));
      });

      promise.fail((xhr) => {
        dispatch(set({
          section,
          isFetching: false,
          isPopulated: false,
          error: xhr
        }));
      });
    };
  },

  [types.SET_CALENDAR_DAYS_COUNT]: function(payload) {
    return function(dispatch, getState) {
      dispatch(set({
        section,
        dayCount: payload.dayCount
      }));

      const state = getState();
      const { time, view } = state.calendar;

      dispatch(fetchCalendar({
        time,
        view
      }));
    };
  },

  [types.SET_CALENDAR_INCLUDE_UNMONITORED]: function(payload) {
    return function(dispatch, getState) {
      dispatch(set({
        section,
        unmonitored: payload.unmonitored
      }));

      const state = getState();
      const { time, view } = state.calendar;

      dispatch(fetchCalendar({
        time,
        view
      }));
    };
  },

  [types.SET_CALENDAR_VIEW]: function(payload) {
    return function(dispatch, getState) {
      const state = getState();
      const view = payload.view;
      const time = view === calendarViews.FORECAST ? moment() : state.calendar.time;

      dispatch(fetchCalendar({ time, view }));
    };
  },

  [types.GOTO_CALENDAR_TODAY]: function(payload) {
    return function(dispatch, getState) {
      const state = getState();
      const view = state.calendar.view;
      const time = moment();

      dispatch(fetchCalendar({ time, view }));
    };
  },

  [types.GOTO_CALENDAR_PREVIOUS_RANGE]: function(payload) {
    return function(dispatch, getState) {
      const state = getState();

      const {
        view,
        dayCount
      } = state.calendar;

      const amount = view === calendarViews.FORECAST ? dayCount : 1;
      const time = moment(state.calendar.time).subtract(amount, viewRanges[view]);

      dispatch(fetchCalendar({ time, view }));
    };
  },

  [types.GOTO_CALENDAR_NEXT_RANGE]: function(payload) {
    return function(dispatch, getState) {
      const state = getState();

      const {
        view,
        dayCount
      } = state.calendar;

      const amount = view === calendarViews.FORECAST ? dayCount : 1;
      const time = moment(state.calendar.time).add(amount, viewRanges[view]);

      dispatch(fetchCalendar({ time, view }));
    };
  }
};

export default calendarActionHandlers;
