import {strict as assert} from "assert";

import type {Page} from "puppeteer";

import common from "../puppeteer_lib/common";

async function submit_notifications_stream_settings(page: Page): Promise<void> {
    await page.waitForSelector('#org-submit-notifications[data-status="unsaved"]', {visible: true});

    const save_button = "#org-submit-notifications";
    assert.strictEqual(
        await common.get_text_from_selector(page, save_button),
        "Save changes",
        "Save button has incorrect text.",
    );
    await page.click(save_button);

    await page.waitForSelector('#org-submit-notifications[data-status="saved"]', {visible: true});
    assert.strictEqual(
        await common.get_text_from_selector(page, "#org-submit-notifications"),
        "Saved",
        "Saved text didn't appear after saving new stream notifications setting",
    );

    await page.waitForSelector("#org-submit-notifications", {hidden: true});
}

async function test_change_new_stream_notifications_setting(page: Page): Promise<void> {
    await page.click("#realm_notifications_stream_id_widget button.dropdown-toggle");
    await page.waitForSelector("#realm_notifications_stream_id_widget ul.dropdown-menu", {
        visible: true,
    });

    await page.type(
        "#realm_notifications_stream_id_widget  .dropdown-search > input[type=text]",
        "rome",
    );

    const verona_in_dropdown = await page.waitForXPath(
        '//*[@id="realm_notifications_stream_id_widget"]//*[@class="dropdown-list-body"]/li[1]',
        {visible: true},
    );
    await verona_in_dropdown!.click();

    await submit_notifications_stream_settings(page);

    const disable_stream_notifications =
        "#realm_notifications_stream_id_widget  .dropdown_list_reset_button";
    await page.waitForSelector(disable_stream_notifications, {visible: true});
    await page.click(disable_stream_notifications);
    await submit_notifications_stream_settings(page);
}

async function test_change_signup_notifications_stream(page: Page): Promise<void> {
    console.log('Changing signup notifications stream to Verona by filtering with "verona"');

    await page.click("#id_realm_signup_notifications_stream_id > button.dropdown-toggle");
    await page.waitForSelector(
        "#realm_signup_notifications_stream_id_widget  .dropdown-search > input[type=text]",
        {visible: true},
    );

    await page.type(
        "#realm_signup_notifications_stream_id_widget  .dropdown-search > input[type=text]",
        "verona",
    );
    await page.waitForSelector(
        "#realm_signup_notifications_stream_id_widget  .dropdown-list-body > li.list_item",
        {visible: true},
    );
    await page.keyboard.press("ArrowDown");
    await page.keyboard.press("Enter");
    await submit_notifications_stream_settings(page);

    const disable_signup_notifications =
        "#realm_signup_notifications_stream_id_widget  .dropdown_list_reset_button";
    await page.click(disable_signup_notifications);
    await submit_notifications_stream_settings(page);
}

async function test_permissions_change_save_worked(page: Page): Promise<void> {
    const saved_status = '#org-submit-stream-permissions[data-status="saved"]';
    await page.waitForSelector(saved_status, {
        visible: true,
    });
    await page.waitForSelector(saved_status, {hidden: true});
}

async function submit_stream_permissions_change(page: Page): Promise<void> {
    const save_button = "#org-submit-stream-permissions";
    await page.waitForSelector(save_button, {visible: true});
    assert.strictEqual(
        await common.get_text_from_selector(page, save_button),
        "Save changes",
        "Save button didn't appear for permissions change.",
    );
    await page.click(save_button);

    await test_permissions_change_save_worked(page);
}

async function test_changing_create_streams_and_invite_to_stream_policies(
    page: Page,
): Promise<void> {
    const policies = {
        "create private stream": "#id_realm_create_private_stream_policy",
        "create public stream": "#id_realm_create_public_stream_policy",
        "invite to stream": "#id_realm_invite_to_stream_policy",
    };
    const policy_values = {
        "admins only": 2,
        "members and admins": 1,
        "full members": 3,
    };

    for (const [policy, selector] of Object.entries(policies)) {
        for (const [policy_value_name, policy_value] of Object.entries(policy_values)) {
            console.log(`Test setting ${policy} policy to '${policy_value_name}'.`);
            await page.waitForSelector(selector, {visible: true});
            await page.evaluate(
                (selector, policy_value) => $(selector).val(policy_value).trigger("change"),
                selector,
                policy_value,
            );
            await submit_stream_permissions_change(page);
        }
    }
}

async function test_save_joining_organization_change_worked(page: Page): Promise<void> {
    const saved_status = '#org-submit-org-join[data-status="saved"]';
    await page.waitForSelector(saved_status, {
        visible: true,
    });
    await page.waitForSelector(saved_status, {hidden: true});
}

async function submit_joining_organization_change(page: Page): Promise<void> {
    const save_button = "#org-submit-org-join";
    await page.waitForSelector(save_button, {visible: true});
    assert.strictEqual(
        await common.get_text_from_selector(page, save_button),
        "Save changes",
        "Save button didn't appear for permissions change.",
    );
    await page.waitForSelector(save_button, {visible: true});
    await page.click(save_button);

    await test_save_joining_organization_change_worked(page);
}

async function test_set_new_user_threshold_to_three_days(page: Page): Promise<void> {
    console.log("Test setting new user threshold to three days.");
    await page.waitForSelector("#id_realm_waiting_period_setting", {visible: true});
    await page.evaluate(() =>
        $("#id_realm_waiting_period_setting").val("three_days").trigger("change"),
    );
    await submit_joining_organization_change(page);
}

async function test_set_new_user_threshold_to_N_days(page: Page): Promise<void> {
    console.log("Test setting new user threshold to three days.");
    await page.waitForSelector("#id_realm_waiting_period_setting", {visible: true});
    await page.evaluate(() =>
        $("#id_realm_waiting_period_setting").val("custom_days").trigger("change"),
    );

    const N = 10;
    await page.evaluate((N: number) => $("#id_realm_waiting_period_threshold").val(N), N);
    await submit_joining_organization_change(page);
}

async function test_organization_permissions(page: Page): Promise<void> {
    await page.click("li[data-section='organization-permissions']");

    await test_changing_create_streams_and_invite_to_stream_policies(page);

    await test_set_new_user_threshold_to_three_days(page);
    await test_set_new_user_threshold_to_N_days(page);
}

async function test_add_emoji(page: Page): Promise<void> {
    await common.fill_form(page, "form.admin-emoji-form", {name: "zulip logo"});

    const emoji_upload_handle = await page.$("#emoji_file_input");
    await emoji_upload_handle!.uploadFile("static/images/logo/zulip-icon-128x128.png");
    await page.click("#admin_emoji_submit");

    const emoji_status = "div#admin-emoji-status";
    await page.waitForSelector(emoji_status, {visible: true});
    assert.strictEqual(
        await common.get_text_from_selector(page, emoji_status),
        "Custom emoji added!",
    );

    await page.waitForSelector("tr#emoji_zulip_logo", {visible: true});
    assert.strictEqual(
        await common.get_text_from_selector(page, "tr#emoji_zulip_logo .emoji_name"),
        "zulip logo",
        "Emoji name incorrectly saved.",
    );
    await page.waitForSelector("tr#emoji_zulip_logo img", {visible: true});
}

async function test_delete_emoji(page: Page): Promise<void> {
    await page.click("tr#emoji_zulip_logo button.delete");

    // assert the emoji is deleted.
    await page.waitForFunction(() => $("tr#emoji_zulip_logo").length === 0);
}

async function test_custom_realm_emoji(page: Page): Promise<void> {
    await page.click("li[data-section='emoji-settings']");
    await page.waitForSelector(".admin-emoji-form", {visible: true});

    await test_add_emoji(page);
    await test_delete_emoji(page);
}

async function get_suggestions(page: Page, str: string): Promise<void> {
    await page.evaluate((str: string) => {
        $(".create_default_stream")
            .trigger("focus")
            .val(str)
            .trigger(new $.Event("keyup", {which: 0}));
    }, str);
}

async function select_from_suggestions(page: Page, item: string): Promise<void> {
    await page.evaluate((item: string) => {
        const tah = $(".create_default_stream").data().typeahead;
        tah.mousemove({
            currentTarget: $(`.typeahead:visible li:contains("${CSS.escape(item)}")`)[0],
        });
        tah.mouseenter({
            currentTarget: $(`.typeahead:visible li:contains("${CSS.escape(item)}")`)[0],
        });
        tah.select();
    }, item);
}

async function test_add_default_stream(
    page: Page,
    stream_name: string,
    row: string,
): Promise<void> {
    // It matches with all the stream names which has 'O' as a substring (Rome, Scotland, Verona
    // etc). 'O' is used to make sure that it works even if there are multiple suggestions.
    // Uppercase 'O' is used instead of the lowercase version to make sure that the suggestions
    // are case insensitive.
    await get_suggestions(page, "o");
    await select_from_suggestions(page, stream_name);
    await page.click(".default-stream-form #do_submit_stream");

    await page.waitForSelector(row, {visible: true});
}

async function test_remove_default_stream(page: Page, row: string): Promise<void> {
    await page.click(row + " button.remove-default-stream");

    // assert row doesn't exist.
    await page.waitForFunction((row: string) => $(row).length === 0, {}, row);
}

async function test_default_streams(page: Page): Promise<void> {
    await page.click("li[data-section='default-streams-list']");
    await page.waitForSelector(".create_default_stream", {visible: true});

    const stream_name = "Scotland";
    const stream_id = await common.get_stream_id(page, stream_name);
    const row = `.default_stream_row[data-stream-id='${CSS.escape(stream_id.toString())}']`;

    await test_add_default_stream(page, stream_name, row);
    await test_remove_default_stream(page, row);
}

async function test_upload_realm_icon_image(page: Page): Promise<void> {
    const upload_handle = await page.$("#realm-icon-upload-widget .image_file_input");
    await upload_handle!.uploadFile("static/images/logo/zulip-icon-128x128.png");

    await page.waitForSelector("#realm-icon-upload-widget .upload-spinner-background", {
        visible: true,
    });
    await page.waitForSelector("#realm-icon-upload-widget .upload-spinner-background", {
        hidden: true,
    });
    await page.waitForSelector(
        '#realm-icon-upload-widget .image-block[src^="/user_avatars/2/realm/icon.png?version=2"]',
        {visible: true},
    );
}

async function delete_realm_icon(page: Page): Promise<void> {
    await page.click("li[data-section='organization-profile']");
    await page.click("#realm-icon-upload-widget .image-delete-button");

    await page.waitForSelector("#realm-icon-upload-widget .image-delete-button", {hidden: true});
}

async function test_organization_profile(page: Page): Promise<void> {
    await page.click("li[data-section='organization-profile']");
    const gravatar_selctor =
        '#realm-icon-upload-widget .image-block[src^="https://secure.gravatar.com/avatar/"]';
    await page.waitForSelector(gravatar_selctor, {visible: true});
    await page.waitForSelector("#realm-icon-upload-widget .image-delete-button", {hidden: true});

    await test_upload_realm_icon_image(page);
    await page.waitForSelector("#realm-icon-upload-widget .image-delete-button", {visible: true});

    await delete_realm_icon(page);
    await page.waitForSelector("#realm-icon-upload-widget .image-delete-button", {hidden: true});
    await page.waitForSelector(gravatar_selctor, {visible: true});
}

async function test_authentication_methods(page: Page): Promise<void> {
    await page.click("li[data-section='auth-methods']");
    await page.waitForSelector(".method_row[data-method='Google'] input[type='checkbox'] + span", {
        visible: true,
    });

    await page.click(".method_row[data-method='Google'] input[type='checkbox'] + span");
    const save_button = "#org-submit-auth_settings";
    assert.strictEqual(await common.get_text_from_selector(page, save_button), "Save changes");
    await page.click(save_button);

    // Leave the page and return.
    const settings_dropdown = "#settings-dropdown";
    await page.click(settings_dropdown);

    await common.manage_organization(page);
    await page.click("li[data-section='auth-methods']");

    // Test setting was saved.
    await page.waitForSelector(".method_row[data-method='Google'] input[type='checkbox'] + span", {
        visible: true,
    });
    await page.waitForFunction(
        () =>
            !($(".method_row[data-method='Google'] input[type='checkbox']")[0] as HTMLInputElement)
                .checked,
    );
}

async function admin_test(page: Page): Promise<void> {
    await common.log_in(page);

    await common.manage_organization(page);
    await test_change_new_stream_notifications_setting(page);
    await test_change_signup_notifications_stream(page);

    await test_organization_permissions(page);
    // Currently, Firefox (with puppeteer) does not support file upload:
    //    https://github.com/puppeteer/puppeteer/issues/6688.
    // Until that is resolved upstream, we need to skip the tests that involve
    // doing file upload on Firefox.
    if (!common.is_firefox) {
        await test_custom_realm_emoji(page);
        await test_organization_profile(page);
    }
    await test_default_streams(page);
    await test_authentication_methods(page);
}

common.run_test(admin_test);
