import React from 'react'
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
const IORequestModule = require('../../src/containers/Logbook/IORequests')
import LogbookContainerInConnect, { LogbookContainer } from '../../src/containers/Logbook/LogbookContainer';
import { createStore, applyMiddleware } from 'redux'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import reducer from '../../src/reducers'
import promise from "redux-promise-middleware"
import { LOGGED_IN } from '../../src/constants/ActionTypes';
import { GUI_CONFIG } from '../../src/config/gui.config';
jest.mock("../../src/config/gui.config");

const resources = require('./resources/LogbookContainer.resource.js');
beforeEach(() => {
    Enzyme.configure({ adapter: new Adapter() });
})

describe("Unit tests on LogbookContainer", () => {
    describe("reverseEventsSortingByCreationDate()", () => {
        function getWrapper() {
            /* overrides default configuration */
            GUI_CONFIG.mockReturnValue({
                EVENTS_PER_PAGE: 2,
                DEFAULT_SORTING_FILTER: {
                    createdAt: -1  // temporary workaround. createdAt must be replaced by creationDate
                },
                AUTOREFRESH_DELAY: 100,
                AUTOREFRESH_ENABLED: false
            })

            const middleware = [thunk];
            const persistConfig = {
                key: 'root',
                storage,
            }
            const persistedReducer = persistReducer(persistConfig, reducer)
            const store = createStore(
                persistedReducer,
                {
                    user: {
                        type: LOGGED_IN,
                        username: 'username',
                        sessionId: 'testSessionId'
                    }
                },
                applyMiddleware(...middleware, logger, promise(), thunk)
            )

            return Enzyme.shallow(<LogbookContainerInConnect
                investigationId="testInvestigationId"
                store={store}
            />)
        }
        /**
                    * creates a new event object
                    * @param {*} id  the event id
                    * @param {*} title the event title
                    * @param {*} creationDate  the creation date
                    */
        function createEvent(id, title, creationDate) {
            return ({
                _id: id,
                category: "comment",
                content: [{
                    format: 'plainText',
                    text: 'this is a test'
                }, {
                    format: 'html',
                    text: '<p> this is a test </p>'
                }],
                createdAt: creationDate, // was "2018-01-01T00:00:00.000Z"
                creationDate: creationDate,
                datasetId: null,
                file: [],
                fileSize: null,
                filename: null,
                investigationId: 'testInvestigationId',
                machine: null,
                previousVersionEvent: null,
                software: null,
                tag: [],
                title: title,
                type: "annotation",
                updatedAt: "2018-01-01T00:00:00.000Z",
                username: "mchaille"
            })
        }

        describe('No events are selected', () => {
            function setupWrapper(wrapper) {
                /* creates 11 events */
                let actualDownloadedEvents = new Array();
                for (let index = 1; index <= 11; index++) {
                    actualDownloadedEvents.push(createEvent(index, "title" + index, new Date('0' + index + ' Jan 2018 00:00:00 GMT')));
                }

                let actualActivePage = 2;

                // setup the starting state of the instance
                wrapper.instance().setState({
                    activePage: actualActivePage,
                    downloadedEvent: actualDownloadedEvents,
                    pageEvents: actualDownloadedEvents.slice(2, 3)
                })
                wrapper.update();

            }

            test("getEvents function is called one time only", () => {
                let mockedGetEvents = jest.spyOn(LogbookContainer.prototype, 'getEvents').mockImplementation(() => null);
                let eventContainerWrapper = getWrapper().dive();
                expect(mockedGetEvents).toHaveBeenCalledTimes(1);

                setupWrapper(eventContainerWrapper);
                // Run the method to be tested
                eventContainerWrapper.instance().reverseEventsSortingByCreationDate();

                expect(mockedGetEvents).toHaveBeenCalledTimes(2);
                mockedGetEvents.mockRestore();
            })

            test("getEvents function is called with proper parameters", () => {
                let mockedGetEvents = jest.spyOn(LogbookContainer.prototype, 'getEvents').mockImplementation(() => null);
                //create the eventContainer wrapper
                let eventContainerWrapper = getWrapper().dive();
                setupWrapper(eventContainerWrapper);
                // Run the method to be tested
                eventContainerWrapper.instance().reverseEventsSortingByCreationDate();

                let expectedOffset = 0;
                let expectedSortingFilter = { createdAt: 1 };
                let expectedSelectionFilter = { "$and": [{ "$or": [{ "type": "annotation" }, { "type": "notification" }] }] };
                ;
                let expectedIsEventCountRequestNeeded = false;

                expect(mockedGetEvents).toHaveBeenCalledWith(expectedOffset, expectedSelectionFilter, expectedIsEventCountRequestNeeded, expectedSortingFilter);
                mockedGetEvents.mockRestore();

            })

            test("activepage is change to 1", () => {
                // here we mock to reimplement the function and avoid http request complains
                let mockedGetEvents = jest.spyOn(LogbookContainer.prototype, 'getEvents').mockImplementation(() => null);

                //create the eventContainer wrapper
                let eventContainerWrapper = getWrapper().dive();
                setupWrapper(eventContainerWrapper);
                // Run the method to be tested
                eventContainerWrapper.instance().reverseEventsSortingByCreationDate();

                let expectedActivePage = 1;
                expect(eventContainerWrapper.instance().state.activePage).toBe(expectedActivePage);
                mockedGetEvents.mockRestore();
            })
        })
    })

    describe("onPageClicked", () => {
        let mockedGetEvents = null;

        beforeEach(() => {
            // mock getEvents to avoid verbose http request errors
            mockedGetEvents = jest.spyOn(LogbookContainer.prototype, 'getEvents').mockImplementation(() => null);
        })
        afterEach(() => {
            mockedGetEvents.mockRestore()
        })


        function getWrapper() {
            /* overrides default configuration */
            GUI_CONFIG.mockReturnValue({
                EVENTS_PER_PAGE: 2,
                DEFAULT_SORTING_FILTER: {
                    createdAt: -1  // temporary workaround. createdAt must be replaced by creationDate
                },
                AUTOREFRESH_DELAY: 100,
                AUTOREFRESH_ENABLED: false
            })

            const middleware = [thunk];
            const persistConfig = {
                key: 'root',
                storage,
            }
            const persistedReducer = persistReducer(persistConfig, reducer)
            const store = createStore(
                persistedReducer,
                {
                    user: {
                        type: LOGGED_IN,
                        username: 'username',
                        sessionId: 'testSessionId'
                    }
                },
                applyMiddleware(...middleware, logger, promise(), thunk)
            )

            return Enzyme.shallow(<LogbookContainerInConnect
                investigationId="testInvestigationId"
                store={store}
            />).dive()
        }

        /**
                    * creates a new event object
                    * @param {*} id  the event id
                    * @param {*} title the event title
                    * @param {*} creationDate  the creation date
                    */
        function createEvent(id, title, creationDate) {
            return ({
                _id: id,
                category: "comment",
                content: [{
                    format: 'plainText',
                    text: 'this is a test'
                }, {
                    format: 'html',
                    text: '<p> this is a test </p>'
                }],
                createdAt: creationDate, // was "2018-01-01T00:00:00.000Z"
                creationDate: creationDate,
                datasetId: null,
                file: [],
                fileSize: null,
                filename: null,
                investigationId: 'testInvestigationId',
                machine: null,
                previousVersionEvent: null,
                software: null,
                tag: [],
                title: title,
                type: "annotation",
                updatedAt: "2018-01-01T00:00:00.000Z",
                username: "mchaille"
            })
        }

        function setupWrapper(wrapper, activePage, numberEventsFoundOnServer, numberEventsDownloaded) {
            function getSliceForPageEvent() {
                if (activePage === 1) {
                    return actualfoundEvents.slice(0, 2)
                }
                if (activePage === 2) {
                    return actualfoundEvents.slice(2, 4)
                }
                if (activePage === 3) {
                    return actualfoundEvents.slice(4, 6)
                }
                if (activePage === 4) {
                    return actualfoundEvents.slice(6, 8)
                }
                if (activePage === 5) {
                    return actualfoundEvents.slice(8, 10)
                }
                if (activePage === 6) {
                    return actualfoundEvents.slice(10)
                }
            }

            function getSliceForDownloadedEvent() {
                if (activePage === 1) {
                    return actualfoundEvents.slice(0, 4)
                }
                if (activePage === 2) {
                    return actualfoundEvents.slice(2, 6)
                }
                if (activePage === 3) {
                    return actualfoundEvents.slice(4, 8)
                }
                if (activePage === 4) {
                    return actualfoundEvents.slice(6, 10)
                }
                if (activePage === 5) {
                    return actualfoundEvents.slice(8, 11)
                }
                if (activePage === 6) {
                    return actualfoundEvents.slice(10)
                }
            }

            function getDownloadedEventIndexes() {
                if (activePage === 1) {
                    return { start: 0, end: 3 }
                }
                if (activePage === 2) {
                    return { start: 2, end: 5 }
                }
                if (activePage === 3) {
                    return { start: 4, end: 7 }
                }
                if (activePage === 4) {
                    return { start: 6, end: 9 }
                }
                if (activePage === 5) {
                    return { start: 8, end: 10 }
                }
                if (activePage === 6) {
                    return { start: 10, end: 10 }
                }
            }

            /* creates numberEventsFoundOnServer events */
            let actualfoundEvents = new Array();
            for (let index = 1; index <= numberEventsFoundOnServer; index++) {
                actualfoundEvents.push(createEvent(index, "title" + index, new Date('0' + index + ' Jan 2018 00:00:00 GMT')));
            }

            let actualActivePage = activePage;

            // setup the starting state of the instance
            wrapper.instance().setState({
                activePage: actualActivePage,
                downloadedEvents: getSliceForDownloadedEvent(),
                downloadedEventIndexes: getDownloadedEventIndexes(),
                eventCountSinceLastRefreshForCurrentSelectionFilter: numberEventsFoundOnServer,
                pageEvents: getSliceForPageEvent()
            })
            wrapper.update();

            // mock the downloadEvent functions, because we want to see how it is called
            wrapper.instance().getEvents = jest.fn();
            wrapper.update();
        }

        it("resets the selectedEvent to zero", () => {
            let actualSelectedEventId = 5; //arbitrary value  != 0
            let selectedPage = 1;
            let myEventContainerWrapper = getWrapper();

            myEventContainerWrapper.instance().state.selectedEventId = actualSelectedEventId;

            myEventContainerWrapper.instance().onPageClicked(selectedPage);

            expect(myEventContainerWrapper.instance().state.selectedEventId).toBe(0);
        })

        describe("User clicks a page which events are already available locally", () => {
            it("display a new page with proper events without calling the server when events are already available locally", () => {
                /*
                page                     1 | 2 | 3 | 4 | 5 | 6
                -------------------------------------------
                downloadedEventIndex     0 | 2 |
                available locally        1 | 3 | 

                                              ^
                                              |- page event index
                */
                let myEventContainerWrapper = getWrapper();

                // create a wrapper that has received 4 events (while there are 11 found by the server) and currently displays page 1
                let actualActivePage = 1;
                setupWrapper(myEventContainerWrapper, actualActivePage, 11, 4)

                // user clicks on page 2
                let selectedPage = 2;
                myEventContainerWrapper.instance().onPageClicked(selectedPage);

                // the proper events are displayed on page 2
                let expectedPageEvents = myEventContainerWrapper.instance().state.downloadedEvents.slice(2)
                expect(myEventContainerWrapper.instance().state.pageEvents).toEqual(expectedPageEvents)

                // no http request was sent to the server
                expect(myEventContainerWrapper.instance().getEvents).toBeCalledTimes(0);
            })

            it("display the last page with proper events without calling the server when events are already available locally", () => {
                /*
                page                     1 | 2 | 3 | 4 | 5 | 6
                ------------------------------------------------
                downloadedEventIndex       |   |   |   | 8 | 10  
                available locally          |   |   |   | 9 | 

                                              ^
                                              |- page event index
                */
                let myEventContainerWrapper = getWrapper();

                // create a wrapper that has received 4 events (while there are 11 found by the 
                // server) and currently displays page 1
                let actualActivePage = 5;
                setupWrapper(myEventContainerWrapper, actualActivePage, 11, 4)

                // user clicks on page 2
                let selectedPage = 6;
                myEventContainerWrapper.instance().onPageClicked(selectedPage);

                // the proper events are displayed on page 6
                let expectedPageEvents = myEventContainerWrapper.instance().state.downloadedEvents.slice(2);

                expect(myEventContainerWrapper.instance().state.pageEvents).toEqual(expectedPageEvents);

                // no http request was sent to the server
                expect(myEventContainerWrapper.instance().getEvents).toBeCalledTimes(0);

            })

            it("display a middle page with proper events without calling the server when events are already available locally", () => {
                /*
                page                     1 | 2 | 3 | 4 | 5 | 6
                ------------------------------------------------
                downloadedEventIndex       |   | 4 | 6 |   | 
                available locally          |   | 5 | 7 |   | 

                                              ^
                                              |- page event index
                */
                let myEventContainerWrapper = getWrapper();

                // create a wrapper that has received 4 events (while there are 11 found by the 
                // server) and currently displays page 1
                let actualActivePage = 3;
                setupWrapper(myEventContainerWrapper, actualActivePage, 11, 4)

                // user clicks on page 4
                let selectedPage = 4;
                myEventContainerWrapper.instance().onPageClicked(selectedPage);

                // the proper events are displayed on page 4
                let expectedPageEvents = myEventContainerWrapper.instance().state.downloadedEvents.slice(2);

                expect(myEventContainerWrapper.instance().state.pageEvents).toEqual(expectedPageEvents);

                // no http request was sent to the server
                expect(myEventContainerWrapper.instance().getEvents).toBeCalledTimes(0);

            })
        })

        describe("User clicks a middle page which events are not available locally", () => {
            it("display a middle page", () => {
                /*
                page                     1 | 2 | 3 | 4 | 5 | 6
                -------------------------------------------
                downloadedEventIndex     0 | 2 |
                available locally        1 | 3 | 

                                              ^
                                              |- page event index
                */
                let myEventContainerWrapper = getWrapper();

                // create a wrapper that has received 4 events (while there are 11 found by the 
                // server) and currently displays page 1
                let actualActivePage = 1;
                setupWrapper(myEventContainerWrapper, actualActivePage, 11, 4)

                // user clicks on page 4
                let selectedPage = 4;
                myEventContainerWrapper.instance().onPageClicked(selectedPage);

                // an http request is sent to the server
                expect(myEventContainerWrapper.instance().getEvents).toBeCalledTimes(1);

                // an http request is sent with proper parameters
                let expectedSortingFilter = { "createdAt": -1 }
                expect(myEventContainerWrapper.instance().getEvents).toHaveBeenCalledWith(6, false, false, expectedSortingFilter);
            })
        })

        describe("User clicks the last page which events are not available locally", () => {
            it("display a middle page", () => {
                /*
                page                     1 | 2 | 3 | 4 | 5 | 6
                -----------------------------------------------
                downloadedEventIndex       | 2 | 4
                available locally          | 3 | 5

                                              ^
                                              |- page event index
                */
                let myEventContainerWrapper = getWrapper();

                // create a wrapper that has received 4 events (while there are 11 found by the 
                // server) and currently displays page 2
                let actualActivePage = 2;
                setupWrapper(myEventContainerWrapper, actualActivePage, 11, 4)

                // user clicks on page 6
                let selectedPage = 6;
                myEventContainerWrapper.instance().onPageClicked(selectedPage);

                // an http request is sent to the server
                expect(myEventContainerWrapper.instance().getEvents).toBeCalledTimes(1);

                // an http request is sent with proper parameters
                let expectedSortingFilter = { "createdAt": -1 }
                expect(myEventContainerWrapper.instance().getEvents).toHaveBeenCalledWith(10, false, false, expectedSortingFilter);
            })
        })

    })

    describe("onNewEventUploaded", () => {

        let mockedGetEvents = null;

        beforeEach(() => {
            // mock getEvents to avoid verbose http request errors
            mockedGetEvents = jest.spyOn(LogbookContainer.prototype, 'getEvents').mockImplementation(() => null);
        })
        afterEach(() => {
            mockedGetEvents.mockRestore()
        })

        function getWrapper() {
            /* overrides default configuration */
            GUI_CONFIG.mockReturnValue({
                EVENTS_PER_PAGE: 2,
                DEFAULT_SORTING_FILTER: {
                    createdAt: -1  // temporary workaround. createdAt must be replaced by creationDate
                },
                AUTOREFRESH_DELAY: 100,

            })

            const middleware = [thunk];
            const persistConfig = {
                key: 'root',
                storage,
            }
            const persistedReducer = persistReducer(persistConfig, reducer)
            const store = createStore(
                persistedReducer,
                {
                    user: {
                        type: LOGGED_IN,
                        username: 'username',
                        sessionId: 'testSessionId'
                    }
                },
                applyMiddleware(...middleware, logger, promise(), thunk)
            )

            return (Enzyme.shallow(<LogbookContainerInConnect
                investigationId="testInvestigationId"
                store={store}
            />).dive())
        }

        function createEvent(id, title, creationDate) {
            return ({
                _id: id,
                category: "comment",
                content: [{
                    format: 'plainText',
                    text: 'this is a test'
                }, {
                    format: 'html',
                    text: '<p> this is a test </p>'
                }],
                createdAt: creationDate, // was "2018-01-01T00:00:00.000Z"
                creationDate: creationDate,
                datasetId: null,
                file: [],
                fileSize: null,
                filename: null,
                investigationId: 'testInvestigationId',
                machine: null,
                previousVersionEvent: null,
                software: null,
                tag: [],
                title: title,
                type: "annotation",
                updatedAt: "2018-01-01T00:00:00.000Z",
                username: "mchaille"
            })
        }

        function setupWrapper(wrapper, activePage, numberEventsFoundOnServer, numberEventsDownloaded) {
            function getSliceForPageEvent() {
                if (activePage === 1) {
                    return actualfoundEvents.slice(0, 2)
                }
                if (activePage === 2) {
                    return actualfoundEvents.slice(2, 4)
                }
                if (activePage === 3) {
                    return actualfoundEvents.slice(4, 6)
                }
                if (activePage === 4) {
                    return actualfoundEvents.slice(6, 8)
                }
                if (activePage === 5) {
                    return actualfoundEvents.slice(8, 10)
                }
                if (activePage === 6) {
                    return actualfoundEvents.slice(10)
                }
            }

            function getSliceForDownloadedEvent() {
                if (activePage === 1) {
                    return actualfoundEvents.slice(0, 4)
                }
                if (activePage === 2) {
                    return actualfoundEvents.slice(2, 6)
                }
                if (activePage === 3) {
                    return actualfoundEvents.slice(4, 8)
                }
                if (activePage === 4) {
                    return actualfoundEvents.slice(6, 10)
                }
                if (activePage === 5) {
                    return actualfoundEvents.slice(8, 11)
                }
                if (activePage === 6) {
                    return actualfoundEvents.slice(10)
                }
            }

            function getDownloadedEventIndexes() {
                if (activePage === 1) {
                    return { start: 0, end: 3 }
                }
                if (activePage === 2) {
                    return { start: 2, end: 5 }
                }
                if (activePage === 3) {
                    return { start: 4, end: 7 }
                }
                if (activePage === 4) {
                    return { start: 6, end: 9 }
                }
                if (activePage === 5) {
                    return { start: 8, end: 10 }
                }
                if (activePage === 6) {
                    return { start: 10, end: 10 }
                }
            }

            /* creates numberEventsFoundOnServer events */
            let actualfoundEvents = new Array();
            for (let index = 1; index <= numberEventsFoundOnServer; index++) {
                actualfoundEvents.push(createEvent(index, "title" + index, new Date('0' + index + ' Jan 2018 00:00:00 GMT')));
            }

            let actualActivePage = activePage;

            // setup the starting state of the instance
            wrapper.instance().setState({
                activePage: actualActivePage,
                downloadedEvents: getSliceForDownloadedEvent(),
                downloadedEventIndexes: getDownloadedEventIndexes(),
                foundEventCount: numberEventsFoundOnServer,
                pageEvents: getSliceForPageEvent()
            })
            wrapper.update();

            // mock the downloadEvent functions, because we want to see how it is called
            wrapper.instance().getEvents = jest.fn();

            wrapper.update();
        }

        it("sets the activepage to 1", () => {
            let myEventContainerWrapper = getWrapper();

            // create a wrapper that has received 4 events (while there are 11 found by the 
            // server) and currently displays page 2
            let actualActivePage = 2;
            setupWrapper(myEventContainerWrapper, actualActivePage, 11, 4)
            expect(myEventContainerWrapper.instance().state.activePage).toBe(2);

            // user has created  a new Event
            myEventContainerWrapper.instance().onNewEventUploaded();

            expect(myEventContainerWrapper.instance().state.activePage).toBe(1);
        })

        it("sets sets isNewEventVisible to false", () => {
            let myEventContainerWrapper = getWrapper();

            // create a wrapper that has received 4 events (while there are 11 found by the 
            // server) and currently displays page 2
            let actualActivePage = 2;
            setupWrapper(myEventContainerWrapper, actualActivePage, 11, 4)
            myEventContainerWrapper.instance().state.isNewEventVisible = true;

            // user has created  a new Event
            myEventContainerWrapper.instance().onNewEventUploaded();

            expect(myEventContainerWrapper.instance().state.isNewEventVisible).toBe(false);
        })
    })

    describe('rendering', () => {
        function getWrapper() {
            /* overrides default configuration */
            GUI_CONFIG.mockReturnValue({
                EVENTS_PER_PAGE: 2,
                EVENTS_PER_DOWNLOAD: 2,
                DEFAULT_SORTING_FILTER: {
                    createdAt: -1  // temporary workaround. createdAt must be replaced by creationDate
                },
                AUTOREFRESH_ENABLED: false,
                AUTOREFRESH_DELAY: 100

            })

            const middleware = [thunk];
            const persistConfig = {
                key: 'root',
                storage,
            }
            const persistedReducer = persistReducer(persistConfig, reducer)
            const store = createStore(
                persistedReducer,
                {
                    user: {
                        type: LOGGED_IN,
                        username: 'username',
                        sessionId: 'testSessionId'
                    }
                },
                applyMiddleware(...middleware, logger, promise(), thunk)
            )

            return (Enzyme.mount(<LogbookContainerInConnect
                investigationId="testInvestigationId"
                store={store}
            />))
        }

        it('displays the loading animation first and then the eventList once event were received', (done) => {
            let resource = resources.rendering.renderAnimationFirstAndThenEventList;

            IORequestModule.getEventsByInvestigationId = jest.fn(
                (sessionId, investigationId, eventsPerDownload, offset, selectionFilter, sortingFilter, onSuccess, onFailure) => { setTimeout(() => onSuccess(resource.serverResponse1), 500) });
            IORequestModule.getEventCountByInvestigationId = jest.fn(
                (sessionId, investigationId, selectionFilter, onSuccess, onFailure) => { setTimeout(() => onSuccess(resource.serverResponse2), 500) });

            let wrapper = getWrapper();
            // first assertion at initialization
            expect(wrapper.find('Loading')).toBeDefined();
            expect(IORequestModule.getEventsByInvestigationId).toHaveBeenCalledTimes(1);
            expect(IORequestModule.getEventCountByInvestigationId).toHaveBeenCalledTimes(2);
            setTimeout(() => {
                // second assertion after onsuccess callback call
                expect(wrapper.find('EvenList')).toBeDefined();
                expect(IORequestModule.getEventsByInvestigationId).toHaveBeenCalledTimes(1);
                expect(IORequestModule.getEventCountByInvestigationId).toHaveBeenCalledTimes(2);
                done();
            }, 4000);
        })
    })
})