import React, { useState, useEffect } from 'react';
import { Field } from 'react-final-form';

// Redux part
import { useSelector, useDispatch } from 'react-redux';
import { StoreState } from 'store/rootReducer';

// Import actions here

import classNames from 'classnames/bind';
import ProjectInput from 'components/formItems/ProjectInput';
import ProjectButton from 'components/items/ProjectButton';
import { required, email, composeValidators } from 'store/utils/validation';
import DateRangePicker from 'components/formItems/DateRangePicker';
import Moment from 'moment';
import { extendMoment } from 'moment-range';

import TextItem from 'components/items/TextItem';
import styleIdentifiers from './bookingForm.scss';

const moment = extendMoment(Moment);

const styles = classNames.bind(styleIdentifiers);

export interface StateProps {}

export interface DispatchProps {}

export interface OwnProps {
  onSubmit: Function;
}

export type BookingFormProps = StateProps & DispatchProps & OwnProps;

const DAY_FORMAT = 'DD/MM/YYYY';
const HOUR_FORMAT = 'HH:mm';

const BookingForm = (props: BookingFormProps) => {
  const { handleSubmit, workspace, values, submitting, valid, initialValues, form } = props;

  // mapStateToProps
  const content = useSelector((state: StoreState) => state.content.raw);
  const profile = useSelector((state: StoreState) => state.account.profile?.data);

  const [forceClose, setForceClose] = useState(false);

  const availability = workspace && workspace.availability;

  useEffect(() => {
    if (values && values.dateFrom && values.dateTo && !values.dates) {
      form.mutators.setValue('dates', {
        startDate: values.dateFrom,
        endDate: values.dateTo,
      });
    }
  }, [values]);

  const overriddenDates = [];

  // override with exceptions
  if (availability.exceptions) {
    availability.exceptions.forEach(exception => {
      let days = moment.range(exception.dates.startDate, exception.dates.endDate);

      if (days) {
        days = Array.from(days.by('day'));
        days.forEach(day => {
          overriddenDates.push({
            date: day.toDate(),
            hours: exception.hours,
            allDay: exception.allDay,
          });
        });
      }
    });
  }

  // get remaining hours by excluding excludedHours in hours
  const getRemainingHours = (hours, excludedHours) => {
    const newHours = [];

    if (!hours?.length || !excludedHours?.length) return newHours;
    for (let i = 0; i < hours.length; i++) {
      const slot = hours[i];
      const slotFrom = moment(slot.from, HOUR_FORMAT);
      const slotTo = moment(slot.to, HOUR_FORMAT);

      for (let j = 0; j < excludedHours.length; j++) {
        const excludedSlot = excludedHours[j];
        const excludedSlotFrom = moment(excludedSlot.from, HOUR_FORMAT);
        const excludedSlotTo = moment(excludedSlot.to, HOUR_FORMAT);

        /**
         *  ----|----------|----  slot
         *  ------|----–|-------  excluded slot
         */
        if (
          excludedSlotFrom.isAfter(slotFrom) &&
          excludedSlotFrom.diff(slotFrom, 'hours') >= workspace.minStay
        ) {
          // hours ok => from : slotFrom, to: exlcudedSlot From
          newHours.push({
            from: slotFrom.format(HOUR_FORMAT),
            to: excludedSlotFrom.format(HOUR_FORMAT),
          });
        }
        if (
          excludedSlotTo.isBefore(slotTo) &&
          slotTo.diff(excludedSlotTo, 'hours') >= workspace.minStay
        ) {
          // hours ok : from: excludedSlotTo, to: slotTo
          newHours.push({
            from: excludedSlotTo.format(HOUR_FORMAT),
            to: slotTo.format(HOUR_FORMAT),
          });
        }
        // other cases
        /**
         * slot is included in excludedSlot => nothing to add
         * ------|----–|-------  slot
         * ----|----------|----  excluded slot
         */
        /**
         * excluded start before slot => should not happen but is covered by above
         * ------|----–|-------  slot
         * ----|----|----------  excluded slot
         */
        /**
         * excluded end after slot => should not happen but is covered by above
         * ---|-------–|-------  slot
         * ------|-------|-----  excluded slot
         */
      }
    }

    return newHours;
  };

  if (workspace?.getBookings) {
    workspace.getBookings.forEach(booking => {
      const dates = booking.dates;
      const slot = booking.slot;

      if (profile?._id === booking.ownerId && booking.status === 'draft') return;

      const excludedHours = [{ from: slot.startTime, to: slot.endTime }];

      dates.forEach(date => {
        const day = moment(date.startDate);

        const existingOverride = find(overriddenDates, d => {
          return moment(d.date).format(DAY_FORMAT) === day.format(DAY_FORMAT);
        });

        if (existingOverride) {
          const newHours = getRemainingHours(existingOverride.hours, excludedHours);
          existingOverride.hours = newHours;
          existingOverride.allDay = newHours.length === 0;
        } else {
          const newHours = getRemainingHours(availability.hours, excludedHours);
          overriddenDates.push({
            date: day.toDate(),
            hours: newHours,
            allDay: newHours.length === 0,
          });
        }
      });
    });
  }

  // console.log('DEBUG: overriddenDates', overriddenDates);

  const availabilityDates = availability && availability.dates;

  const minDate = availabilityDates && moment.max(moment(), moment(availabilityDates.startDate));

  const maxDate = availabilityDates && moment(availabilityDates.endDate);

  const handleDisabled = day => {
    const momentDay = moment(day);

    // Weekends
    if (availability.noWeekend) {
      const isoDay = momentDay.isoWeekday();

      if (isoDay === 6 || isoDay === 7) return true;
    }
    if (overriddenDates) {
      for (let i = 0; i < overriddenDates.length; i++) {
        const d = overriddenDates[i];
        if (momentDay.format(DAY_FORMAT) === moment(d.date).format(DAY_FORMAT)) {
          // check hours
          if (d.allDay) return true;

          // TODO: should check there
          return false;
        }
      }
    }
    return false;
  };

  const handleDateChange = dates => {
    form.mutators.setValue('dateFrom', dates ? dates.startDate : null);

    form.mutators.setValue('dateTo', dates ? dates.endDate : null);
  };

  const handleDone = () => {
    setForceClose(true);
    setTimeout(() => setForceClose(false), 500);
  };

  const getHourIntersection = (slot1, slot2) => {
    const h1From = moment(slot1.from, HOUR_FORMAT);
    const h1To = moment(slot1.to, HOUR_FORMAT);

    const h2From = moment(slot2.from, HOUR_FORMAT);
    const h2To = moment(slot2.to, HOUR_FORMAT);

    const range1 = moment.range(h1From, h1To);
    const range2 = moment.range(h2From, h2To);
    const intersection = range1.intersect(range2); // moment.range(b, c)
    const dates = intersection?.toDate();
    if (!dates) return null;
    const slot = {
      from: moment(dates[0]).format(HOUR_FORMAT),
      to: moment(dates[1]).format(HOUR_FORMAT),
    };

    return slot;
  };

  const getHoursIntersections = (hours1, hours2) => {
    const newHours = [];

    for (let index = 0; index < hours1.length; index++) {
      const slot1 = hours1[index];
      for (let index2 = 0; index2 < hours2.length; index2++) {
        const slot2 = hours2[index2];

        const intersect = getHourIntersection(slot1, slot2);
        if (intersect) newHours.push(intersect);
      }
    }
    return newHours;
  };

  /**
   * Hours utils
   */
  const getRangeHours = () => {
    const range = moment.range(values.dateFrom, values.dateTo);

    let hours = availability.hours;

    for (let i = 0; i < overriddenDates.length; i += 1) {
      const item = overriddenDates[i];

      if (range.contains(item.date) && item.hours) {
        hours = getHoursIntersections(hours, item.hours);
      }
    }
    return hours;
  };

  const getMinMaxHours = (hours, fromDate) => {
    let minHour;
    let maxHour;

    for (let i = 0; i < hours.length; i += 1) {
      const item = hours[i];

      if (!minHour || moment(item.from, HOUR_FORMAT).isBefore(minHour)) {
        minHour = moment(item.from, HOUR_FORMAT);
      }
      if (!maxHour || moment(item.to, HOUR_FORMAT).isAfter(maxHour)) {
        maxHour = moment(item.to, HOUR_FORMAT);
      }
    }

    // case when from date is today to avoid allowing user to select start time in the past
    if (moment(fromDate).format(DAY_FORMAT) === moment().format(DAY_FORMAT)) {
      minHour = moment.max(minHour, moment());
    }

    return { minHour, maxHour };
  };

  /**
   * Check if from & to are included in a slot
   */
  const checkRange = (slots, from, to) => {
    for (let i = 0; i < slots.length; i += 1) {
      const item = slots[i];

      const itemFrom = moment(item.from, HOUR_FORMAT);
      const itemTo = moment(item.to, HOUR_FORMAT);

      const range = moment.range(itemFrom, itemTo);

      // if from & to are included in one slot => ok
      if (range.contains(from) && range.contains(to)) return true;
    }
    return false;
  };
  /**
   * Construct <-- FROM hours list
   */
  const contructFrom = () => {
    const listFrom = [];

    if (!values.dateFrom || !values.dateTo) return listFrom;

    const hours = getRangeHours();

    if (!hours.length) return listFrom;

    const { minHour, maxHour } = getMinMaxHours(hours, values.dateFrom);

    let check;
    const current = moment(minHour);
    do {
      const checkTo = moment(current).add(workspace.minStay, 'hours');
      if (checkRange(hours, current, checkTo)) {
        listFrom.push(current.format(HOUR_FORMAT));
      }

      current.add(30, 'minutes');

      check = current.isBefore(maxHour);
    } while (check);

    return listFrom;
  };

  /**
   * Construct --> TO hours list
   */
  const constructTo = value => {
    const listTo = [];

    if (!value) return listTo;

    const from = moment(value, HOUR_FORMAT);

    const hours = getRangeHours();

    if (!hours.length) return listTo;

    const hoursFrom = moment(value, HOUR_FORMAT);
    // start à hoursFrom + minStay
    const current = moment(hoursFrom).add(workspace.minStay, 'hours');

    let check = checkRange(hours, from, current);
    while (check) {
      listTo.push(current.format(HOUR_FORMAT));

      current.add(30, 'minutes');

      check = checkRange(hours, from, current);
    }

    return listTo;
  };

  const listFrom = contructFrom();

  const listTo = constructTo(values.hoursFrom);

  const handleHourFrom = value => {
    const newListTo = constructTo(value);
    form.change('hoursTo', newListTo.length === 1 ? newListTo[0] : undefined);
  };

  return (
    <form className={styles('BookingForm')} onSubmit={handleSubmit}>
      <Field
        noMargin
        dropdown
        component={DateRangePicker}
        minDate={minDate.toDate()}
        maxDate={maxDate.toDate()}
        doneAction={handleDone}
        onChange={handleDateChange}
        seed="daterange"
        disabledDay={handleDisabled}
        name="dates"
        dropdownClassName={styles('filter-item', 'last')}
        label="forms.booking.date.date"
        placeholder="forms.booking.date.placeholder"
        forceClose={forceClose}
        validate={required}
      />
      {values.dateFrom && values.dateTo && (
        <>
          <div className={styles('inline')}>
            <Field
              component={ProjectInput}
              name="hoursFrom"
              seed="select"
              items={listFrom}
              onChange={handleHourFrom}
              label="forms.booking.start.start"
              validate={required}
              disabled={!listFrom?.length}
              customClassName={styles('select')}
            />

            <Field
              component={ProjectInput}
              name="hoursTo"
              disabled={!values.hoursFrom || !listTo?.length}
              seed="select"
              items={listTo}
              label="forms.booking.finish.placeholder"
              min={values.hoursFrom}
              validate={required}
              customClassName={styles('select')}
            />
          </div>
          {!listFrom?.length && !listTo?.length && (
            <div className={styles('no-result')}>
              <TextItem path="forms.booking.noHours" />
            </div>
          )}
        </>
      )}
      {/* <div className={styles('available-schedule')}>
        <TextItem path="Horaires disponnibles : " />
        {availability.hours.map((item, key) => {
          return <span key={key}>{`${key > 0 ? ', ' : ''}${item.from} - ${item.to}`}</span>;
        })}
      </div> */}

      {/* <Field
        component={ProjectInput}
        name="people"
        label="forms.booking.person.person"
        placeholder="forms.booking.person.placeholder"
        inputType="number"
        min={1}
        max={14}
        customClassName={styles('select')}
      /> */}
      <ProjectButton
        customClassName={styles('button')}
        disabled={submitting || !valid}
        onSubmit={handleSubmit}
        rounded
        label="forms.booking.book"
        type="submit"
        color="primary"
      />
    </form>
  );
};

export default BookingForm;
