import classNames from 'classnames';
import Datepicker from 'react-datepicker';
import extend from 'extend';
import isequalwith from 'lodash.isequalwith';
import moment from 'moment';
import React, { PropTypes } from 'react';

import BaseSelect from '../BaseSelect';
import BetweenDatepicker from './BetweenDatepicker';
import ClearAll from '../ClearAll';
import HourSelect from './HourSelect';
import optionsFactory, { buildChicklets, formatLabel } from './options';
import wrap from '../ItemContainerWrapper';
import { itemHandlerCreator, operatorSelector, isSameDate } from '../../utils';
import { dateHasErrors } from '../../utils/validations.js';
import { HOUR_FORMAT } from './hourOptions';


const defaultProps = {
  min: moment().subtract(3, 'months'),
  max: moment().add(1, 'years'),
  selectedValuesInitial: [],
  datepicker: {
    showMonthDropDown: true,
    showYearDropDown: true,
  },
  mainSelector: {
    visible: true,
    notSelectedOption: null,
    optionsGroups: [],
  },
  events: {},
  clearAll: { visible: true },
};

const propTypes = {
  min: PropTypes.object,
  max: PropTypes.object,
  labelHandler: PropTypes.func,
  datepicker: PropTypes.shape({
    showMonthDropDown: PropTypes.bool,
    showYearDropDown: PropTypes.bool,
  }),
  mainSelector: PropTypes.shape({
    visible: PropTypes.bool,
    notSelectedOption: PropTypes.shape({
      label: PropTypes.string,
      selected: PropTypes.bool,
    }),
    optionGroups: PropTypes.arrayOf(
      PropTypes.shape({
        label: PropTypes.string,
        options: PropTypes.arrayOf(
          PropTypes.shape({
            values: PropTypes.arrayOf(
              PropTypes.oneOfType([
                PropTypes.object,
                PropTypes.string,
              ]),
            ),
            operator: PropTypes.string,
            showTime: PropTypes.bool,
            selected: PropTypes.bool,
          }),
        ),
      }).isRequired,
    ),
  }),
  events: PropTypes.shape({
    onSelect: PropTypes.func,
    onChange: PropTypes.func,
  }),
  // internal
  theme: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.func,
  ]),
  onOptionSelected: PropTypes.func.isRequired,
  onItemAdded: PropTypes.func.isRequired,
  onSetChicklets: PropTypes.func.isRequired,
  selectedValuesInitial: PropTypes.arrayOf(PropTypes.object),
};

class KdSelectDate extends React.Component {
  constructor(props) {
    super(props);

    this.mainSelector = extend(true, {}, defaultProps.mainSelector, this.props.mainSelector);

    const { kdFilterModel } = this.props;
    this.kdFilterOptions = {
      model: kdFilterModel,
      operator: operatorSelector(kdFilterModel.getDataType()),
      itemHandler: this.props.itemHandler || itemHandlerCreator(kdFilterModel.getDataType()),
    };
    this.options = this.buildOptions();
  }

  setSelectedValues = (selectedValuesInitial) => {
    const {
      onOptionSelected,
      onError,
    } = this.props;
    if (selectedValuesInitial.length) {
      const chicklets = buildChicklets(selectedValuesInitial);
      let selectedOption;
      chicklets.forEach((chicklet) => {
        const option = this.options.find(opt => opt.id === chicklet.operator);
        if (option) {
          this.addByOperator({ ...chicklet, silent: true });
          selectedOption = option;
        } else {
          console.error(`Error - ${chicklet.operator} must be included in the options`);
        }
      });
      if (selectedOption) {
        this.setState({ selectedOption }, () => {
          onOptionSelected(selectedOption.id);
        });
      }
    }
  }

  getDefaultSelected = () => {
    const selected = this.options.filter(o => o.isSelected)[0];
    return selected || this.options[0];
  }

  componentWillMount() {
    const selectedOption = this.getDefaultSelected();
    this.onSelectChange({ target: { value: selectedOption.id } }, true);
    this.setSelectedValues(this.props.selectedValuesInitial);
  }

  componentWillReceiveProps(nextProps) {
    const { selectedValuesInitial, onClearChicklets } = this.props;
    const valuesHasChanged = !isequalwith(selectedValuesInitial, nextProps.selectedValuesInitial, isSameDate);

    if (selectedValuesInitial && valuesHasChanged) {
      onClearChicklets(false);
      this.setSelectedValues(nextProps.selectedValuesInitial);
    }
  }

  onSelectChange = ({ target }, silence = false) => {
    const selectedOption = this.options.find(option => option.id === target.value);
    const {
      emptyItems,
      onClearChicklets,
      onError,
      min,
      max,
      isChickletNone,
      clearErrors,
    } = this.props;

    onClearChicklets(false);
    if (selectedOption.default) {
      selectedOption.clearValues();
    }
    if (selectedOption.id === 'any') {
      clearErrors();
    } else if (selectedOption.loadDefaultValue()) {
      this.addByOperator(selectedOption.selected);
    } else {
      if (isChickletNone) {
        onError({ errors: dateHasErrors(selectedOption.values, min, max) });
      }
      if (emptyItems) {
        onError({ errors: dateHasErrors(selectedOption.values, min, max) });
      }
    }

    this.props.onOptionSelected(selectedOption.id);

    this.setState({ selectedOption }, () => {
      const { onSelect } = this.props.events;
      const label = selectedOption.displayName;
      const value = selectedOption.id;
      const data = { label, value };
      if (onSelect && !silence) {
        onSelect({ data });
      }
    });
  }

  getSelectedHour = () => {
    const { selectedOption } = this.state;

    if (selectedOption && selectedOption.selected) {
      return selectedOption.selected.value.format(HOUR_FORMAT);
    }

    return moment().hour(0).minutes(0).format(HOUR_FORMAT);
  }

  buildOptions = () => {
    const {
      optionGroups,
      notSelectedOption,
    } = this.mainSelector;
    let opts = optionGroups[0].options
      .map(
        (option) => {
          if (option.values) {
            return optionsFactory.createCustom(option);
          } else {
            return optionsFactory.create(option.operator, option.showTime, option.label, option.selected);
          }
        }
      );
    if (notSelectedOption) {
      const { label, selected } = notSelectedOption;
      opts = [optionsFactory.create('Any', false, label, selected), ...opts];
    }
    return opts;
  }

  handleHourChange = (hourString) => {
    const hour = moment(hourString, HOUR_FORMAT).hour();

    const { selectedOption } = this.state;
    const date = selectedOption.selected;

    if (date && date.value) {
      date.value.hour(hour);
      selectedOption.selected = date.value;
      this.addByOperator(selectedOption.selected);
    }
  }

  handleChange = (date) => {
    const { selectedOption } = this.state;
    if (!selectedOption.selected || !selectedOption.selected.values.length) {
      const process = d => d && d.hour(0).minutes(0);

      if (Array.isArray(date)) {
        selectedOption.selected = date.map(process);
      } else {
        selectedOption.selected = process(date);
      }
    } else {
      selectedOption.selected = date;
    }

    this.addByOperator(selectedOption.selected);
  }

  selectedOptionHelper = () => {
    const { selectedOption } = this.state;
    const { min, max, theme } = this.props;
    let selectedValue = null;
    const noErrors = !dateHasErrors(selectedOption.selectedValue[0], min, max);
    const noEqOp = selectedOption.option !== 'Eq' && selectedOption.option !== 'notEq';
    if (noErrors && noEqOp) {
      selectedValue = selectedOption.selectedValue[0];
    }

    const dateFormat = this.props.datepicker.showYearDropDown ? 'MM/DD/YYYY' : 'MM/DD';

    switch (selectedOption.id) {
      case 'Eq':
      case 'Gt':
      case 'Gte':
      case 'Lte':
      case 'Lt': {
        return (
          <div className={classNames('o-layout', theme['o-layout'])}>
            <div
              className={
                classNames(
                  'o-layout__item',
                  {
                    'u-3/4': this.renderTime(selectedOption.id),
                    'u-1/1': !this.renderTime(selectedOption.id),
                  },
                  theme['o-layout__item'],
                  {
                    [theme['u-3/4']]: this.renderTime(selectedOption.id),
                    [theme['u-1/1']]: !this.renderTime(selectedOption.id),
                  },
                )
              }
            >
              <Datepicker
                className={classNames('c-kd__input', theme['c-kd__input'])}
                selected={selectedValue}
                onChange={this.handleChange}
                showMonthDropdown={this.props.datepicker.showMonthDropDown}
                dropdownMode="select"
                showYearDropdown={this.props.datepicker.showYearDropDown}
                scrollableYearDropdown
                minDate={min}
                maxDate={max}
                dateFormat={dateFormat}
                placeholderText={dateFormat}
              />
            </div>
            {
              (() => {
                if (this.renderTime(selectedOption.id)) {
                  return (
                    <div
                      className={
                        classNames('u-1/4', 'o-layout__item', theme['u-1/4'], theme['o-layout__item'])
                      }
                    >
                      <HourSelect
                        onChange={this.handleHourChange}
                        selected={this.getSelectedHour()}
                        theme={this.props.theme}
                      />
                    </div>
                  );
                }

                return null;
              })()
            }
          </div>
        );
      }
      case 'Between': {
        const selectedValue = selectedOption.selectedValue[0];
        const from = selectedValue ? selectedValue[0] : null;
        const to = selectedValue ? selectedValue[1] : null;
        return (
          <BetweenDatepicker
            theme={theme}
            onChange={this.handleChange}
            min={min}
            max={max}
            from={from}
            to={to}
            showMonthDropdown={this.props.datepicker.showMonthDropDown}
            showYearDropdown={this.props.datepicker.showYearDropDown}
          />
        );
      }
      default:
        return null;
    }
  }

  renderTime = (option) => {
    if (option.showtime) {
      return true;
    }
    return false;
  }

  getChickletLabel = (selectedItem) => {
    const { labelHandler } = this.props;
    if (labelHandler) {
      return labelHandler(selectedItem);
    }
    return selectedItem.chickletLabel;
  }

  selectDefaultOption = () => {
    this.setState({ selectedOption: this.options[0] },
      () => this.props.onOptionSelected(this.state.selectedOption.id)
    );
  }

  addByOperator(selectedItem) {
    const { option, values, prefix, showTime } = selectedItem;
    const { min, max } = this.props;
    switch (option) {
      case 'Eq':
      case 'NotEq':
        values.forEach(value =>
          this.addItem({
            ...selectedItem,
            value,
            id: selectedItem.id + formatLabel(value),
            chickletLabel: `${prefix || ''}${formatLabel(value)}`,
          })
        );
        break;
      case 'Between':
        if (values.length && !dateHasErrors(values, min, max)) {
          const chickletLabel = `${formatLabel(values[0], showTime)} - ${formatLabel(values[1], showTime)}`;
          this.addItem({ ...selectedItem, value: values, chickletLabel });
        }
        break;
      case 'Gt':
      case 'Gte':
      case 'Lt':
      case 'Lte':
        this.addItem({
          ...selectedItem,
          value: values[0],
          chickletLabel: prefix + formatLabel(values[0]),
        });
        break;
      default:
        console.error('Error - Invalid Operator ');
        break;
    }
  }

  addItem = (selectedItem) => {
    const { option, value, silent = false } = selectedItem;
    const { min, max, onItemAdded, itemHandler, events: { onChange } } = this.props;
    const { model } = this.kdFilterOptions;
    const kdFilterOptions = {
      model,
      operator: option,
      itemHandler: itemHandler || itemHandlerCreator(model.getDataType()),
    };
    let callback;
    if (!silent) {
      callback = () => {
        onChange && onChange({
          data: {
            option,
            value,
            model,
            ...model.getFilterItemValue(),
          },
        });
      };
    }

    const remove = (items) => {
      if (!items.length) {
        this.selectDefaultOption();
      }
    };

    onItemAdded({
      ...selectedItem,
      value,
      kdFilterOptions,
      remove,
      id: this.getChickletLabel({ ...selectedItem, operator: option }),
      chickletLabel: this.getChickletLabel({ ...selectedItem, operator: option }),
      errors: dateHasErrors(value, min, max),
      option: selectedItem.id,
    }, callback);
  }

  render() {
    const { theme } = this.props;
    return (
      <div className={classNames('c-kd-select-date', theme['c-kd-select-date'])}>
        {
          (() => {
            if (this.mainSelector.visible) {
              return (
                <BaseSelect
                  theme={theme}
                  inline
                  options={this.options}
                  onChange={this.onSelectChange}
                  defaultValue={this.state.selectedOption.id}
                />
              );
            }

            return null;
          })()
        }
        {this.selectedOptionHelper()}
        {
          !this.props.emptyItems &&
            !this.props.isChickletNone &&
              this.props.chicklets.clearAll &&
                <ClearAll theme={theme} onClick={
                  () => {
                    this.selectDefaultOption();
                    this.props.onClearChicklets();
                  }} />
        }
      </div>
    );
  }
}

KdSelectDate.defaultProps = defaultProps;
KdSelectDate.propTypes = propTypes;

export default wrap(KdSelectDate);
