set_global('$', {});

set_global('reload', {});
zrequire('reload_state');
zrequire('channel');

const default_stub_xhr = 'default-stub-xhr';

function test_with_mock_ajax(test_params) {
    const {
        xhr = default_stub_xhr,
        run_code,
        check_ajax_options,
    } = test_params;

    let ajax_called;
    let ajax_options;
    $.ajax = function (options) {
        $.ajax = undefined;
        ajax_called = true;
        ajax_options = options;

        options.simulate_success = function (data, text_status) {
            options.success(data, text_status, xhr);
        };

        options.simulate_error = function () {
            options.error(xhr);
        };

        return xhr;
    };

    run_code();
    assert(ajax_called);
    check_ajax_options(ajax_options);
}

run_test('basics', () => {
    test_with_mock_ajax({
        run_code: function () {
            channel.post({});
        },

        check_ajax_options: function (options) {
            assert.equal(options.type, 'POST');
            assert.equal(options.dataType, 'json');

            // Just make sure these don't explode.
            options.simulate_success();
            options.simulate_error();
        },
    });

    test_with_mock_ajax({
        run_code: function () {
            channel.patch({});
        },

        check_ajax_options: function (options) {
            assert.equal(options.type, 'POST');
            assert.equal(options.data.method, 'PATCH');
            assert.equal(options.dataType, 'json');

            // Just make sure these don't explode.
            options.simulate_success();
            options.simulate_error();
        },
    });

    test_with_mock_ajax({
        run_code: function () {
            channel.put({});
        },

        check_ajax_options: function (options) {
            assert.equal(options.type, 'PUT');
            assert.equal(options.dataType, 'json');

            // Just make sure these don't explode.
            options.simulate_success();
            options.simulate_error();
        },
    });

    test_with_mock_ajax({
        run_code: function () {
            channel.del({});
        },

        check_ajax_options: function (options) {
            assert.equal(options.type, 'DELETE');
            assert.equal(options.dataType, 'json');

            // Just make sure these don't explode.
            options.simulate_success();
            options.simulate_error();
        },
    });

    test_with_mock_ajax({
        run_code: function () {
            channel.get({});
        },

        check_ajax_options: function (options) {
            assert.equal(options.type, 'GET');
            assert.equal(options.dataType, 'json');

            // Just make sure these don't explode.
            options.simulate_success();
            options.simulate_error();
        },
    });

});

run_test('normal_post', () => {
    const data = {
        s: 'some_string',
        num: 7,
        lst: [1, 2, 4, 8],
    };

    let orig_success_called;
    let orig_error_called;
    const stub_xhr = 'stub-xhr-normal-post';

    test_with_mock_ajax({
        xhr: stub_xhr,

        run_code: function () {
            channel.post({
                data: data,
                url: '/json/endpoint',
                success: function (data, text_status, xhr) {
                    orig_success_called = true;
                    assert.equal(data, 'response data');
                    assert.equal(text_status, 'success');
                    assert.equal(xhr, stub_xhr);
                },
                error: function () {
                    orig_error_called = true;
                },
            });
        },

        check_ajax_options: function (options) {
            assert.equal(options.type, 'POST');
            assert.equal(options.dataType, 'json');
            assert.deepEqual(options.data, data);
            assert.equal(options.url, '/json/endpoint');

            options.simulate_success('response data', 'success');
            assert(orig_success_called);

            options.simulate_error();
            assert(orig_error_called);
        },
    });
});

run_test('patch_with_form_data', () => {
    let appended;

    const data = {
        append: function (k, v) {
            assert.equal(k, 'method');
            assert.equal(v, 'PATCH');
            appended = true;
        },
    };

    test_with_mock_ajax({
        run_code: function () {
            channel.patch({
                data: data,
                processData: false,
            });
            assert(appended);
        },

        check_ajax_options: function (options) {
            assert.equal(options.type, 'POST');
            assert.equal(options.dataType, 'json');

            // Just make sure these don't explode.
            options.simulate_success();
            options.simulate_error();
        },
    });
});

run_test('reload_on_403_error', () => {
    test_with_mock_ajax({
        xhr: {
            status: 403,
            responseText: '{"msg": "CSRF Fehler: etwas", "code": "CSRF_FAILED"}',
        },

        run_code: function () {
            channel.post({});
        },

        check_ajax_options: function (options) {
            let reload_initiated;
            reload.initiate = function (options) {
                reload_initiated = true;
                assert.deepEqual(options, {
                    immediate: true,
                    save_pointer: true,
                    save_narrow: true,
                    save_compose: true,
                });
            };

            options.simulate_error();
            assert(reload_initiated);
        },
    });
});

run_test('unexpected_403_response', () => {
    test_with_mock_ajax({
        xhr: {
            status: 403,
            responseText: 'unexpected',
        },

        run_code: function () {
            channel.post({});
        },

        check_ajax_options: function (options) {
            blueslip.expect('error', 'Unexpected 403 response from server');
            options.simulate_error();
        },
    });
});

run_test('retry', () => {
    test_with_mock_ajax({
        run_code: function () {
            channel.post({
                idempotent: true,
                data: 42,
            });
        },

        check_ajax_options: function (options) {
            global.patch_builtin('setTimeout', function (f, delay) {
                assert.equal(delay, 0);
                f();
            });

            blueslip.expect('log', 'Retrying idempotent[object Object]');
            test_with_mock_ajax({
                run_code: function () {
                    options.simulate_success();
                },

                check_ajax_options: function (options) {
                    assert.equal(options.data, 42);
                },
            });
        },
    });
});

run_test('too_many_pending', () => {
    $.ajax = function () {
        const xhr = 'stub';
        return xhr;
    };

    blueslip.expect('warn',
                    'The length of pending_requests is over 50. ' +
                    'Most likely they are not being correctly removed.');
    _.times(50, function () {
        channel.post({});
    });
});

run_test('xhr_error_message', () => {
    let xhr = {
        status: '200',
        responseText: 'does not matter',
    };
    let msg = 'data added';
    assert.equal(channel.xhr_error_message(msg, xhr), 'data added');

    xhr = {
        status: '404',
        responseText: '{"msg": "file not found"}',
    };
    msg = 'some message';
    assert.equal(channel.xhr_error_message(msg, xhr), 'some message: file not found');
});
