set_global('page_params', {realm_is_zephyr_mirror_realm: false});
set_global('md5', function (s) {
    return 'md5-' + s;
});

const settings_config = zrequire('settings_config');
page_params.realm_email_address_visibility =
    settings_config.email_address_visibility_values.admins_only.code;

set_global('Handlebars', global.make_handlebars());
zrequire('recent_senders');
zrequire('pm_conversations');
zrequire('people');
zrequire('emoji');
zrequire('stream_data');
zrequire('narrow');
zrequire('hash_util');
zrequire('marked', 'third/marked/lib/marked');
const pygments_data = zrequire('pygments_data', 'generated/pygments_data.json');
const actual_pygments_data = Object.assign({}, pygments_data);
const ct = zrequire('composebox_typeahead');
const th = zrequire('typeahead_helper');
const LazySet = zrequire('lazy_set.js').LazySet;

let next_id = 0;

function assertSameEmails(lst1, lst2) {
    assert.deepEqual(
        lst1.map(r => r.email),
        lst2.map(r => r.email)
    );
}

stream_data.create_streams([
    {name: 'Dev', subscribed: true, color: 'blue', stream_id: 1},
    {name: 'Linux', subscribed: true, color: 'red', stream_id: 2},
]);

run_test('sort_streams', () => {
    const popular = new LazySet([1, 2, 3, 4, 5, 6]);

    const unpopular = new LazySet([1]);

    let test_streams = [
        {name: 'Dev', pin_to_top: false, subscribers: unpopular, subscribed: true},
        {name: 'Docs', pin_to_top: false, subscribers: popular, subscribed: true},
        {name: 'Derp', pin_to_top: false, subscribers: unpopular, subscribed: true},
        {name: 'Denmark', pin_to_top: true, subscribers: popular, subscribed: true},
        {name: 'dead', pin_to_top: false, subscribers: unpopular, subscribed: true},
    ];
    test_streams.forEach(stream_data.update_calculated_fields);

    global.stream_data.is_active = function (sub) {
        return sub.name !== 'dead';
    };

    test_streams = th.sort_streams(test_streams, 'd');
    assert.deepEqual(test_streams[0].name, "Denmark"); // Pinned streams first
    assert.deepEqual(test_streams[1].name, "Docs"); // Active streams next
    assert.deepEqual(test_streams[2].name, "Derp"); // Less subscribers
    assert.deepEqual(test_streams[3].name, "Dev"); // Alphabetically last
    assert.deepEqual(test_streams[4].name, "dead"); // Inactive streams last

    // Test sort streams with description
    test_streams = [
        {name: 'Dev', description: 'development help', subscribers: unpopular, subscribed: true},
        {name: 'Docs', description: 'writing docs', subscribers: popular, subscribed: true},
        {name: 'Derp', description: 'derping around', subscribers: unpopular, subscribed: true},
        {name: 'Denmark', description: 'visiting Denmark', subscribers: popular, subscribed: true},
        {name: 'dead', description: 'dead stream', subscribers: unpopular, subscribed: true},
    ];
    test_streams.forEach(stream_data.update_calculated_fields);
    test_streams = th.sort_streams(test_streams, 'wr');
    assert.deepEqual(test_streams[0].name, "Docs"); // Description match
    assert.deepEqual(test_streams[1].name, "Denmark"); // Popular stream
    assert.deepEqual(test_streams[2].name, "Derp"); // Less subscribers
    assert.deepEqual(test_streams[3].name, "Dev"); // Alphabetically last
    assert.deepEqual(test_streams[4].name, "dead"); // Inactive streams last

    // Test sort both subscribed and unsubscribed streams.
    test_streams = [
        {name: 'Dev', description: 'Some devs', subscribed: true, subscribers: popular},
        {name: 'East', description: 'Developing east', subscribed: true, subscribers: popular},
        {name: 'New', description: 'No match', subscribed: true, subscribers: popular},
        {name: 'Derp', description: 'Always Derping', subscribed: false, subscribers: popular},
        {name: 'Ether', description: 'Destroying ether', subscribed: false, subscribers: popular},
        {name: 'Mew', description: 'Cat mews', subscribed: false, subscribers: popular},
    ];
    test_streams.forEach(stream_data.update_calculated_fields);

    test_streams = th.sort_streams(test_streams, 'd');
    assert.deepEqual(test_streams[0].name, "Dev"); // Subscribed and stream name starts with query
    assert.deepEqual(test_streams[1].name, "Derp"); // Unsubscribed and stream name starts with query
    assert.deepEqual(test_streams[2].name, "East"); // Subscribed and description starts with query
    assert.deepEqual(test_streams[3].name, "Ether"); // Unsubscribed and description starts with query
    assert.deepEqual(test_streams[4].name, "New"); // Subscribed and no match
    assert.deepEqual(test_streams[5].name, "Mew"); // Unsubscribed and no match
});

run_test('sort_languages', () => {
    Object.assign(pygments_data, {langs:
        {python: 40, javscript: 50, php: 38, pascal: 29, perl: 22, css: 0},
    });

    let test_langs = ["pascal", "perl", "php", "python", "javascript"];
    test_langs = th.sort_languages(test_langs, "p");

    // Sort languages by matching first letter, and then by popularity
    assert.deepEqual(test_langs, ["python", "php", "pascal", "perl", "javascript"]);

    // Test if popularity between two languages are the same
    pygments_data.langs.php = 40;
    test_langs = ["pascal", "perl", "php", "python", "javascript"];
    test_langs = th.sort_languages(test_langs, "p");

    assert.deepEqual(test_langs, ["php", "python", "pascal", "perl", "javascript"]);

    // Some final tests on the actual pygments data to check ordering.
    //
    // We may eventually want to use human-readable names like
    // "JavaScript" with several machine-readable aliases for what the
    // user typed, which might help provide a better user experience.
    Object.assign(pygments_data, actual_pygments_data);
    test_langs = ["j", "java", "javascript", "js"];

    // Sort acccording to priority only.
    test_langs = th.sort_languages(test_langs, "jav");
    assert.deepEqual(test_langs, ["javascript", "java", "js", "j"]);

    // Push exact matches to top, regardless of priority
    test_langs = th.sort_languages(test_langs, "java");
    assert.deepEqual(test_langs, ["java", "javascript", "js", "j"]);
    test_langs = th.sort_languages(test_langs, "j");
    assert.deepEqual(test_langs, ["j", "javascript", "java", "js"]);
});

const a_bot = {
    email: "a_bot@zulip.com",
    full_name: "A zulip test bot",
    is_admin: false,
    is_bot: true,
    user_id: 1,
};

const a_user = {
    email: "a_user@zulip.org",
    full_name: "A zulip user",
    is_admin: false,
    is_bot: false,
    user_id: 2,
};

const b_user_1 = {
    email: "b_user_1@zulip.net",
    full_name: "Bob 1",
    is_admin: false,
    is_bot: false,
    user_id: 3,
};

const b_user_2 = {
    email: "b_user_2@zulip.net",
    full_name: "Bob 2",
    is_admin: true,
    is_bot: false,
    user_id: 4,
};

const b_user_3 = {
    email: "b_user_3@zulip.net",
    full_name: "Bob 3",
    is_admin: false,
    is_bot: false,
    user_id: 5,
};

const b_bot = {
    email: "b_bot@example.com",
    full_name: "B bot",
    is_admin: false,
    is_bot: true,
    user_id: 6,
};

const zman = {
    email: "zman@test.net",
    full_name: "Zman",
    is_admin: false,
    is_bot: false,
    user_id: 7,
};

const matches = [
    a_bot,
    a_user,
    b_user_1,
    b_user_2,
    b_user_3,
    b_bot,
    zman,
];

for (const person of matches) {
    people.add(person);
}

function get_typeahead_result(query, current_stream, current_topic) {
    const result = th.sort_recipients(
        people.get_realm_users(),
        query,
        current_stream,
        current_topic
    );
    return result.map(person => person.email);
}

run_test('sort_recipients', () => {
    // Typeahead for recipientbox [query, "", undefined]
    assert.deepEqual(get_typeahead_result("b", ""), [
        'b_user_1@zulip.net',
        'b_user_2@zulip.net',
        'b_user_3@zulip.net',
        'b_bot@example.com',
        'a_user@zulip.org',
        'zman@test.net',
        'a_bot@zulip.com',
    ]);

    // Typeahead for private message [query, "", ""]
    assert.deepEqual(get_typeahead_result("a", "", ""), [
        'a_user@zulip.org',
        'a_bot@zulip.com',
        'b_user_1@zulip.net',
        'b_user_2@zulip.net',
        'b_user_3@zulip.net',
        'zman@test.net',
        'b_bot@example.com',
    ]);

    const subscriber_email_1 = "b_user_2@zulip.net";
    const subscriber_email_2 = "b_user_3@zulip.net";
    const subscriber_email_3 = "b_bot@example.com";
    stream_data.add_subscriber("Dev", people.get_user_id(subscriber_email_1));
    stream_data.add_subscriber("Dev", people.get_user_id(subscriber_email_2));
    stream_data.add_subscriber("Dev", people.get_user_id(subscriber_email_3));

    const dev_sub = stream_data.get_sub("Dev");
    const linux_sub = stream_data.get_sub("Linux");
    stream_data.update_calculated_fields(dev_sub);
    stream_data.update_calculated_fields(linux_sub);

    // For splitting based on whether a PM was sent
    global.pm_conversations.set_partner(5);
    global.pm_conversations.set_partner(6);
    global.pm_conversations.set_partner(2);
    global.pm_conversations.set_partner(7);

    // For splitting based on recency
    global.recent_senders.process_message_for_senders({
        sender_id: 7,
        stream_id: 1,
        topic: "Dev Topic",
        id: next_id += 1,
    });
    global.recent_senders.process_message_for_senders({
        sender_id: 5,
        stream_id: 1,
        topic: "Dev Topic",
        id: next_id += 1,
    });
    global.recent_senders.process_message_for_senders({
        sender_id: 6,
        stream_id: 1,
        topic: "Dev Topic",
        id: next_id += 1,
    });

    // Typeahead for stream message [query, stream-name, topic-name]
    assert.deepEqual(get_typeahead_result("b", "Dev", "Dev Topic"), [
        subscriber_email_3,
        subscriber_email_2,
        subscriber_email_1,
        'b_user_1@zulip.net',
        'zman@test.net',
        'a_user@zulip.org',
        'a_bot@zulip.com',
    ]);

    global.recent_senders.process_message_for_senders({
        sender_id: 5,
        stream_id: 2,
        topic: "Linux Topic",
        id: next_id += 1,
    });
    global.recent_senders.process_message_for_senders({
        sender_id: 7,
        stream_id: 2,
        topic: "Linux Topic",
        id: next_id += 1,
    });

    // No match
    assert.deepEqual(get_typeahead_result("h", "Linux", "Linux Topic"), [
        'zman@test.net',
        'b_user_3@zulip.net',
        'a_user@zulip.org',
        'b_bot@example.com',
        'a_bot@zulip.com',
        'b_user_1@zulip.net',
        'b_user_2@zulip.net',
    ]);
});

const all_obj = ct.broadcast_mentions()[0];
assert.equal(all_obj.email, 'all');
assert.equal(all_obj.is_broadcast, true);
assert.equal(all_obj.idx, 0);

run_test('sort_recipients all mention', () => {
    // Test person email is "all" or "everyone"
    const test_objs = matches.concat([all_obj]);

    const results = th.sort_recipients(
        test_objs,
        'a',
        'Linux',
        'Linux Topic'
    );

    assertSameEmails(results, [
        all_obj,
        a_user,
        a_bot,
        zman,
        b_user_3,
        b_bot,
        b_user_1,
        b_user_2,
    ]);
});

run_test('sort_recipients pm counts', () => {
    // Test sort_recipients with pm counts
    a_bot.pm_recipient_count = 50;
    a_user.pm_recipient_count = 2;
    b_user_1.pm_recipient_count = 32;
    b_user_2.pm_recipient_count = 42;
    b_user_3.pm_recipient_count = 0;
    b_bot.pm_recipient_count = 1;

    assert.deepEqual(get_typeahead_result("b", "Linux", "Linux Topic"), [
        'b_user_3@zulip.net',
        'b_bot@example.com',
        'b_user_1@zulip.net',
        'b_user_2@zulip.net',
        'zman@test.net',
        'a_user@zulip.org',
        'a_bot@zulip.com',
    ]);
});

run_test('sort_recipients dup bots', () => {
    const dup_objects = matches.concat([a_bot]);

    const recipients = th.sort_recipients(dup_objects, "b", "", "");
    const recipients_email = recipients.map(person => person.email);
    const expected = [
        'b_bot@example.com',
        'b_user_3@zulip.net',
        'b_user_2@zulip.net',
        'b_user_1@zulip.net',
        'a_user@zulip.org',
        'zman@test.net',
        'a_bot@zulip.com',
        'a_bot@zulip.com',
    ];
    assert.deepEqual(recipients_email, expected);
});

run_test('sort_recipients dup alls', () => {
    // full_name starts with same character but emails are 'all'
    const test_objs = [
        all_obj,
        a_user,
        all_obj,
    ];

    const recipients = th.sort_recipients(
        test_objs, "a", "Linux", "Linux Topic");

    const expected = [
        all_obj,
        all_obj,
        a_user,
    ];
    assertSameEmails(recipients, expected);
});

run_test('sort_recipients subscribers', () => {
    // b_user_2 is a subscriber and b_user_1 is not.
    const small_matches = [b_user_2, b_user_1];
    const recipients = th.sort_recipients(small_matches, "b", "Dev", "Dev Topic");
    const recipients_email = recipients.map(person => person.email);
    const expected = [
        'b_user_2@zulip.net',
        'b_user_1@zulip.net',
    ];
    assert.deepEqual(recipients_email, expected);
});

run_test('sort_recipients pm partners', () => {
    // b_user_3 is a pm partner and b_user_2 is not and
    // both are not subscribered to the stream Linux.
    const small_matches = [b_user_3, b_user_2];
    const recipients = th.sort_recipients(small_matches, "b", "Linux", "Linux Topic");
    const recipients_email = recipients.map(person => person.email);
    const expected = [
        'b_user_3@zulip.net',
        'b_user_2@zulip.net',
    ];
    assert.deepEqual(recipients_email, expected);
});

run_test('sort broadcast mentions', () => {
    // test the normal case, which is that the
    // broadcast mentions are already sorted (we
    // actually had a bug where the sort would
    // randomly rearrange them)
    const results = th.sort_people_for_relevance(
        ct.broadcast_mentions().reverse(),
        '',
        '');

    assert.deepEqual(
        results.map(r => r.email),
        ['all', 'everyone', 'stream']
    );

    // Reverse the list to test actual sorting
    // and ensure test coverage for the defensive
    // code.  Also, add in some people users.
    const test_objs = Array.from(ct.broadcast_mentions()).reverse();
    test_objs.unshift(zman);
    test_objs.push(a_user);

    const results2 = th.sort_people_for_relevance(
        test_objs,
        '',
        '');

    assert.deepEqual(
        results2.map(r => r.email),
        ['all',
         'everyone',
         'stream',
         a_user.email,
         zman.email]
    );
});

run_test('test compare directly', () => {
    // This is important for ensuring test coverage.
    // We don't technically need it now, but our test
    // coverage is subject to the whims of how JS sorts.
    assert.equal(
        th.compare_people_for_relevance(all_obj, all_obj),
        0);

    assert.equal(
        th.compare_people_for_relevance(all_obj, zman),
        -1);

    assert.equal(
        th.compare_people_for_relevance(zman, all_obj),
        1);
});

run_test('highlight_with_escaping', () => {
    function highlight(query, item) {
        const regex = th.build_highlight_regex(query);
        return th.highlight_with_escaping_and_regex(regex, item);
    }

    let item = "Denmark";
    let query = "Den";
    let expected = "<strong>Den</strong>mark";
    let result = highlight(query, item);
    assert.equal(result, expected);

    item = "w3IrD_naMe";
    query = "w3IrD_naMe";
    expected = "<strong>w3IrD_naMe</strong>";
    result = highlight(query, item);
    assert.equal(result, expected);

    item = "development help";
    query = "development h";
    expected = "<strong>development h</strong>elp";
    result = highlight(query, item);
    assert.equal(result, expected);
});

run_test('render_person when emails hidden', () => {
    // Test render_person with regular person, under hidden email visibility case
    page_params.is_admin = false;
    let rendered = false;
    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.equal(args.primary, b_user_1.full_name);
        assert.equal(args.secondary, undefined);
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_person(b_user_1), 'typeahead-item-stub');
    assert(rendered);
});

run_test('render_person', () => {
    page_params.is_admin = true;
    // Test render_person with regular person
    let rendered = false;
    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.equal(args.primary, a_user.full_name);
        assert.equal(args.secondary, a_user.email);
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_person(a_user), 'typeahead-item-stub');
    assert(rendered);

    // Test render_person with special_item_text person
    const special_person = {
        email: "special@example.com",
        full_name: "Special person",
        is_admin: false,
        is_bot: false,
        user_id: 7,
        special_item_text: "special_text",
    };
    rendered = false;
    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.equal(args.primary, special_person.special_item_text);
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_person(special_person), 'typeahead-item-stub');
    assert(rendered);
});

run_test('clear_rendered_person', () => {
    let rendered = false;
    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.equal(args.primary, b_bot.full_name);
        assert.equal(args.secondary, b_bot.email);
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_person(b_bot), 'typeahead-item-stub');
    assert(rendered);

    // Bot once rendered won't be rendered again until clear_rendered_person
    // function is called. clear_rendered_person is used to clear rendered
    // data once bot name is modified.
    rendered = false;
    assert.equal(th.render_person(b_bot), 'typeahead-item-stub');
    assert.equal(rendered, false);

    // Here rendered will be true as it is being rendered again.
    th.clear_rendered_person(b_bot.user_id);
    assert.equal(th.render_person(b_bot), 'typeahead-item-stub');
    assert(rendered);

});

run_test('render_stream', () => {
    // Test render_stream with short description
    let rendered = false;
    let stream = {
        description: 'This is a short description.',
        stream_id: 42,
        name: 'Short Description',
    };
    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.equal(args.primary, stream.name);
        assert.equal(args.secondary, stream.description);
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_stream(stream), 'typeahead-item-stub');
    assert(rendered);

    // Test render_stream with long description
    rendered = false;
    stream = {
        description: 'This is a very very very very very long description.',
        stream_id: 43,
        name: 'Long Description',
    };
    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.equal(args.primary, stream.name);
        const short_desc = stream.description.substring(0, 35);
        assert.equal(args.secondary, short_desc + "...");
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_stream(stream), 'typeahead-item-stub');
    assert(rendered);
});

run_test('clear_rendered_stream', () => {
    let rendered = false;
    const stream = {
        description: 'This is a description.',
        stream_id: 44,
        name: 'Stream To Be Cleared',
    };
    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.equal(args.primary, stream.name);
        assert.equal(args.secondary, stream.description);
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_stream(stream), 'typeahead-item-stub');
    assert(rendered);

    rendered = false;
    assert.equal(th.render_stream(stream), 'typeahead-item-stub');
    assert.equal(rendered, false);

    // Here rendered will be true as it is being rendered again.
    th.clear_rendered_stream(stream.stream_id);
    assert.equal(th.render_stream(stream), 'typeahead-item-stub');
    assert(rendered);
});

run_test('render_emoji', () => {
    // Test render_emoji with normal emoji.
    let rendered = false;
    let test_emoji = {
        emoji_name: 'thumbs_up',
        emoji_code: '1f44d',
    };
    emoji.active_realm_emojis = new Map(Object.entries({
        realm_emoji: 'TBD',
    }));

    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.deepEqual(args, {
            primary: 'thumbs up',
            emoji_code: '1f44d',
            is_emoji: true,
            has_image: false,
            has_secondary: false,
        });
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_emoji(test_emoji), 'typeahead-item-stub');
    assert(rendered);

    // Test render_emoji with normal emoji.
    rendered = false;
    test_emoji = {
        emoji_name: 'realm_emoji',
        emoji_url: 'TBD',
    };

    global.stub_templates(function (template_name, args) {
        assert.equal(template_name, 'typeahead_list_item');
        assert.deepEqual(args, {
            primary: 'realm emoji',
            img_src: 'TBD',
            is_emoji: true,
            has_image: true,
            has_secondary: false,
        });
        rendered = true;
        return 'typeahead-item-stub';
    });
    assert.equal(th.render_emoji(test_emoji), 'typeahead-item-stub');
    assert(rendered);
});

run_test('sort_slash_commands', () => {
    const slash_commands = [
        { name: 'my' },
        { name: 'poll' },
        { name: 'me' },
        { name: 'mine' },
        { name: 'test' },
        { name: 'ping' },
    ];
    assert.deepEqual(th.sort_slash_commands(slash_commands, 'm'), [
        { name: 'me' },
        { name: 'mine' },
        { name: 'my' },
        { name: 'ping' },
        { name: 'poll' },
        { name: 'test' },
    ]);
});

run_test('sort_recipientbox_typeahead', () => {
    let recipients = th.sort_recipientbox_typeahead("b, a", matches, ""); // search "a"
    let recipients_email = recipients.map(person => person.email);
    assert.deepEqual(recipients_email, [
        'a_user@zulip.org', // matches "a"
        'a_bot@zulip.com', // matches "a"
        'b_bot@example.com',
        'b_user_3@zulip.net',
        'zman@test.net',
        'b_user_2@zulip.net',
        'b_user_1@zulip.net',
    ]);

    recipients = th.sort_recipientbox_typeahead("b, a, b", matches, ""); // search "b"
    recipients_email = recipients.map(person => person.email);
    assert.deepEqual(recipients_email, [
        'b_bot@example.com',
        'b_user_3@zulip.net',
        'b_user_2@zulip.net',
        'b_user_1@zulip.net',
        'a_user@zulip.org',
        'zman@test.net',
        'a_bot@zulip.com',
    ]);
});
