require('../helper');

var assert = require('../support/assert');
var app = require(global.settings.app_root + '/app/app')();
var querystring = require('qs');
var metadataBackend = require('cartodb-redis')({
    host: global.settings.redis_host,
    port: global.settings.redis_port,
    max: global.settings.redisPool,
    idleTimeoutMillis: global.settings.redisIdleTimeoutMillis,
    reapIntervalMillis: global.settings.redisReapIntervalMillis
});
var batchFactory = require('../../batch');
var jobStatus = require('../../batch/job_status');

describe('Batch API callback templates', function () {

    function createJob(jobDefinition, callback) {
        assert.response(app, {
            url: '/api/v2/sql/job?api_key=1234',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                host: 'vizzuality.cartodb.com'
            },
            method: 'POST',
            data: querystring.stringify(jobDefinition)
        }, {
            status: 201
        }, function (res, err) {
            if (err) {
                return callback(err);
            }
            return callback(null, JSON.parse(res.body));
        });
    }

    function getJobStatus(jobId, callback) {
        assert.response(app, {
            url: '/api/v2/sql/job/' + jobId + '?api_key=1234&',
            headers: {
                host: 'vizzuality.cartodb.com'
            },
            method: 'GET'
        }, {
            status: 200
        }, function (res, err) {
            if (err) {
                return callback(err);
            }
            return callback(null, JSON.parse(res.body));
        });
    }

    function getQueryResult(query, callback) {
        assert.response(app, {
            url: '/api/v2/sql?' + querystring.stringify({q: query, api_key: 1234}),
            headers: {
                host: 'vizzuality.cartodb.com'
            },
            method: 'GET'
        }, {
            status: 200
        }, function (res, err) {
            if (err) {
                return callback(err);
            }
            return callback(null, JSON.parse(res.body));
        });
    }

    function validateExpectedResponse(actual, expected) {
        actual.query.forEach(function(actualQuery, index) {
            var expectedQuery = expected.query[index];
            assert.ok(expectedQuery);
            Object.keys(expectedQuery).forEach(function(expectedKey) {
                assert.equal(
                    actualQuery[expectedKey],
                    expectedQuery[expectedKey],
                    'Expected value for key "' + expectedKey + '" does not match: ' + actualQuery[expectedKey] + ' ==' +
                    expectedQuery[expectedKey] + ' at query index=' + index
                );
            });
            var propsToCheckDate = ['started_at', 'ended_at'];
            propsToCheckDate.forEach(function(propToCheckDate) {
                if (actualQuery.hasOwnProperty(propToCheckDate)) {
                    assert.ok(new Date(actualQuery[propToCheckDate]));
                }
            });
        });

        assert.equal(actual.onsuccess, expected.onsuccess);
        assert.equal(actual.onerror, expected.onerror);
    }

    var batch = batchFactory(metadataBackend);

    before(function () {
        batch.start();
    });

    after(function (done) {
        batch.stop();
        batch.drain(function () {
            metadataBackend.redisCmd(5, 'DEL', [ 'batch:queues:localhost' ], done);
        });
    });

    describe('should use templates for error_message and job_id onerror callback', function () {
        var jobResponse;
        before(function(done) {
            createJob({
                "query": {
                    "query": [
                        {
                            "query": "create table test_batch_errors (job_id text, error_message text)"
                        },
                        {
                            "query": "SELECT * FROM invalid_table",
                            "onerror": "INSERT INTO test_batch_errors values ('<%= job_id %>', '<%= error_message %>')"
                        }
                    ]
                }
            }, function(err, job) {
                jobResponse = job;
                return done(err);
            });
        });

        it('should keep the original templated query but use the error message', function (done) {
            var expectedQuery = {
                query: [
                    {
                        "query": "create table test_batch_errors (job_id text, error_message text)",
                        status: 'done'
                    },
                    {
                        "query": "SELECT * FROM invalid_table",
                        "onerror": "INSERT INTO test_batch_errors values ('<%= job_id %>', '<%= error_message %>')",
                        status: 'failed',
                        fallback_status: 'done'
                    }
                ]
            };

            var interval = setInterval(function () {
                getJobStatus(jobResponse.job_id, function(err, job) {
                    if (job.status === jobStatus.FAILED) {
                        clearInterval(interval);
                        validateExpectedResponse(job.query, expectedQuery);
                        getQueryResult('select * from test_batch_errors', function(err, result) {
                            if (err) {
                                return done(err);
                            }
                            assert.equal(result.rows[0].job_id, jobResponse.job_id);
                            assert.equal(result.rows[0].error_message, 'relation "invalid_table" does not exist');
                            getQueryResult('drop table test_batch_errors', done);
                        });
                    } else if (job.status === jobStatus.DONE || job.status === jobStatus.CANCELLED) {
                        clearInterval(interval);
                        done(new Error('Job ' + job.job_id + ' is ' + job.status + ', expected to be "failed"'));
                    }
                });
            }, 50);
        });
    });

    describe('should use template for job_id onsuccess callback', function () {
        var jobResponse;
        before(function(done) {
            createJob({
                "query": {
                    "query": [
                        {
                            query: "create table batch_jobs (job_id text)"
                        },
                        {
                            "query": "SELECT 1",
                            "onsuccess": "INSERT INTO batch_jobs values ('<%= job_id %>')"
                        }
                    ]
                }
            }, function(err, job) {
                jobResponse = job;
                return done(err);
            });
        });

        it('should keep the original templated query but use the job_id', function (done) {
            var expectedQuery = {
                query: [
                    {
                        query: "create table batch_jobs (job_id text)",
                        status: 'done'
                    },
                    {
                        query: "SELECT 1",
                        onsuccess: "INSERT INTO batch_jobs values ('<%= job_id %>')",
                        status: 'done',
                        fallback_status: 'done'
                    }
                ]
            };

            var interval = setInterval(function () {
                getJobStatus(jobResponse.job_id, function(err, job) {
                    if (job.status === jobStatus.DONE) {
                        clearInterval(interval);
                        validateExpectedResponse(job.query, expectedQuery);
                        getQueryResult('select * from batch_jobs', function(err, result) {
                            if (err) {
                                return done(err);
                            }
                            assert.equal(result.rows[0].job_id, jobResponse.job_id);
                            getQueryResult('drop table batch_jobs', done);
                        });
                    } else if (job.status === jobStatus.FAILED || job.status === jobStatus.CANCELLED) {
                        clearInterval(interval);
                        done(new Error('Job ' + job.job_id + ' is ' + job.status + ', expected to be "done"'));
                    }
                });
            }, 50);
        });
    });

});

@@ -2751,16 +2751,92 @@
  + index
+ + '. Full response: ' +%0A                    JSON.stringify(actual, null, 4)
 %0A       

Output actual response when it fails