import MockPagedDataProvider from 'mocks/data-provider/paged';
QUnit.module('bitbucket-paged-data-provider');

class TestableMockPagedDataProvider extends MockPagedDataProvider {
    constructor(...args) {
        super(...args);
        this._reachedEndSpy = sinon.spy();
        this._fetchSpy = sinon.spy();
        this._fetchNextSpy = sinon.spy();
    }

    _reachedEnd(...args) {
        this._reachedEndSpy(...args);
        return super._reachedEnd(...args);
    }

    _fetch(...args) {
        this._fetchSpy(...args);
        return super._fetch(...args);
    }

    _fetchNext(...args) {
        this._fetchNextSpy(...args);
        return super._fetchNext(...args);
    }
}

QUnit.test('SPI test - abstract methods get called', function(assert) {
    let reachedEnd;
    const mock = new TestableMockPagedDataProvider({
        nextUrl: 'http://example.com',
        pages: 2,
        itemsPerPage: 10,
    });
    assert.equal(mock._reachedEndSpy.callCount, 0, '_reachedEnd not called in ctor');
    assert.equal(mock._fetchSpy.callCount, 0, '_fetch not called in ctor');
    assert.equal(mock._fetchNextSpy.callCount, 0, '_fetchNext not called in ctor');

    reachedEnd = mock.reachedEnd;
    assert.equal(mock._reachedEndSpy.callCount, 0, '_reachedEnd not called - we know the answer to this before any requests have been made');
    assert.equal(mock._fetchNextSpy.callCount, 0, '_fetchNext not called through getters');
    assert.equal(mock._fetchSpy.callCount, 0, '_fetch not called through getters');

    const p = mock.fetchNext();
    assert.equal(mock._fetchNextSpy.callCount, 1, '_fetchNext called through fetchNext');
    assert.equal(mock._fetchSpy.callCount, 1, '_fetch called through fetchNext');
    assert.strictEqual(mock._fetchNextSpy.args[0][0], null, '_fetchNext called with null');
    assert.strictEqual(mock._fetchSpy.args[0][0], 'http://example.com', '_fetch called with default url');

    const mockResponse = {
        isLastPage: true,
        values: [1],
        nextUrl: 'blam',
    };
    mock._currentDeferred.resolve(mockResponse);
    assert.strictEqual(p.state(), 'resolved');
    let val;
    p.then(v => {
        val = v;
    });
    assert.strictEqual(val, mockResponse.values, 'The fetch resolves to the post-transform object.');

    reachedEnd = mock.reachedEnd;
    assert.strictEqual(reachedEnd, true, 'reachedEnd is correctly pulled form the pre-transform object');
    assert.equal(mock._reachedEndSpy.callCount, 1, '_reachedEnd called');
    assert.strictEqual(mock._reachedEndSpy.args[0][0], mockResponse, '_reachedEnd called with pre-transform object');
});

QUnit.test('Mock test - Lifecycle with a reset', function(assert) {
    let p;
    const mock = new TestableMockPagedDataProvider({
        nextUrl: 'http://example.com',
        pages: 2,
        itemsPerPage: 10,
    });
    assert.strictEqual(mock.reachedEnd, false, 'Before fetching, have not reachedEnd');

    p = mock.fetchNext();
    mock._currentDeferred.resolve();
    assert.strictEqual(mock.reachedEnd, false, 'After first page, have not reachedEnd');
    assert.strictEqual(p.state(), 'resolved');

    p = mock.fetchNext();
    mock._currentDeferred.resolve();
    assert.strictEqual(mock.reachedEnd, true, 'After second page, have reachedEnd.');
    assert.strictEqual(p.state(), 'resolved');
    assert.throws(() => mock.fetchNext(), 'Calling fetchNext() after reaching the end throws an error.');

    mock.reset();
    assert.strictEqual(mock.reachedEnd, false, 'After reset, have not reachedEnd');

    p = mock.fetchNext();
    mock._currentDeferred.resolve();
    assert.strictEqual(mock.reachedEnd, false, 'After first page, have not reachedEnd');
    assert.strictEqual(p.state(), 'resolved');

    p = mock.fetchNext();
    mock._currentDeferred.resolve();
    assert.strictEqual(mock.reachedEnd, true, 'After second page, have reachedEnd.');
    assert.strictEqual(p.state(), 'resolved');
    assert.throws(() => mock.fetchNext(), 'Calling fetchNext() after reaching the end throws an error.');
});

QUnit.test('Mock test - Lifecycle with a rejection', function(assert) {
    let p;
    const mock = new TestableMockPagedDataProvider({
        nextUrl: 'http://example.com',
        pages: 2,
        itemsPerPage: 10,
    });
    assert.strictEqual(mock.reachedEnd, false, 'Before fetching, have not reachedEnd');

    p = mock.fetchNext();
    mock._currentDeferred.resolve();
    assert.strictEqual(mock.reachedEnd, false, 'After first page, have not reachedEnd');
    assert.strictEqual(p.state(), 'resolved');

    p = mock.fetchNext();
    mock._currentDeferred.reject();
    assert.strictEqual(mock.reachedEnd, false, 'After failed second page, have not reachedEnd');
    assert.strictEqual(p.state(), 'rejected');

    p = mock.fetchNext();
    mock._currentDeferred.resolve();
    assert.strictEqual(mock.reachedEnd, true, 'After second page, have reachedEnd.');
    assert.strictEqual(p.state(), 'resolved');
    assert.throws(() => mock.fetchNext(), 'Calling fetchNext() after reaching the end throws an error.');
});

QUnit.test('setFilter', function(assert) {
    assert.throws(function() {
        new MockPagedDataProvider({ // eslint-disable-line no-new
            filter: {
                shouldThrow: 'blah',
            },
        });
    }, 'throws when filter provided to unfilterable in ctor');

    const mock = new MockPagedDataProvider({});
    assert.throws(function() {
        mock.setFilter('shouldThrow', 'blah');
    }, 'throws when filter provided to unfilterable');

    class FilteredPagedDataProvider extends MockPagedDataProvider {
        constructor(options, ...args) {
            options.filterDescriptor = {
                foo: 'number?',
                bar: 'number?',
            };
            super(options, ...args);
        }
    }
    const filterable = new FilteredPagedDataProvider({});

    filterable.setFilter('foo', 123);
    assert.equal(filterable.filter.foo, 123, 'sets a key to a given value.');
    filterable.setFilter('bar', 456);
    assert.equal(filterable.filter.foo, 123, 'leaves other values alone when setting a key.');
    filterable.setFilter('foo', filterable.filter.bar);
    assert.equal(filterable.filter.foo, filterable.filter.bar, 'updates a key with new values.');

    assert.throws(function() {
        filterable.setFilter('foo', 'string');
    }, 'throws on invalid props');
});
