'use strict';
const test = require('./shared').assert,
  setupDatabase = require('./shared').setupDatabase,
  expect = require('chai').expect;

describe('Connection', function() {
  before(function() {
    return setupDatabase(this.configuration);
  });

  /**
   * @ignore
   */
  it('should correctly start monitoring for single server connection', {
    metadata: { requires: { topology: 'single', os: '!win32' } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var client = configuration.newClient(
        { w: 1 },
        { poolSize: 1, host: '/tmp/mongodb-27017.sock', heartbeatFrequencyMS: 250 }
      );

      client.connect(function(err, client) {
        test.equal(null, err);

        client.topology.once('monitoring', function() {
          client.close(done);
        });
      });
    }
  });

  /**
   * @ignore
   */
  it('should correctly disable monitoring for single server connection', {
    metadata: { requires: { topology: 'single', os: '!win32' } },

    // The actual test we wish to run
    test: function(done) {
      const configuration = this.configuration;
      if (configuration.usingUnifiedTopology()) {
        // skipped for direct legacy variable inspection
        return this.skip();
      }

      var client = configuration.newClient(
        { w: 1 },
        { poolSize: 1, host: '/tmp/mongodb-27017.sock', monitoring: false }
      );

      client.connect(function(err, client) {
        test.equal(null, err);
        test.equal(false, client.topology.s.coreTopology.s.monitoring);

        client.close(done);
      });
    }
  });

  /**
   * @ignore
   */
  it('should correctly connect to server using domain socket', {
    metadata: { requires: { topology: 'single', os: '!win32' } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var client = configuration.newClient(
        { w: 1 },
        { poolSize: 1, host: '/tmp/mongodb-27017.sock' }
      );

      client.connect(function(err, client) {
        var db = client.db(configuration.db);
        test.equal(null, err);

        db.collection('domainSocketCollection0').insert({ a: 1 }, { w: 1 }, function(err) {
          test.equal(null, err);

          db.collection('domainSocketCollection0')
            .find({ a: 1 })
            .toArray(function(err, items) {
              test.equal(null, err);
              test.equal(1, items.length);

              client.close(done);
            });
        });
      });
    }
  });

  /**
   * @ignore
   */
  it('should correctly connect to server using just events', {
    metadata: {
      requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] }
    },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var client = configuration.newClient({ w: 1 }, { poolSize: 1, auto_reconnect: true });

      client.on('open', function() {
        client.close(done);
      });

      client.connect();
    }
  });

  /**
   * @ignore
   */
  it('should correctly identify parser type', {
    metadata: {
      requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] }
    },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var client = configuration.newClient({ w: 1 }, { poolSize: 1, auto_reconnect: true });

      client.connect().then(() => {
        test.equal('js', client.topology.parserType);

        client.close(done);
      });
    }
  });

  /**
   * @ignore
   */
  it('should correctly connect to server using big connection pool', {
    metadata: {
      requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] },
      ignore: { travis: true }
    },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var client = configuration.newClient({ w: 1 }, { poolSize: 2000, auto_reconnect: true });
      client.on('open', function() {
        client.close(done);
      });

      client.connect();
    }
  });

  /**
   * @ignore
   */
  it('should connect to server using domain socket with undefined port', {
    metadata: { requires: { topology: 'single', os: '!win32' } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var client = configuration.newClient(
        { w: 1 },
        { poolSize: 1, host: '/tmp/mongodb-27017.sock', port: undefined }
      );

      client.connect(function(err, client) {
        var db = client.db(configuration.db);
        test.equal(null, err);

        db.collection('domainSocketCollection1').insert({ x: 1 }, { w: 1 }, function(err) {
          test.equal(null, err);

          db.collection('domainSocketCollection1')
            .find({ x: 1 })
            .toArray(function(err, items) {
              test.equal(null, err);
              test.equal(1, items.length);

              client.close(done);
            });
        });
      });
    }
  });

  /**
   * @ignore
   */
  it('should fail to connect using non-domain socket with undefined port', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration,
        Server = configuration.require.Server,
        MongoClient = configuration.require.MongoClient;

      var error;
      try {
        var client = new MongoClient(new Server('localhost', undefined), { w: 0 });
        client.connect(function() {});
      } catch (err) {
        error = err;
      }

      test.ok(error instanceof Error);
      test.ok(/port must be specified/.test(error));
      done();
    }
  });

  /**
   * @ignore
   */
  function connectionTester(configuration, testName, callback) {
    return function(err, client) {
      var db = client.db(configuration.db);
      test.equal(err, null);

      db.collection(testName, function(err, collection) {
        test.equal(err, null);

        collection.insert({ foo: 123 }, { w: 1 }, function(err) {
          test.equal(err, null);

          db.dropDatabase(function(err, dropped) {
            test.equal(err, null);
            test.ok(dropped);
            if (callback) return callback(client);
          });
        });
      });
    };
  }

  /**
   * @ignore
   */
  it('test connect no options', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      const configuration = this.configuration;
      const client = configuration.newClient();

      client.connect(
        connectionTester(configuration, 'testConnectNoOptions', function(client) {
          client.close(done);
        })
      );
    }
  });

  /**
   * @ignore
   */
  it('test connect server options', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      const configuration = this.configuration;
      if (configuration.usingUnifiedTopology()) {
        // skipped for direct legacy variable inspection
        return this.skip();
      }

      const client = configuration.newClient(configuration.url(), {
        auto_reconnect: true,
        poolSize: 4
      });

      client.connect(
        connectionTester(configuration, 'testConnectServerOptions', function(client) {
          test.ok(client.topology.poolSize >= 1);
          test.equal(4, client.topology.s.coreTopology.s.pool.size);
          test.equal(true, client.topology.autoReconnect);
          client.close(done);
        })
      );
    }
  });

  /**
   * @ignore
   */
  it('testConnectAllOptions', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      const configuration = this.configuration;
      if (configuration.usingUnifiedTopology()) {
        // skipped for direct legacy variable inspection
        return this.skip();
      }

      const client = configuration.newClient(configuration.url(), {
        auto_reconnect: true,
        poolSize: 4,
        native_parser: process.env['TEST_NATIVE'] != null
      });

      client.connect(
        connectionTester(configuration, 'testConnectAllOptions', function(client) {
          test.ok(client.topology.poolSize >= 1);
          test.equal(4, client.topology.s.coreTopology.s.pool.size);
          test.equal(true, client.topology.autoReconnect);
          client.close(done);
        })
      );
    }
  });

  /**
   * @ignore
   */
  it('test connect good auth', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var user = 'testConnectGoodAuth',
        password = 'password';

      const setupClient = configuration.newClient();

      // First add a user.
      setupClient.connect(function(err, client) {
        test.equal(err, null);
        var db = client.db(configuration.db);

        db.addUser(user, password, function(err) {
          test.equal(err, null);
          client.close(restOfTest);
        });
      });

      function restOfTest() {
        const testClient = configuration.newClient(configuration.url(user, password));
        testClient.connect(
          connectionTester(configuration, 'testConnectGoodAuth', function(client) {
            client.close(done);
          })
        );
      }
    }
  });

  /**
   * @ignore
   */
  it('test connect good auth as option', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var user = 'testConnectGoodAuthAsOption',
        password = 'password';

      // First add a user.
      const setupClient = configuration.newClient();
      setupClient.connect(function(err, client) {
        test.equal(err, null);
        var db = client.db(configuration.db);

        db.addUser(user, password, function(err) {
          test.equal(err, null);
          client.close(restOfTest);
        });
      });

      function restOfTest() {
        var opts = { auth: { user: user, password: password } };

        const testClient = configuration.newClient(
          configuration.url('baduser', 'badpassword'),
          opts
        );

        testClient.connect(
          connectionTester(configuration, 'testConnectGoodAuthAsOption', function(client) {
            client.close(done);
          })
        );
      }
    }
  });

  /**
   * @ignore
   */
  it('test connect bad auth', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      const client = configuration.newClient(configuration.url('slithy', 'toves'));
      client.connect((err, client) => {
        expect(err).to.exist;
        expect(client).to.not.exist;
        done();
      });
    }
  });

  /**
   * @ignore
   */
  it('test connect bad url', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      const configuration = this.configuration;
      const client = configuration.newClient('mangodb://localhost:27017/test?safe=false');

      test.throws(function() {
        client.connect(function() {
          test.ok(false, 'Bad URL!');
        });
      });

      done();
    }
  });

  /**
   * @ignore
   */
  it('should correctly return false on `isConnected` before connection happened', {
    metadata: { requires: { topology: 'single' } },

    // The actual test we wish to run
    test: function(done) {
      var configuration = this.configuration;
      var client = configuration.newClient({ w: 1 }, { poolSize: 1, auto_reconnect: false });
      test.equal(false, client.isConnected());
      done();
    }
  });

  /**
   * @ignore
   */
  it('should correctly reconnect and finish query operation', {
    metadata: { requires: { topology: 'single', unifiedTopology: false } },

    // The actual test we wish to run
    test: function(done) {
      const configuration = this.configuration;
      if (configuration.usingUnifiedTopology()) {
        // The unified topology deprecates autoReconnect, this test depends on the `reconnect` event
        return this.skip();
      }

      var client = configuration.newClient({ w: 1 }, { poolSize: 1, auto_reconnect: true });
      client.connect(function(err, client) {
        var db = client.db(configuration.db);
        test.equal(null, err);

        db.collection('test_reconnect').insert({ a: 1 }, function(err) {
          test.equal(null, err);
          // Signal db reconnect
          var dbReconnect = 0;
          var dbClose = 0;

          db.on('reconnect', function() {
            ++dbReconnect;
          });

          db.on('close', function() {
            ++dbClose;
          });

          client.topology.once('reconnect', function() {
            // Await reconnect and re-authentication
            db.collection('test_reconnect').findOne(function(err, doc) {
              test.equal(null, err);
              test.equal(1, doc.a);
              test.equal(1, dbReconnect);
              test.equal(1, dbClose);

              // Attempt disconnect again
              client.topology.connections()[0].destroy();

              // Await reconnect and re-authentication
              db.collection('test_reconnect').findOne(function(err, doc) {
                test.equal(null, err);
                test.equal(1, doc.a);
                test.equal(2, dbReconnect);
                test.equal(2, dbClose);

                client.close(done);
              });
            });
          });

          // Force close
          client.topology.connections()[0].destroy();
        });
      });
    }
  });
});
