'use strict';

const expect = require('expect.js');
const http = require('http');
const express = require('express');
const linkCheck = require('../');

describe('link-check', function () {

    this.timeout(2500);//increase timeout to enable 429 retry tests

    let baseUrl;
    let laterCustomRetryCounter;

    before(function (done) {
        const app = express();

        app.head('/nohead', function (req, res) {
            res.sendStatus(405); // method not allowed
        });
        app.get('/nohead', function (req, res) {
            res.sendStatus(200);
        });

        app.get('/foo/redirect', function (req, res) {
            res.redirect('/foo/bar');
        });
        app.get('/foo/bar', function (req, res) {
            res.json({foo:'bar'});
        });

        app.get('/loop', function (req, res) {
            res.redirect('/loop');
        });

        app.get('/hang', function (req, res) {
            // no reply
        });

        app.get('/notfound', function (req, res) {
            res.sendStatus(404);
        });

        app.get('/basic-auth', function (req, res) {

            if (req.headers["authorization"] === "Basic Zm9vOmJhcg==") {
                return res.sendStatus(200);
            }
            res.sendStatus(401);
        });

        // prevent first header try to be a hit
        app.head('/later-custom-retry-count', function (req, res) {
            res.sendStatus(405); // method not allowed
        });
        app.get('/later-custom-retry-count', function (req, res) {
            laterCustomRetryCounter++;

            if(laterCustomRetryCounter === parseInt(req.query.successNumber)) {
                res.sendStatus(200);
            }else{
              res.setHeader('retry-after', 1);
              res.sendStatus(429);
            }
        });

        // prevent first header try to be a hit
        app.head('/later-standard-header', function (req, res) {
            res.sendStatus(405); // method not allowed
        });
        var stdRetried = false;
        var stdFirstTry = 0;
        app.get('/later', function (req, res) {
            var isRetryDelayExpired = stdFirstTry + 1000 < Date.now();
            if(!stdRetried || !isRetryDelayExpired){
              stdFirstTry = Date.now();
              stdRetried = true;
              res.setHeader('retry-after', 1);
              res.sendStatus(429);
            }else{
              res.sendStatus(200);
            }
        });

        // prevent first header try to be a hit
        app.head('/later-no-header', function (req, res) {
            res.sendStatus(405); // method not allowed
        });
        var stdNoHeadRetried = false;
        var stdNoHeadFirstTry = 0;
        app.get('/later-no-header', function (req, res) {
            var minTime = stdNoHeadFirstTry + 1000;
            var maxTime = minTime + 100;
            var now = Date.now();
            var isRetryDelayExpired = minTime < now && now < maxTime;
            if(!stdNoHeadRetried || !isRetryDelayExpired){
              stdNoHeadFirstTry = Date.now();
              stdNoHeadRetried = true;
              res.sendStatus(429);
            }else{
              res.sendStatus(200);
            }
        });

        // prevent first header try to be a hit
        app.head('/later-non-standard-header', function (req, res) {
            res.sendStatus(405); // method not allowed
        });
        var nonStdRetried = false;
        var nonStdFirstTry = 0;
        app.get('/later-non-standard-header', function (req, res) {
            var isRetryDelayExpired = nonStdFirstTry + 1000 < Date.now();
            if(!nonStdRetried || !isRetryDelayExpired){
              nonStdFirstTry = Date.now();
              nonStdRetried = true;
              res.setHeader('retry-after', '1s');
              res.sendStatus(429);
            }else {
              res.sendStatus(200);
            }
        });

        app.get(encodeURI('/url_with_unicode–'), function (req, res) {
            res.sendStatus(200);
        });

        app.get('/url_with_special_chars\\(\\)\\+', function (req, res) {
            res.sendStatus(200);
        });

        const server = http.createServer(app);
        server.listen(0 /* random open port */, 'localhost', function serverListen(err) {
            if (err) {
                done(err);
                return;
            }
            baseUrl = 'http://' + server.address().address + ':' + server.address().port;
            done();
        });
    });

    it('should find that a valid link is alive', function (done) {
        linkCheck(baseUrl + '/foo/bar', function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be(baseUrl + '/foo/bar');
            expect(result.status).to.be('alive');
            expect(result.statusCode).to.be(200);
            expect(result.err).to.be(null);
            done();
        });
    });

    it('should find that a valid external link with basic authentication is alive', function (done) {
        linkCheck(baseUrl + '/basic-auth', {
            headers: {
                'Authorization': 'Basic Zm9vOmJhcg=='
            },
        }, function (err, result) {
            expect(err).to.be(null);
            expect(result.status).to.be('alive');
            expect(result.statusCode).to.be(200);
            expect(result.err).to.be(null);
            done();
        });
    });

    it('should find that a valid relative link is alive', function (done) {
        linkCheck('/foo/bar', { baseUrl: baseUrl }, function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be('/foo/bar');
            expect(result.status).to.be('alive');
            expect(result.statusCode).to.be(200);
            expect(result.err).to.be(null);
            done();
        });
    });

    it('should find that an invalid link is dead', function (done) {
        linkCheck(baseUrl + '/foo/dead', function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be(baseUrl + '/foo/dead');
            expect(result.status).to.be('dead');
            expect(result.statusCode).to.be(404);
            expect(result.err).to.be(null);
            done();
        });
    });

    it('should find that an invalid relative link is dead', function (done) {
        linkCheck('/foo/dead', { baseUrl: baseUrl }, function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be('/foo/dead');
            expect(result.status).to.be('dead');
            expect(result.statusCode).to.be(404);
            expect(result.err).to.be(null);
            done();
        });
    });

    it('should report no DNS entry as a dead link (http)', function (done) {
        linkCheck('http://example.example.example.com/', function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be('http://example.example.example.com/');
            expect(result.status).to.be('dead');
            expect(result.statusCode).to.be(0);
            expect(result.err.code).to.be('ENOTFOUND');
            done();
        });
    });

    it('should report no DNS entry as a dead link (https)', function (done) {
        const badLink = 'https://githuuuub.com/tcort/link-check';
        linkCheck(badLink, function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be(badLink);
            expect(result.status).to.be('dead');
            expect(result.statusCode).to.be(0);
            expect(result.err.code).to.contain('ENOTFOUND');
            done();
        });
    });

    it('should timeout if there is no response', function (done) {
        linkCheck(baseUrl + '/hang', { timeout: '100ms' }, function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be(baseUrl + '/hang');
            expect(result.status).to.be('dead');
            expect(result.statusCode).to.be(0);
            expect(result.err.code).to.be('ECONNRESET');
            done();
        });
    });

    it('should try GET if HEAD fails', function (done) {
        linkCheck(baseUrl + '/nohead', function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be(baseUrl + '/nohead');
            expect(result.status).to.be('alive');
            expect(result.statusCode).to.be(200);
            expect(result.err).to.be(null);
            done();
        });
    });

    it('should handle redirects', function (done) {
        linkCheck(baseUrl + '/foo/redirect', function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be(baseUrl + '/foo/redirect');
            expect(result.status).to.be('alive');
            expect(result.statusCode).to.be(200);
            expect(result.err).to.be(null);
            done();
        });
    });

    it('should handle valid mailto', function (done) {
        linkCheck('mailto:linuxgeek@gmail.com', function (err, result) {
            expect(err).to.be(null);
            expect(result.link).to.be('mailto:linuxgeek@gmail.com');
            expect(result.status).to.be('alive');
            done();
        });
    });

    it('should handle