import { clone, uniq } from 'lodash';
import { Model } from '../src/model.js';
import { Collection } from "../src/collection";
import { expect } from 'chai';
import Storage from '../src/storage.js';
import root from 'window-or-global';


const attributes = {
    string: 'String',
    string2: 'String 2',
    number: 1337
};

const SavedModel = Model.extend({
    browserStorage: new Storage('SavedModel', 'session'),
    defaults: attributes,
    urlRoot: '/test/'
});

const AjaxModel = Model.extend({
    defaults: attributes
});

const SavedCollection = Collection.extend({
    model: AjaxModel,
    browserStorage: new Storage('SavedCollection', 'session')
});


describe('Storage Model using sessionStorage', function () {
    beforeEach(() => sessionStorage.clear());

    it('is saved with the given name', async function () {
        const mySavedModel = new SavedModel({'id': 10});
        await new Promise((resolve, reject) => mySavedModel.save(null, {'success': resolve}));
        const item = root.sessionStorage.getItem('localforage/SavedModel-10');
        const parsed = JSON.parse(item);
        expect(parsed.id).to.equal(10);
        expect(parsed.string).to.equal('String');
        expect(parsed.string2).to.equal('String 2');
        expect(parsed.number).to.equal(1337);
    });

    it('can be converted to JSON', function () {
        const mySavedModel = new SavedModel({'id': 10});
        mySavedModel.save();
        expect(mySavedModel.toJSON()).to.eql({
            string: 'String',
            id: 10,
            number: 1337,
            string2: 'String 2'
        });
    });

    describe('once saved', function () {
        beforeEach(() => sessionStorage.clear());

        it('can be fetched from sessionStorage', function () {
            const newModel = new SavedModel({'id': 10});
            newModel.fetch();
            expect(newModel.get('string')).to.equal('String');
            expect(newModel.get('string2')).to.equal('String 2');
            expect(newModel.get('number')).to.equal(1337);
        });

        it('passes fetch calls to success', function(done) {
            const mySavedModel = new SavedModel({'id': 10});
            mySavedModel.save();
            mySavedModel.fetch({
                success(model, response, options) {
                    expect(model).to.equal(mySavedModel);
                    done();
                }
            });
        });

        it('can be updated', async function () {
            const mySavedModel = new SavedModel({'id': 10});
            await new Promise((resolve, reject) => mySavedModel.save({'string': 'New String', 'number2': 1234}, {'success': resolve}));
            expect(mySavedModel.pick('string', 'number2')).to.eql({
                'string': 'New String',
                'number2': 1234
            });
        });

        it('persists its update to sessionStorage', async function () {
            const mySavedModel = new SavedModel({'id': 10});
            await new Promise((resolve, reject) => mySavedModel.save({'string': 'New String', 'number2': 1234}, {'success': resolve}));
            const item = root.sessionStorage.getItem(`localforage/SavedModel-${mySavedModel.id}`);
            expect(item).to.be.a('string');
            const parsed = JSON.parse(item);
            expect(parsed).to.deep.equal({
                id: 10,
                string: 'New String',
                string2: 'String 2',
                number: 1337,
                number2: 1234
            });
        });

        it('saves to sessionStorage with patch', async function () {
            const mySavedModel = new SavedModel({'id': 10});
            await new Promise(success => mySavedModel.save(null, {success}));
            await new Promise(success => mySavedModel.save({'string': 'New String', 'number2': 1234}, {'patch': true, success}));
            const item = root.sessionStorage.getItem(`localforage/SavedModel-${mySavedModel.id}`);
            expect(item).to.be.a('string');
            const parsed = JSON.parse(item);
            expect(parsed).to.deep.equal({
                string: 'New String',
                string2: 'String 2',
                id: 10,
                number: 1337,
                number2: 1234
            });
        });

        it('can be destroyed', async function () {
            const mySavedModel = new SavedModel({'id': 10});
            await new Promise((resolve, reject) => mySavedModel.destroy({'success': resolve}));
            const item = root.sessionStorage.getItem('localforage/SavedModel-10');
            expect(item).to.be.null;
        });
    });

    describe('with storage updated from elsewhere', function () {
        beforeEach(() => sessionStorage.clear());

        it('will re-fetch new data', async function () {
            const newModel = new SavedModel({'id': 10});
            await new Promise((resolve, reject) => newModel.save({'string': 'String'}, {'success': resolve}));
            await new Promise((resolve, reject) => newModel.fetch({'success': resolve}));
            expect(newModel.get('string')).to.equal('String');

            const mySavedModel = new SavedModel({'id': 10});
            await new Promise((resolve, reject) => mySavedModel.save({'string': 'Brand new string'}, {'success': resolve}));
            await new Promise((resolve, reject) => newModel.fetch({'success': resolve}));
            expect(newModel.get('string')).to.equal('Brand new string');
        });
    });

    describe('with a different idAttribute', function () {
        beforeEach(() => sessionStorage.clear());

        const DifferentIdAttribute = Model.extend({
            browserStorage: new Storage('DifferentId', 'session'),
            idAttribute: 'number'
        });

        it('can be saved with the new value', async function () {
            const mySavedModel = new DifferentIdAttribute(attributes);
            await new Promise((resolve, reject) => mySavedModel.save(null, {'success': resolve}));
            const item = root.sessionStorage.getItem('localforage/DifferentId-1337');
            const parsed = JSON.parse(item);

            expect(item).to.be.a('string');
            expect(parsed.string).to.be.a('string');
        });

        it('can be fetched with the new value', async function () {
            const mySavedModel = new DifferentIdAttribute(attributes);
            root.sessionStorage.setItem('localforage/DifferentId-1337', JSON.stringify(attributes));
            const newModel = new DifferentIdAttribute({'number': 1337 });
            await new Promise((resolve, reject) => newModel.fetch({'success': resolve}));
            expect(newModel.id).to.equal(1337);
            expect(newModel.get('string')).to.be.a('string');
        });
    });

    describe('New sessionStorage model', function () {
        beforeEach(() => sessionStorage.clear());

        it('creates a new item in sessionStorage', async function () {
            const mySavedModel = new SavedModel();
            await new Promise((resolve, reject) => mySavedModel.save({'data': 'value'}, {'success': resolve}));
            const item = root.sessionStorage.getItem(`localforage/SavedModel-${mySavedModel.id}`);
            const parsed = JSON.parse(item);
            expect(parsed).to.eql(mySavedModel.attributes);
        });
    });
});

describe('browserStorage Collection using sessionStorage', function () {
    beforeEach(() => sessionStorage.clear());

    it('saves to sessionStorage', function () {
        const mySavedCollection = new SavedCollection();
        mySavedCollection.create(attributes);
        expect(mySavedCollection.length).to.equal(1);
    });

    it('saves to sessionStorage when wait=true', async function () {
        const mySavedCollection = new SavedCollection();
        await new Promise(success => mySavedCollection.create(attributes, {'wait': true, success}));
        expect(mySavedCollection.length).to.equal(1);
        const attrs2 = { string: 'String' };
        await new Promise(success => mySavedCollection.create(attrs2, {'wait': true, success}));
        expect(mySavedCollection.length).to.equal(2);
        await new Promise(success => mySavedCollection.fetch({success}));
        expect(mySavedCollection.length).to.equal(2);

    });

    it('cannot duplicate id in sessionStorage', async function () {
        const item = clone(attributes);
        item.id = 5;
        const newCollection = new SavedCollection([item]);
        await new Promise((resolve, reject) => newCollection.create(item, {'success': resolve}));
        await new Promise((resolve, reject) => newCollection.create(item, {'success': resolve}));
        const localItem = root.sessionStorage.getItem('localforage/SavedCollection-5');
        expect(newCollection.length).to.equal(1);
        expect(JSON.parse(localItem).id).to.equal(5);
    });


    describe('pulling from sessionStorage', function () {
        beforeEach(() => sessionStorage.clear());

        it('saves into the sessionStorage', async function () {
            const mySavedCollection = new SavedCollection();
            const model = await new Promise((resolve, reject) => mySavedCollection.create(attributes, {'success': resolve}));
            const item = root.sessionStorage.getItem(`localforage/SavedCollection-${model.id}`);
            expect(item).to.be.a('string');
        });

        it('saves the right data', async function () {
            const mySavedCollection = new SavedCollection();
            const model = await new Promise((resolve, reject) => mySavedCollection.create(attributes, {'success': resolve}));
            const item = root.sessionStorage.getItem(`localforage/SavedCollection-${model.id}`);
            const parsed = JSON.parse(item);
            expect(parsed.id).to.equal(model.id);
            expect(parsed.string).to.equal('String');
        });

        it('reads from sessionStorage', async function () {
            const mySavedCollection = new SavedCollection();
            let model = await new Promise((resolve, reject) => mySavedCollection.create(attributes, {'success': resolve}));
            const newCollection = new SavedCollection();
            model = await new Promise((resolve, reject) => newCollection.fetch({'success': resolve}));
            expect(newCollection.length).to.equal(1);
            const newModel = newCollection.at(0);
            expect(newModel.get('string')).to.equal('String');
        });

        it('destroys models and removes from collection', async function () {
            const mySavedCollection = new SavedCollection();
            const model = await new Promise((resolve, reject) => mySavedCollection.create(attributes, {'success': resolve}));
            const item = root.sessionStorage.getItem(`localforage/SavedCollection-${model.id}`);
            const parsed = JSON.parse(item);
            const newModel = mySavedCollection.get(parsed.id);
            await new Promise((resolve, reject) => newModel.destroy({'success': resolve}));
            const removed = root.sessionStorage.getItem(`localforage/SavedCollection-${parsed.id}`);
            expect(removed).to.be.null;
            expect(mySavedCollection.length).to.equal(0);
        });
    });

    describe('will fetch from sessionStorage if updated separately', function () {
        beforeEach(() => sessionStorage.clear());

        it('fetches the items from the original collection', async function () {
            const mySavedCollection = new SavedCollection();
            await new Promise((resolve, reject) => mySavedCollection.create(attributes, {'success': resolve}));
            const newCollection = new SavedCollection();
            await new Promise((resolve, reject) => newCollection.fetch({'success': resolve}));
            expect(newCollection.length).to.equal(1);
        });

        it('will update future changes', async function () {
            const mySavedCollection = new SavedCollection();
            const newAttributes = clone(attributes);
            mySavedCollection.create(newAttributes);
            await new Promise((resolve, reject) => mySavedCollection.create(newAttributes, {'success': resolve}));

            const newCollection = new SavedCollection();
            await new Promise((resolve, reject) => newCollection.fetch({'success': resolve}));
            expect(newCollection.length).to.equal(2);
        });
    });
});