var helpers = require('./helpers'),
    should  = require('should'),
    sinon   = require('sinon'),
    needle  = require('./../');

var ports = {
  http  : 8888,
  https : 9999
}

var protocols = {
  http  : require('http'),
  https : require('https')
}

var code = 301;
var location; // var to set the response location

function response_code() {
  return code;
}

function response_headers() {
  return { 'Content-Type': 'text/plain', 'Location': location }
}

describe('redirects', function() {

  var spies   = {},
      servers = {};

  var current_protocol;
  var hostname = require('os').hostname();

  // open two servers, one that responds to a redirect
  before(function(done) {

    var conf = {
      port    : ports.http,
      code    : response_code,
      headers : response_headers
    }

    servers.http = helpers.server(conf, function() {
      conf.port = ports.https;
      conf.protocol = 'https';
      servers.https = helpers.server(conf, done);
    });
  })

  after(function(done) {
    servers.http.close(function() {
      servers.https.close(done);
    });
  })

  var prots = {'http': 'https'};
  Object.keys(prots).forEach(function(protocol) {

    current_protocol = protocol;
    var other_protocol = protocol == 'http' ? 'https' : 'http';

    var opts, // each test will modify this
        host = '127.0.0.1',
        url  = protocol + '://' + host + ':' + ports[protocol] + '/hello';

    function send_request(opts, cb) {
      if (protocol == 'https') opts.rejectUnauthorized = false;
      // console.log(' -- sending request ' + url + ' -- redirect to ' + location);
      needle.post(url, { foo: 'bar' }, opts, cb);
    }

    function not_followed(done) {
      send_request(opts, function(err, resp) {
        resp.statusCode.should.eql(301);
        if (current_protocol == 'http') {
          spies.http.callCount.should.eql(1); // only original request
          spies.https.callCount.should.eql(0);
        } else {
          spies.http.callCount.should.eql(0);
          spies.https.callCount.should.eql(1); // only original request
        }
        done();
      })
    }

    function followed_same_protocol(done) {
      send_request(opts, function(err, resp) {
        // the original request plus the redirect one
        spies[current_protocol].callCount.should.eql(2);
        done();
      })
    }

    function followed_other_protocol(done) {
      send_request(opts, function(err, resp) {
        // on new-ish node versions, https.request calls http.request internally,
        // so we need to amount for that additional call.
        // update: this doesn't happen on node > 10.x

        var node_major_ver = process.version.split('.')[0].replace('v', '');
        var http_calls = protocols.http.Agent.defaultMaxSockets == Infinity && parseInt(node_major_ver) < 10 ? 2 : 1;

        spies.http.callCount.should.eql(http_calls); // the one(s) from http.request
        spies.https.callCount.should.eql(1); // the one from https.request (redirect)
        done();
      })
    }

    // set a spy on [protocol].request
    // so we can see how many times a request was made
    before(function() {
      spies.http  = sinon.spy(protocols.http, 'request');
      spies.https = sinon.spy(protocols.https, 'request');
    })

    // and make sure it is restored after each test
    afterEach(function() {
      spies.http.reset();
      spies.https.reset();
    })

    after(function() {
      spies.http.restore();
      spies.https.restore();
    })

    describe('when overriding defaults', function() {

      before(function() {
        needle.defaults({ follow_max: 10 });
        opts = {};
      })

      after(function() {
        // reset values to previous
        needle.defaults({ follow_max: 0 });
      })

      describe('and redirected to the same path on same host and protocol', function() {
        before(function() {
          location = url;
        })
        it('does not follow redirect', not_followed);
      })

      describe('and redirected to the same path on same host and different protocol', function() {
        before(function() {
          location = url.replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
        })

        it('follows redirect', followed_other_protocol);
      })

      describe('and redirected to a different path on same host, same protocol', function() {
        before(function() {
          location = url.replace('/hello', '/goodbye');
        })
        it('follows redirect', followed_same_protocol);
      })

      describe('and redirected to a different path on same host, different protocol', function() {
        before(function() {
          location = url.replace('/hello', '/goodbye').replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
        })
        it('follows redirect', followed_other_protocol);
      })

      describe('and redirected to same path on another host, same protocol', function() {
        before(function() {
          location = url.replace(host, hostname);
        })
        it('follows redirect', followed_same_protocol);
      })

      describe('and redirected to same path on another host, different protocol', function() {
        before(function() {
          location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
        })
        it('follows redirect', followed_other_protocol);
      })

    })

    // false and null have the same result
    var values = [false, null];
    values.forEach(function(value) {

      describe('when follow is ' + value, function() {

        before(function() {
          opts = { follow: value };
        })

        describe('and redirected to the same path on same host and protocol', function() {
          before(function() {
            location = url;
          })

          it('throws an error', function() {
            (function() {
              send_request(opts, function() { });
            }).should.throw;
          })

        })

      })

    })

    describe('when follow is true', function() {

      before(function() {
        opts = { follow: true };
      })

      describe('and redirected to the same path on same host and protocol', function() {
        before(function() { location = url })

        it('throws an error', function() {
          (function() {
            send_request(opts, function() { });
          }).should.throw;
        })

      })

    })

    describe('when follow is > 0', function() {

      before(function() {
        needle.defaults({ follow: 10 });
      })

      after(function() {
        needle.defaults({ follow: 0 });
      })

      describe('when keep_method is false', function() {

        before(function() {
          opts = { follow_keep_method: false };
        })

        // defaults to follow host and protocol
        describe('and redirected to the same path on same host and different protocol', function() {

          before(function() {
            location = url.replace(protocol, other_protocol);
          })

          it('follows redirect', followed_other_protocol);

          it('sends a GET request with no data', function(done) {
            send_request(opts, function(err, resp) {
              // spy.args[0][3].should.eql(null);
              spies.http.args[0][0].method.should.eql('GET');
              done();
            })
          })

          it('does not resend cookies if follow_set_cookies is false', function(done) {
            opts.cookies = {foo: 'bar'};
            opts.follow_set_cookies = false;
            send_request(opts, function(err, resp) {
              should.not.exist(spies.http.args[0][0].headers['cookie']);
              done();
            })
          })

          it('resends cookies if follow_set_cookies is true', function(done) {
            opts.cookies = {foo: 'bar'};
            opts.follow_set_cookies = true;
            send_request(opts, function(err, resp) {
              spies.http.args[0][0].headers['cookie'].should.eql('foo=bar')
              done();
            })
          })

        })

      })

      describe('and set_referer is true', function() {

        before(function() {
          opts = { follow_set_referer: true };
        })

        // defaults to follow host and protocol
        describe('and redirected to the same path on same host and different protocol', function() {

          before(function() {
            location = url.replace(protocol, other_protocol);
          })

          it('follows redirect', followed_other_protocol);

          it('sets Referer header when following redirect', function(done) {
            send_request(opts, function(err, resp) {
              // spies.http.args[0][3].should.eql({ foo: 'bar'});
              spies.http.args[0][0].headers['referer'].should.eql("http://" + host + ":8888/hello");
              done();
            })
          })

          it('does not resend cookies if follow_set_cookies is false', function(done) {
            opts.cookies = {foo: 'bar'};
            opts.follow_set_cookies = false;
            send_request(opts, function(err, resp) {
              should.not.exist(spies.http.args[0][0].headers['cookie']);
              done();
            })
          })

          it('resends cookies if follow_set_cookies is true', function(done) {
            opts.cookies = {foo: 'bar'};
            opts.follow_set_cookies = true;
            send_request(opts, function(err, resp) {
              spies.http.args[0][0].headers['cookie'].should.eql('foo=bar')
              done();
            })
          })

        })

      })

      describe('and keep_method is true', function() {

        before(function() {
          opts = { follow_keep_method: true };
        })

        // defaults to follow host and protocol
        describe('and redirected to the same path on same host and different protocol', function() {

          before(function() {
            location = url.replace(protocol, other_protocol);
          })

          it('follows redirect', followed_other_protocol);

          it('sends a POST request with the original data', function(done) {
            send_request(opts, function(err, resp) {
              spies.http.args[0][0].method.should.eql('post');
              // spies.http.args[0][3].should.eql({ foo: 'bar'});
              done();
            })
          })

          it('does not resend cookies if follow_set_cookies is false', function(done) {
            opts.cookies = {foo: 'bar'};
            opts.follow_set_cookies = false;
            send_request(opts, function(err, resp) {
              should.not.exist(spies.http.args[0][0].headers['cookie']);
              done();
            })
          })

          it('resends cookies if follow_set_cookies is true', function(done) {
            opts.cookies = {foo: 'bar'};
            opts.follow_set_cookies = true;
            send_request(opts, function(err, resp) {
              spies.http.args[0][0].headers['cookie'].should.eql('foo=bar')
              done();
            })
          })

        })

      })

      describe('and if_same_host is false', function() {

        before(function() {
          opts = { follow_if_same_host: false };
        })

        // by default it will follow other protocols
        describe('and redirected to same path on another domain, same protocol', function() {
          before(function() {
            location = url.replace(host, hostname);
          })

          it('follows redirect', followed_same_protocol);

          it('does not resend cookies even if follow_set_cookies is true', function(done) {
            opts.cookies = {foo: 'bar'};
            opts.follow_set_cookies = true;
            send_request(opts, function(err, resp) {
              should.not.exist(spies.http.args[0][0].headers['cookie']);
              done();
            })
          })
        })

      })

      describe('and if_same_host is true', function() {

        before(function() {
          opts = { follow_if_same_host: true };
        })

        // by default it will follow other protocols
        describe('and redirected to same path on another domain, same protocol', function() {
          before(function() {
            location = url.replace(host, hostname);
          })

          it('does not follow redirect', not_followed);
        })

      })

      describe('and if_same_protocol is false', function() {

        before(function() {
          opts = { follow_if_same_protocol: false };
        })

        // by default it will follow other hosts
        describe('and redirected to same path on another domain, different protocol', function() {
          before(function() {
            location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
          })

          it('follows redirect', followed_other_protocol);

          it('does not resend cookies even if follow_set_cookies is true', function(done) {
            opts.cookies = {foo: 'bar'};
            opts.follow_set_cookies = true;
            send_request(opts, function(err, resp) {
              should.not.exist(spies.http.args[0][0].headers['cookie']);
              done();
            })
          })
        })

      })

      describe('and if_same_protocol is true', function() {

        before(function() {
          opts = { follow_if_same_protocol: true };
        })

        // by default it will follow other hosts
        describe('and redirected to same path on another domain, different protocol', function() {
          before(function() {
            location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
          })
          it('does not follow redirect', not_followed);
        })

      })

    })

  })

});
