import { getTimeframeWindowFrom, newDate, totalDaysInMonth } from '~/lib/utils/datetime_utility';

import { DAYS_IN_WEEK, DATE_RANGES, PRESET_TYPES } from '../constants';

const monthsForQuarters = {
  1: [0, 1, 2],
  2: [3, 4, 5],
  3: [6, 7, 8],
  4: [9, 10, 11],
};

export const getWeeksForDates = (startDate, endDate) => {
  const timeframe = [];
  const start = newDate(startDate);
  const end = newDate(endDate);

  // Move to Sunday that comes just before startDate
  start.setDate(start.getDate() - start.getDay());

  while (start.getTime() < end.getTime()) {
    // Push date to timeframe only when day is
    // first day (Sunday) of the week
    timeframe.push(newDate(start));

    // Move date next Sunday
    start.setDate(start.getDate() + DAYS_IN_WEEK);
  }

  return timeframe;
};

export const getTimeframeForRangeType = ({
  timeframeRangeType = DATE_RANGES.CURRENT_QUARTER,
  presetType = PRESET_TYPES.WEEKS,
  initialDate = new Date(),
}) => {
  let timeframe = [];
  const startDate = newDate(initialDate);
  startDate.setHours(0, 0, 0, 0);

  // We need to prepare timeframe containing all the weeks of
  // current quarter.
  if (timeframeRangeType === DATE_RANGES.CURRENT_QUARTER) {
    // Get current quarter for current month
    const currentQuarter = Math.floor((startDate.getMonth() + 3) / 3);
    // Get index of current month in current quarter
    // It could be 0, 1, 2 (i.e. first, second or third)
    const currentMonthInCurrentQuarter = monthsForQuarters[currentQuarter].indexOf(
      startDate.getMonth(),
    );

    // Get last day of the last month of current quarter
    const endDate = newDate(startDate);
    if (currentMonthInCurrentQuarter === 0) {
      endDate.setMonth(endDate.getMonth() + 2);
    } else if (currentMonthInCurrentQuarter === 1) {
      endDate.setMonth(endDate.getMonth() + 1);
    }
    endDate.setDate(totalDaysInMonth(endDate));

    // Move startDate to first day of the first month of current quarter
    startDate.setMonth(startDate.getMonth() - currentMonthInCurrentQuarter);
    startDate.setDate(1);

    timeframe = getWeeksForDates(startDate, endDate);
  } else if (timeframeRangeType === DATE_RANGES.CURRENT_YEAR) {
    // Move start date to first day of current year
    startDate.setMonth(0);
    startDate.setDate(1);

    if (presetType === PRESET_TYPES.MONTHS) {
      timeframe = getTimeframeWindowFrom(startDate, 12);
    } else {
      // Get last day of current year
      const endDate = newDate(startDate);
      endDate.setMonth(11);
      endDate.setDate(totalDaysInMonth(endDate));

      timeframe = getWeeksForDates(startDate, endDate);
    }
  } else {
    // Get last day of the month, 18 months from startDate.
    const endDate = newDate(startDate);
    endDate.setMonth(endDate.getMonth() + 18);
    endDate.setDate(totalDaysInMonth(endDate));

    // Move start date to the 18 months behind
    startDate.setMonth(startDate.getMonth() - 18);
    startDate.setDate(1);

    if (presetType === PRESET_TYPES.QUARTERS) {
      timeframe = getTimeframeWindowFrom(startDate, 18 * 2);
      const quartersTimeframe = [];

      // Iterate over the timeframe and break it down
      // in chunks of quarters
      for (let i = 0; i < timeframe.length; i += 3) {
        const range = timeframe.slice(i, i + 3);
        const lastMonthOfQuarter = range[range.length - 1];
        const quarterSequence = Math.floor((range[0].getMonth() + 3) / 3);
        const year = range[0].getFullYear();

        // Ensure that `range` spans across duration of
        // entire quarter
        lastMonthOfQuarter.setDate(totalDaysInMonth(lastMonthOfQuarter));

        quartersTimeframe.push({
          quarterSequence,
          range,
          year,
        });
      }
      timeframe = quartersTimeframe;
    } else if (presetType === PRESET_TYPES.MONTHS) {
      timeframe = getTimeframeWindowFrom(startDate, 18 * 2);
    } else {
      timeframe = getWeeksForDates(startDate, endDate);
    }
  }

  return timeframe;
};

/**
 * Returns timeframe range in string based on provided config.
 *
 * @param {object} config
 * @param {string} config.presetType String representing preset type
 * @param {array} config.timeframe Array of dates representing timeframe
 *
 * @returns {object} Returns an object containing `startDate` & `dueDate` strings
 *                   Computed using presetType and timeframe.
 */
export const getEpicsTimeframeRange = ({ presetType = '', timeframe = [] }) => {
  let start;
  let due;

  const firstTimeframe = timeframe[0];
  const lastTimeframe = timeframe[timeframe.length - 1];
  // Compute start and end dates from timeframe
  // based on provided presetType.
  if (presetType === PRESET_TYPES.QUARTERS) {
    [start] = firstTimeframe.range;
    due = lastTimeframe.range[lastTimeframe.range.length - 1];
  } else if (presetType === PRESET_TYPES.MONTHS) {
    start = firstTimeframe;
    due = lastTimeframe;
  } else if (presetType === PRESET_TYPES.WEEKS) {
    start = firstTimeframe;
    due = newDate(lastTimeframe);
    due.setDate(due.getDate() + 6);
  }

  return {
    timeframe: {
      start: start.toISOString().split('T')[0],
      end: due.toISOString().split('T')[0],
    },
  };
};

export const sortEpics = (epics, sortedBy) => {
  const sortByStartDate = sortedBy.indexOf('start_date') > -1;
  const sortOrderAsc = sortedBy.indexOf('asc') > -1;

  epics.sort((a, b) => {
    let aDate;
    let bDate;

    if (sortByStartDate) {
      // Always use the original start date.
      // if originalStartDate exists, it means startDate was changed to a proxy date
      // (refer to roadmap_item_utils.js)
      const startDateForA = a.originalStartDate ? a.originalStartDate : a.startDate;
      const startDateForB = b.originalStartDate ? b.originalStartDate : b.startDate;

      // When epic has no fixed start date, use Number.NEGATIVE_INFINITY for comparison.
      // In other words, epics without fixed start date should, in theory, have the earliest start date.
      // (the actual min possible value for Date object is much smaller; ECMA-262 20.4.1.1)
      aDate = a.startDateUndefined ? Number.NEGATIVE_INFINITY : startDateForA.getTime();
      bDate = b.startDateUndefined ? Number.NEGATIVE_INFINITY : startDateForB.getTime();
    } else {
      const endDateForA = a.originalEndDate ? a.originalEndDate : a.endDate;
      const endDateForB = b.originalEndDate ? b.originalEndDate : b.endDate;

      // Similarly, use Infinity when epic has no fixed due date.
      aDate = a.endDateUndefined ? Infinity : endDateForA.getTime();
      bDate = b.endDateUndefined ? Infinity : endDateForB.getTime();
    }

    // Sort in ascending or descending order
    if (aDate < bDate) {
      return sortOrderAsc ? -1 : 1;
    } else if (aDate > bDate) {
      return sortOrderAsc ? 1 : -1;
    }
    return 0;
  });
};

export const getPresetTypeForTimeframeRangeType = (timeframeRangeType, initialPresetType) => {
  let presetType;
  switch (timeframeRangeType) {
    case DATE_RANGES.CURRENT_QUARTER:
      presetType = PRESET_TYPES.WEEKS;
      break;
    case DATE_RANGES.CURRENT_YEAR:
      presetType = [PRESET_TYPES.MONTHS, PRESET_TYPES.WEEKS].includes(initialPresetType)
        ? initialPresetType
        : PRESET_TYPES.MONTHS;
      break;
    case DATE_RANGES.THREE_YEARS:
      presetType = [PRESET_TYPES.QUARTERS, PRESET_TYPES.MONTHS, PRESET_TYPES.WEEKS].includes(
        initialPresetType,
      )
        ? initialPresetType
        : PRESET_TYPES.QUARTERS;
      break;
    default:
      break;
  }
  return presetType;
};
