import React from 'react';
import { shallow, mount } from 'enzyme';

import { DatePicker } from 'web-foundation';
import Day from 'web-foundation/atoms/date-picker/day';
import Month from 'web-foundation/atoms/date-picker/month';
import DateRangePicker
    from 'web-foundation/atoms/date-picker/date-range-picker';

// (enzyme wrapper) -> returns true if it is a Day component with non-null date
function isDayWithDate(node) {
    return node.type() === Day && node.prop('date') instanceof Date;
}

describe('<Day />', function() {
    it('should initialize properly', function() {
        const wrapper = shallow(<Day />);

        expect(wrapper.hasClass('day')).to.be.true;
    });

    it('should allow several props that affect classes', function() {
        const wrapper = shallow(
            <Day
                selected
                inRange
                date={ new Date() } />
        );

        expect(wrapper.hasClass('selected')).to.be.true;
        expect(wrapper.hasClass('in-range')).to.be.true;
    });

    describe('passed null as date', function() {
        it('should be empty if passed null as date', function() {
            const wrapper = shallow(<Day date={ null } />);

            expect(wrapper.hasClass('empty')).to.be.true;
            expect(wrapper.hasClass('disabled')).to.be.true;
            expect(wrapper.text()).to.not.be.ok;
        });

        it('should not trigger callback for mouseenter and click', function() {
            const spy = sinon.spy(),
                wrapper = shallow(
                    <Day
                        onMouseEnter={ spy }
                        onClick={ spy }
                        date={ null } />
                );

            wrapper.simulate('click');
            expect(spy.called).to.be.false;

            wrapper.simulate('mouseenter');
            expect(spy.called).to.be.false;
        });
    });

    it('should trigger callback on mouseenter and click', function() {
        const date = new Date(),
            spy = sinon.spy(),
            wrapper = shallow(
                <Day
                    onMouseEnter={ spy }
                    onClick={ spy }
                    date={ date }/>
            );

        wrapper.simulate('click');
        expect(spy.calledOnce).to.be.true;
        expect(spy.calledWith(date)).to.be.true;

        wrapper.simulate('mouseenter');
        expect(spy.calledTwice).to.be.true;
        expect(spy.secondCall.calledWith(date)).to.be.true;
    });
});

describe('<Month />', function() {
    it('should initialize properly', function() {
        const wrapper = shallow(
            <Month
                year={ 2016 }
                month={ 1 } />
        );

        expect(wrapper.hasClass('month')).to.be.true;
    });

    it('should look ok with all the right props', function() {
        const rangeStart = new Date(2016, 4, 2),
            hoveredDate = new Date(2016, 4, 15),
            wrapper = mount(
                <Month
                    year={ 2016 }
                    month={ 5 }
                    startsOn={ 1 }
                    rangeStart={ rangeStart }
                    hoveredDate={ hoveredDate } />
            );

        expect(wrapper.find('.week')).to.have.length(6);

        expect(wrapper.find('.day')).to.have.length(42);
        expect(wrapper.find('.day').filter('.empty')).to.have.length(11);

        expect(wrapper.findWhere((node) => {
            return isDayWithDate(node) &&
                node.prop('date').getTime() === rangeStart.getTime();
        })).to.have.length(1);

        expect(wrapper.findWhere((node) => {
            return isDayWithDate(node) && node.prop('inRange');
        })).to.have.length(14);
    });

    it('should trigger callbacks on select and hover', function() {
        const spy = sinon.spy(),
            wrapper = mount(
                <Month
                    year={ 2016 }
                    month={ 5 }
                    startsOn={ 1 }
                    onSelectDate={ spy }
                    onHoverOverDate={ spy } />
            ),
            day = wrapper.findWhere(isDayWithDate).first(),
            date = day.prop('date');

        day.simulate('click');
        expect(spy.calledOnce).to.be.true;
        expect(spy.calledWith(date)).to.be.true;

        day.simulate('mouseenter');
        expect(spy.calledTwice).to.be.true;
        expect(spy.secondCall.calledWith(date)).to.be.true;
    });
});

describe('<DateRangePicker />', function() {
    it('should initialize properly', function() {
        const wrapper = shallow(
            <DateRangePicker
                year={ 2016 }
                month={ 6 } />
        );

        expect(wrapper.hasClass('date-range-picker')).to.be.true;
    });

    it('should trigger callback on date selection', function() {
        const spy = sinon.spy(),
            wrapper = mount(
                <DateRangePicker
                    year={ 2016 }
                    month={ 5 }
                    onSelectDates={ spy } />
            ),
            days = wrapper.findWhere(isDayWithDate),
            dayA = days.at(15),
            dayB = days.at(25),
            applyButton = wrapper.find('.button');
        let start, end;

        // select first date
        dayA.simulate('click');
        applyButton.simulate('click');
        expect(spy.calledOnce).to.be.true;

        ({ start, end } = spy.firstCall.args[0]);

        expect(start.getTime()).to.equal(dayA.prop('date').getTime());
        expect(end).to.be.null;

        // select the same first date again
        dayA.simulate('click');
        applyButton.simulate('click');
        expect(spy.calledTwice).to.be.true;

        ({ start, end } = spy.secondCall.args[0]);

        expect(start.getTime()).to.equal(dayA.prop('date').getTime());
        expect(end).to.be.null;

        // select a second (different) date
        dayB.simulate('click');
        applyButton.simulate('click');
        expect(spy.calledThrice).to.be.true;

        ({ start, end } = spy.thirdCall.args[0]);

        expect(start.getTime()).to.equal(dayA.prop('date').getTime());
        expect(end.getTime()).to.equal(dayB.prop('date').getTime());
    });

    it('should reverse dates automatically for callback', function() {
        const spy = sinon.spy(),
            wrapper = mount(
                <DateRangePicker
                    year={ 2016 }
                    month={ 5 }
                    onSelectDates={ spy } />
            ),
            days = wrapper.findWhere(isDayWithDate),
            dayA = days.at(15),
            dayB = days.at(8),
            applyButton = wrapper.find('.button');
        let start, end;

        // select first date
        dayA.simulate('click');
        applyButton.simulate('click');
        expect(spy.calledOnce).to.be.true;

        ({ start, end } = spy.firstCall.args[0]);

        expect(start.getTime()).to.equal(dayA.prop('date').getTime());
        expect(end).to.be.null;

        // select a second (different) date
        dayB.simulate('click');
        applyButton.simulate('click');
        expect(spy.calledTwice).to.be.true;

        ({ start, end } = spy.secondCall.args[0]);

        // the dates should be in chronological order
        expect(start.getTime()).to.equal(dayB.prop('date').getTime());
        expect(end.getTime()).to.equal(dayA.prop('date').getTime());
    });

    it('should be selecting first date on mount no matter what', function() {
        const spy = sinon.spy(),
            someDay = new Date(2016, 4, 1),
            wrapper = mount(
                <DateRangePicker
                    year={ 2016 }
                    month={ 5 }
                    initialRangeStart={ someDay } // init with a start date
                    onSelectDates={ spy } />
            ),
            days = wrapper.findWhere(isDayWithDate),
            anotherDay = days.at(15),
            applyButton = wrapper.find('.button');

        // select a date
        anotherDay.simulate('click');
        applyButton.simulate('click');
        expect(spy.calledOnce).to.be.true;

        const { start, end } = spy.firstCall.args[0];

        // expect start to equal selected date, and end to be null
        expect(start.getTime()).to.equal(anotherDay.prop('date').getTime());
        expect(end).to.be.null;
    });

    it('state should change upon mouseenter and mouseleave events', function() {
        const wrapper = mount(
            <DateRangePicker
                year={ 2016 }
                month={ 5 } />),
            day = wrapper.findWhere(isDayWithDate).first();

        day.simulate('mouseenter');

        expect(wrapper.state('hoveredDate').getTime())
            .to.equal(day.prop('date').getTime());

        wrapper.simulate('mouseleave');

        expect(wrapper.state('hoveredDate')).to.be.null;
    });

    it('should show months that contain selected range', function() {
        const start = new Date(2015, 4, 1),
            end = new Date(2016, 6, 1),
            wrapper = mount(
                <DateRangePicker
                    initialRangeStart={ start }
                    initialRangeEnd={ end } />
            );

        expect(wrapper.findWhere((node) =>
            node.type() === Month &&
                node.prop('year') === start.getFullYear() &&
                node.prop('month') === start.getMonth() + 1)
        ).to.have.length(1);
    });
});

describe('<DatePicker />', function() {
    class DummyPicker extends React.Component {
        render() {
            return (
                <div className="dummy">Empty</div>
            );
        }
    }

    const today = new Date(2016, 5, 22);

    it('should initialize properly', function() {
        const wrapper = shallow(<DatePicker initialRangeStart={ today }/>);

        expect(wrapper.hasClass('date-picker')).to.be.true;
    });

    it('should show placeholder if no initial date range', function() {
        const wrapper = mount(<DatePicker placeholder="foobar"/>);

        expect(wrapper.find('.current .button').text()).to.match(/foobar/);
    });

    it('should have child component if open', function() {
        const wrapper = mount(
            <DatePicker initialRangeStart={ today }>
                <DummyPicker />
            </DatePicker>
        );

        wrapper.find('.current .button').simulate('click');

        expect(wrapper.find('.dummy')).to.have.length(1);
    });

    it('should allow shortcuts', function() {
        const spy = sinon.spy(),
            today = new Date(2016, 5, 14),
            shortcuts = [
                {
                    name: '7-Day',
                    rangeStart: new Date(2016, 5, 7),
                    rangeEnd: today
                },
                {
                    name: '30-Day',
                    rangeStart: new Date(2016, 4, 14),
                    rangeEnd: today
                }
            ],
            wrapper = mount(
                <DatePicker
                    initialRangeStart={ today }
                    onSelectDates={ spy }
                    shortcuts={ shortcuts }>
                    <DummyPicker />
                </DatePicker>
            );

        wrapper.setState({ open: true });

        expect(wrapper.find('.shortcuts .button'))
            .to.have.length(shortcuts.length);

        wrapper.find('.shortcuts .button').first().simulate('click');

        expect(spy.called).to.be.true;

        const { start, end } = spy.firstCall.args[0];

        expect(start.getTime()).to.equal(shortcuts[0].rangeStart.getTime());
        expect(end.getTime()).to.equal(shortcuts[0].rangeEnd.getTime());
    });

    it('should trigger callbacks when opened or closed', function() {
        const spy = sinon.spy(),
            wrapper = mount(
                <DatePicker
                    initialRangeStart={ today }
                    onOpen={ spy }
                    onClose={ spy } >
                    <DummyPicker />
                </DatePicker>
            );

        wrapper.find('.current .button').simulate('click');

        expect(wrapper.state('open')).to.be.true;
        expect(spy.calledOnce).to.be.true;

        wrapper.find('.controls .button.exit').simulate('click');

        expect(wrapper.state('open')).to.be.false;
        expect(spy.calledTwice).to.be.true;
    });

    it('should pass the right props to child component', function() {
        const wrapper = shallow(
            <DatePicker initialRangeStart={ today } >
                <DummyPicker />
            </DatePicker>
        );

        wrapper.setState({ open: true });

        const dummy = wrapper
            .findWhere((node) => node.type() === DummyPicker)
            .first();

        expect(dummy).to.be.ok;
        expect(dummy.prop('initialRangeStart'))
            .to.equal(wrapper.state('currentRangeStart'));
        expect(dummy.prop('initialRangeEnd'))
            .to.equal(wrapper.state('currentRangeEnd'));
        expect(dummy.prop('startsOn'))
            .to.equal(wrapper.instance().props.startsOn);
        expect(dummy.prop('onSelectDates')).to.be.a('function');
    });

    it('should trigger callback when custom picked dates applied', function() {
        const start = new Date(2015, 5, 1),
            end = new Date(2016, 5, 1),
            spy = sinon.spy(),
            wrapper = mount(
                <DatePicker
                    initialRangeStart={ today }
                    onSelectDates={ spy } >
                    <DummyPicker />
                </DatePicker>
            );

        wrapper.setState({ open: true });

        const dummy = wrapper
            .findWhere((node) => node.type() === DummyPicker)
            .first();

        dummy.prop('onSelectDates')({ start, end });

        expect(spy.called).to.be.true;

        const args = spy.firstCall.args[0];

        expect(args.start).to.equal(start);
        expect(args.end).to.equal(end);
    });

    it('should show the correct dates well-formatted when closed', function() {
        const today = new Date(2016, 5, 3),
            wrapper = mount(
                <DatePicker
                    initialRangeStart={ today }
                    initialRangeEnd={ null } >
                    <DummyPicker />
                </DatePicker>
            );

        expect(wrapper.find('.current .button').text())
            .to.equal('June 3, 2016');

        wrapper.setState({
            currentRangeStart: new Date(2014, 7, 8),
            currentRangeEnd: new Date(2016, 5, 30)
        });

        expect(wrapper.find('.current .button').text())
            .to.equal('08/08/14 - 06/30/16');
    });
});
