'use strict';

var adapters = [
  ['local', 'http'],
  ['http', 'http'],
  ['http', 'local'],
  ['local', 'local']
];

adapters.forEach(function (adapters) {
  var title = 'test.replication_events.js-' + adapters[0] + '-' + adapters[1];
  describe('suite2 ' + title, function () {

    var dbs = {};

    beforeEach(function () {
      dbs.name = testUtils.adapterUrl(adapters[0], 'testdb');
      dbs.remote = testUtils.adapterUrl(adapters[1], 'test_repl_remote');
    });

    afterEach(function (done) {
      testUtils.cleanup([dbs.name, dbs.remote], done);
    });

    it('#3852 Test basic starting empty', function (done) {

      var db = new PouchDB(dbs.name);
      var repl = db.replicate.to(dbs.remote, {retry: true, live: true});
      var counter = 0;

      repl.on('complete', function () { done(); });

      repl.on('active', function () {
        counter++;
        if (!(counter === 2 || counter === 4)) {
          done('active fired incorrectly');
        }
      });

      repl.on('paused', function () {
        counter++;
        // We should receive a paused event when replication
        // starts because there is nothing to replicate
        if (counter === 1) {
          db.bulkDocs([{_id: 'a'}, {_id: 'b'}]);
        } else if (counter === 3) {
          db.bulkDocs([{_id: 'c'}, {_id: 'd'}]);
        } else if (counter === 5) {
          repl.cancel();
        } else {
          done('paused fired incorrectly');
        }
      });
    });


    it('#3852 Test basic starting with docs', function (done) {

      var db = new PouchDB(dbs.name);

      db.bulkDocs([{_id: 'a'}, {_id: 'b'}]).then(function () {

        var repl = db.replicate.to(dbs.remote, {retry: true, live: true});

        var counter = 0;

        repl.on('complete', function () { done(); });

        repl.on('active', function () {
          counter++;
          if (!(counter === 1 || counter === 3 || counter === 5)) {
            done('active fired incorrectly:' + counter);
          }
        });

        repl.on('paused', function () {
          counter++;
          // We should receive a paused event when replication
          // starts because there is nothing to replicate
          if (counter === 2) {
            db.bulkDocs([{_id: 'c'}, {_id: 'd'}]);
          } else if (counter === 4) {
            db.bulkDocs([{_id: 'e'}, {_id: 'f'}]);
          } else if (counter === 6) {
            repl.cancel();
          } else {
            done('paused fired incorrectly');
          }
        });
      });
    });

    it('#5710 Test pending property support', function (done) {

      var db = new PouchDB(dbs.name);
      var remote = new PouchDB(dbs.remote);
      var docId = 0;
      var numDocs = 10;

      function generateDocs(n) {
        return Array.apply(null, new Array(n)).map(function () {
          docId += 1;
          return {
            _id: docId.toString(),
            foo: Math.random().toString()
          };
        });
      }
      remote.bulkDocs(generateDocs(numDocs)).then(function () {
        var repl = db.replicate.from(dbs.remote, { retry: true, live: false, batch_size: 4 });
        var pendingSum = 0;

        repl.on('change', function (info) {
          if (typeof info.pending === 'number') {
            pendingSum += info.pending;
            if (info.pending === 0) {
              pendingSum += info.docs.length;
            }
          }
        });

        repl.on('complete', function () {
          if (pendingSum > 0) {
            pendingSum.should.equal(numDocs);
          }
          done();
        });
      });
    });

    it('#3852 Test errors', function (done) {

      if (!(/http/.test(dbs.remote) && !/http/.test(dbs.name))) {
        // Only run test when remote is http and local is local
        return done();
      }

      var db = new PouchDB(dbs.name);
      var remote = new PouchDB(dbs.remote, {
        fetch: function (url, opts) {
          if (rejectAjax) {
            throw new Error('flunking you');
          } else {
            return PouchDB.fetch(url, opts);
          }
        }
      });
      var rejectAjax = true;

      db.bulkDocs([{_id: 'a'}, {_id: 'b'}]).then(function () {

        var repl = db.replicate.to(remote, {
          retry: true,
          live: true,
          back_off_function: function () { return 0; }
        });

        var counter = 0;

        repl.on('complete', function () {
          done();
        });

        repl.on('active', function () {
          counter++;
          if (counter === 2) {
            // All good, wait for pause
          } else if (counter === 4) {
            // Lets start failing while active
            rejectAjax = true;
            db.bulkDocs([{_id: 'e'}, {_id: 'f'}]);
          } else if (counter === 6) {
            // All good, wait for pause
          } else {
            done('active fired incorrectly');
          }
        });

        repl.on('paused', function (err) {
          counter++;
          // Replication starts with a paused(err) because ajax is
          // failing
          if (counter === 1) {
            should.exist(err);
            // Lets let the repliation start
            rejectAjax = false;
          } else if (counter === 3) {
            db.bulkDocs([{_id: 'c'}, {_id: 'd'}]);
          } else if (counter === 5) {
            // We started failing while active, should have an error
            // then we stop rejecting and should become active again
            should.exist(err);
            rejectAjax = false;
          } else if (counter === 7) {
            repl.cancel();
          } else {
            done('paused fired incorrectly');
          }
        });
      }).catch(done);
    });


    // this test sets up a 2 way replication which initially transfers
    // documents from a remote to a local database.
    // At the same time, we insert documents locally - the changes
    // should propagate to the remote database and then back to the
    // local database via the live replications.
    // Previously, this test resulted in 'change' events being
    // generated for already-replicated documents. When PouchDB is working
    // as expected, each remote document should be passed to a
    // change event exactly once (though a change might contain multiple docs)
    it('#4627 Test no duplicate changes in live replication', function (done) {
      var db = new PouchDB(dbs.name);
      var remote = new PouchDB(dbs.remote);
      var docId = -1;
      var docsToGenerate = 10;
      var lastChange = -1;
      var firstReplication;
      var secondReplication;
      var completeCalls = 0;

      function generateDocs(n) {
        return Array.apply(null, new Array(n)).map(function () {
          docId += 1;
          return {
            _id: docId.toString(),
            foo: Math.random().toString()
          };
        });
      }

      function complete() {
        completeCalls++;
        if (completeCalls === 2) {
          done();
        }
      }

      remote.bulkDocs(generateDocs(docsToGenerate)).then(function () {
        firstReplication = db.replicate.to(remote, {
          live: true,
          retry: true,
          since: 0
        })
        .on('error', done)
        .on('complete', complete);

        secondReplication = remote.replicate.to(db, {
          live: true,
          retry: true,
          since: 0
        })
        .on('error', done)
        .on('complete', complete)
        .on('change', function (feed) {
          // attempt to detect changes loop
          var ids = feed.docs.map(function (d) {
            return parseInt(d._id, 10);
          }).sort();

          var firstChange = ids[0];
          if (firstChange <= lastChange) {
            done(new Error("Duplicate change events detected"));
          }

          lastChange = ids[ids.length - 1];

          if (lastChange === docsToGenerate - 1) {
            // if a change loop doesn't occur within 2 seconds, assume success
            setTimeout(function () {
              // success!
              // cancelling the replications to clean up and trigger
              // the 'complete' event, which in turn ends the test
              firstReplication.cancel();
              secondReplication.cancel();
            }, 2000);
          }

          // write doc to local db - should round trip in _changes
          // but not generate a change event
          db.bulkDocs(generateDocs(1));
        });
      }).catch(done);
    });

    describe('#5172 triggering error when replicating', replication401TestFunction('unauthorized'));

    function replication401TestFunction(eventName) {
      return function () {
        var securedDbs = [], source, dest;
        beforeEach(function () {
          var err = {
            'status': 401,
            'name': eventName,
            'message': 'You are not authorized to access this db.'
          };

          if (adapters[0] === 'http') {
            source = new PouchDB(dbs.name, {
              fetch: function () {
                throw err;
              }
            });
            dest = new PouchDB(dbs.remote);
            securedDbs.push(source);
          }

          if (adapters[1] === 'http') {
            source = new PouchDB(dbs.name);
            dest = new PouchDB(dbs.remote, {
              fetch: function () {
                throw err;
              }
            });
            securedDbs.push(dest);
          }
        });

        function attachHandlers(replication) {
          var invokedHandlers = [];
          ['change', 'complete', 'paused', 'active', 'denied', 'error'].forEach(function (type) {
            replication.on(type, function () {
              invokedHandlers.push(type);
            });
          });
          return invokedHandlers;
        }

        it('from or to a secured database, using live replication', function () {
          if (adapters[0] === 'local' && adapters[1] === 'local') {
            return;
          }

          var replication = source.replicate.to(dest, {live: true});
          var invokedHandlers = attachHandlers(replication);

          return replication.then(function () {
            throw new Error('Resulting promise should be rejected');
          }, function () {
            invokedHandlers.should.be.eql(['error'], 'incorrect handler was invoked');
          });
        });

        it('from or to a secured database, using live replication with checkpoint', function () {
          if (adapters[0] === 'local' && adapters[1] === 'local') {
            return;
          }

          var replication = source.replicate.to(dest, {live: true, since: 1234});
          var invokedHandlers = attachHandlers(replication);

          return replication.then(function () {
            throw new Error('Resulting promise should be rejected');
          }, function () {
            invokedHandlers.should.be.eql(['error'], 'incorrect handler was invoked');
          });
        });

        it('from or to a secured database, using live replication with retrying', function () {
          if (adapters[0] === 'local' && adapters[1] === 'local') {
            return;
          }

          var replication = source.replicate.to(dest, {live: true, retry: true});
          var invokedHandlers = attachHandlers(replication);

          return replication.then(function () {
            throw new Error('Resulting promise should be rejected');
          }, function () {
            invokedHandlers.should.be.eql(['error'], 'incorrect handler was invoked');
          });
        });

        it('from or to a secured database, using one-shot replication', function () {
          if (adapters[0] === 'local' && adapters[1] === 'local') {
            return;
          }

          var replication = source.replicate.to(dest);
          var invokedHandlers = attachHandlers(replication);

          return replication.then(function () {
            throw new Error('Resulting promise should be rejected');
          }, function () {
            invokedHandlers.should.be.eql(['error'], 'incorrect handler was invoked');
          });
        });
      };
    }

    // #5607 https://github.com/pouchdb/pouchdb/issues/5607#issuecomment-346078688
    // Note: Uppercase 'Unauthorized' response
    describe('#5607 handle uppercase third-party error responses', replication401TestFunction('Unauthorized'));

    it('Test checkpoint events', async () => {
      const docs = {
        docs: [
          {_id: '0', integer: 0, string: '0'},
          {_id: '1', integer: 1, string: '1'},
          {_id: '2', integer: 2, string: '2'},
        ],
      };
      const remote = new PouchDB(dbs.remote);
      const checkpointEvents = [];

      await remote.bulkDocs(docs, {});
      const replication = PouchDB.replicate(dbs.remote, dbs.name, {});
      replication.on('checkpoint', result => checkpointEvents.push(result));
      await replication;
      String(checkpointEvents[0]['pending_batch']).slice(0, 1).should.equal('1');
      String(checkpointEvents[1]['pending_batch']).slice(0, 1).should.equal('2');
      String(checkpointEvents[2]['pending_batch']).slice(0, 1).should.equal('3');
      String(checkpointEvents[3]['start_next_batch']).slice(0, 1).should.equal('3');
      checkpointEvents[4]['revs_diff'].should.have.property('id');
      String(checkpointEvents[4]['revs_diff']['seq']).slice(0, 1).should.equal('1');
      checkpointEvents[5]['revs_diff'].should.have.property('changes');
      String(checkpointEvents[5]['revs_diff']['seq']).slice(0, 1).should.equal('2');
      checkpointEvents[6]['revs_diff'].should.have.property('changes');
      String(checkpointEvents[6]['revs_diff']['seq']).slice(0, 1).should.equal('3');
      String(checkpointEvents[7]['checkpoint']).slice(0, 1).should.equal('3');
      [
        checkpointEvents[4]['revs_diff']['id'],
        checkpointEvents[5]['revs_diff']['id'],
        checkpointEvents[6]['revs_diff']['id']
      ].should.have.members(['0', '1', '2']);
    });
  });
});
