"use strict";

const {strict: assert} = require("assert");

const _ = require("lodash");

const {mock_jquery, mock_esm, set_global, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const blueslip = require("../zjsunit/zblueslip");
const {page_params} = require("../zjsunit/zpage_params");

set_global("setTimeout", (f, delay) => {
    assert.equal(delay, 0);
    f();
});

const xhr_401 = {
    status: 401,
    responseText: '{"msg": "Use cannot access XYZ"}',
};

let login_to_access_shown = false;
mock_esm("../../static/js/spectators", {
    login_to_access: () => {
        login_to_access_shown = true;
    },
});

set_global("window", {
    location: {
        replace: () => {},
    },
});

const reload_state = zrequire("reload_state");
const channel = zrequire("channel");

const default_stub_xhr = {"default-stub-xhr": 0};

const $ = mock_jquery({});

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 = (options) => {
        $.ajax = undefined;
        ajax_called = true;
        ajax_options = options;

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

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

        return xhr;
    };

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

function test(label, f) {
    run_test(label, ({override}) => {
        reload_state.clear_for_testing();
        f({override});
    });
}

test("post", () => {
    test_with_mock_ajax({
        run_code() {
            channel.post({});
        },

        check_ajax_options(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("patch", () => {
    test_with_mock_ajax({
        run_code() {
            channel.patch({});
        },

        check_ajax_options(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("put", () => {
    test_with_mock_ajax({
        run_code() {
            channel.put({});
        },

        check_ajax_options(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("delete", () => {
    test_with_mock_ajax({
        run_code() {
            channel.del({});
        },

        check_ajax_options(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("get", () => {
    test_with_mock_ajax({
        run_code() {
            channel.get({});
        },

        check_ajax_options(options) {
            assert.equal(options.type, "GET");
            assert.equal(options.dataType, "json");

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

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": 0};

    test_with_mock_ajax({
        xhr: stub_xhr,

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

        check_ajax_options(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.ok(orig_success_called);

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

test("patch_with_form_data", () => {
    let appended;

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

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

        check_ajax_options(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("authentication_error_401_is_spectator", () => {
    test_with_mock_ajax({
        xhr: xhr_401,
        run_code() {
            channel.post({});
        },

        // is_spectator = true
        check_ajax_options(options) {
            page_params.is_spectator = true;

            options.simulate_error();
            assert.ok(login_to_access_shown);

            login_to_access_shown = false;
        },
    });
});

test("authentication_error_401_password_change_in_progress", () => {
    test_with_mock_ajax({
        xhr: xhr_401,
        run_code() {
            channel.post({});
        },

        // is_spectator = true
        // password_change_in_progress = true
        check_ajax_options(options) {
            page_params.is_spectator = true;
            channel.set_password_change_in_progress(true);

            options.simulate_error();
            assert.ok(!login_to_access_shown);

            channel.set_password_change_in_progress(false);
            page_params.is_spectator = false;
            login_to_access_shown = false;
        },
    });
});

test("authentication_error_401_not_spectator", () => {
    test_with_mock_ajax({
        xhr: xhr_401,
        run_code() {
            channel.post({});
        },

        // is_spectator = false
        check_ajax_options(options) {
            page_params.is_spectator = false;

            options.simulate_error();
            assert.ok(!login_to_access_shown);

            login_to_access_shown = false;
        },
    });
});

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

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

        check_ajax_options(options) {
            let handler_called = false;
            reload_state.set_csrf_failed_handler(() => {
                handler_called = true;
            });

            options.simulate_error();
            assert.ok(handler_called);
        },
    });
});

test("unexpected_403_response", () => {
    test_with_mock_ajax({
        xhr: {
            status: 403,
            responseText: "unexpected",
        },

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

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

test("retry", () => {
    test_with_mock_ajax({
        run_code() {
            channel.post({
                idempotent: true,
                data: 42,
            });
        },

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

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

test("too_many_pending", () => {
    channel.clear_for_tests();
    $.ajax = () => {
        const xhr = {stub: 0};
        return xhr;
    };

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

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");
});

test("while_reloading", () => {
    reload_state.set_state_to_in_progress();

    assert.equal(channel.get({ignore_reload: false}), undefined);

    test_with_mock_ajax({
        run_code() {
            channel.del({
                url: "/json/endpoint",
                ignore_reload: true,
                /* istanbul ignore next */
                success() {
                    throw new Error("unexpected success");
                },
                /* istanbul ignore next */
                error() {
                    throw new Error("unexpected error");
                },
            });
        },

        check_ajax_options(options) {
            blueslip.expect("log", "Ignoring DELETE /json/endpoint response while reloading");
            options.simulate_success();

            blueslip.expect("log", "Ignoring DELETE /json/endpoint error response while reloading");
            options.simulate_error();
        },
    });
});
