import {Page} from '@playwright/test';

import DashboardPage from '../../../page-objects/dashboard/DashboardPage';
import DashboardSettings from '../../../page-objects/dashboard/DashboardSettings';
import {getUniqueTimestamp, openTestPage, slct, waitForCondition} from '../../../utils';
import {dragAndDropListItem} from '../helpers';

import {RobotChartsDashboardUrls, RobotChartsEditorUrls} from '../../../utils/constants';
import {
    ControlQA,
    DialogDashWidgetQA,
    NavigationInputQA,
    ParamsSettingsQA,
    TabMenuQA,
} from '../../../../src/shared/constants';
import {RESTRICTED_PARAM_NAMES} from '../../../../src/shared/constants/dash';
import datalensTest from '../../../utils/playwright/globalTestDefinition';

const DASH_PARAMS: Array<[string, string]> = [
    ['param1', ''],
    ['param2', 'value1'],
    ['param2', 'value2'],
    ['param2', 'value3'],
    ['param3', 'value1'],
];

const openParams = async (page: Page) => {
    const newParamsButton = await page.$(slct(ParamsSettingsQA.Open));
    if (newParamsButton !== null) {
        const newParams = await page.$(slct(ParamsSettingsQA.Settings));

        if (!newParams) {
            await newParamsButton.click();
        }
    }
};

const getParamsFromPage = async (page: Page) => {
    await openParams(page);

    // getting the entire list of parameters without errors
    const paramRows = await page.$$(slct(ParamsSettingsQA.ParamRow));

    // for each row, getting the parameters and values in the array of arrays
    const paramsContent = await Promise.all(
        paramRows.map((paramRow) =>
            (async () => {
                const paramsSeed: string[] = [];

                // getting the name of the parameter
                const titleEl = await paramRow.$(`${slct(ParamsSettingsQA.ParamTitle)} input`);
                const title = ((await titleEl?.inputValue()) || '').trim();

                // if the parameter name is empty, then ignoring the line
                if (title === '') {
                    return paramsSeed;
                }

                // getting the parameter values
                const valuesEl = await paramRow.$$(slct(ParamsSettingsQA.ParamValue));

                // if there are no parameter values, adding a parameter without a value
                if (valuesEl.length < 1) {
                    paramsSeed.push(`${title}=`);
                    return paramsSeed;
                }

                for (let i = 0; i < valuesEl.length; i++) {
                    const valuePromise = valuesEl[i]?.textContent();

                    const value = ((await valuePromise) || '').trim();

                    paramsSeed.push(`${title}=${value}`);
                }
                return paramsSeed;
            })(),
        ),
    );

    // transforming to single-level array of all parameters from an array of arrays
    return paramsContent.flat();
};

const addParam = async (page: Page, param: {title: string; value: string}) => {
    await openParams(page);

    // click the add parameters button to ensure that a new line appears
    await page.locator(slct(ParamsSettingsQA.Add)).click();

    // getting the entire list of parameters
    const paramRows = await page.$$(slct(ParamsSettingsQA.ParamRow));

    // getting all the parameter names
    const paramRowsTitles = await Promise.all(
        paramRows.map((paramRow) =>
            (async () => {
                const titleEl = await paramRow.$(`input`);
                const title = ((await titleEl?.inputValue()) || '').trim();

                return title;
            })(),
        ),
    );

    // finding the index of the first line of the parameter with an empty name
    const addIndex = paramRowsTitles.findIndex((title) => title === '');

    const lastParamRow = paramRows[addIndex];

    // filling the name of the parameter
    const paramTitleInput = await lastParamRow.$(`${slct(ParamsSettingsQA.ParamTitle)} input`);
    paramTitleInput?.fill(param.title);

    if (param.value) {
        // click on the button to add a new value for the input field appears
        const addParamValueButton = await lastParamRow.$(slct(ParamsSettingsQA.ParamAddValue));
        await addParamValueButton?.click();

        // finding the parameter value input field and add the value
        const paramValueInput = await lastParamRow.$(`${slct(ParamsSettingsQA.ParamValue)} input`);
        await paramValueInput?.fill(param.value);

        // shifting the focus from the input field for the value is preserved
        await paramTitleInput?.focus();
    }
};

const removeParam = async (page: Page, paramTitle: string) => {
    await openParams(page);

    await page.click(slct(ParamsSettingsQA.Add));

    // getting the entire list of parameters
    const paramRows = await page.$$(slct(ParamsSettingsQA.ParamRow));

    // getting all the parameter names
    const paramRowsTitles = await Promise.all(
        paramRows.map((paramRow) =>
            (async () => {
                const titleEl = await paramRow.$(`input`);
                const title = ((await titleEl?.inputValue()) || '').trim();

                return title;
            })(),
        ),
    );

    // finding the index of the parameter to be deleted
    const removeIndex = paramRowsTitles.findIndex((title) => title === paramTitle);
    if (removeIndex < 0) {
        throw new Error('index of param for delete not found');
    }

    // emulating hovering over a line for the delete button to appear
    await paramRows[removeIndex].hover();

    const removeButton = await paramRows[removeIndex].$(slct(ParamsSettingsQA.Remove));
    await removeButton?.click();
};

const addExternalSelector = async (dashboardPage: DashboardPage) => {
    await dashboardPage.enterEditMode();

    await dashboardPage.controlActions.clickAddExternalSelector();
};

datalensTest.describe(`Dashboards - chart/external selector/dashboard parameters`, () => {
    datalensTest.beforeEach(async ({page}: {page: Page}) => {
        const dashName = `e2e-test-dash-params-${getUniqueTimestamp()}`;
        const dashboardPage = new DashboardPage({page});
        await openTestPage(page, RobotChartsDashboardUrls.DashboardWithChartDefaultParams);
        await dashboardPage.copyDashboard(dashName);
    });

    datalensTest(
        'Parsing of chart parameters when adding a chart by link',
        async ({page}: {page: Page}) => {
            const dashboardPage = new DashboardPage({page});

            await dashboardPage.enterEditMode();
            await page.click(slct(ControlQA.controlSettings));

            // finding the current element with a link to the chart to attach parameters to it
            const chartLinkEl = await dashboardPage.waitForSelector(slct(NavigationInputQA.Open));

            expect(chartLinkEl).not.toBeNull();

            // getting the link from the button
            const chartLink = await chartLinkEl?.getAttribute('href');

            expect(chartLink).not.toBeNull();

            // remember the parameter values before parsing the link
            const currentParams = await getParamsFromPage(page);

            const paramsString = DASH_PARAMS.map(([key, value]) => `${key}=${value}`);
            const chartLinkWithParams = chartLink + '?' + paramsString.join('&');

            // click the add chart button on the link
            await page.click(slct(NavigationInputQA.Link));

            // specifying the link
            await page.fill(`${slct(NavigationInputQA.Input)} input`, chartLinkWithParams);

            // adding
            await page.click(slct(NavigationInputQA.Apply));

            let actualParams: string[] = [];

            // expecting the number of parameters equal to the sum of [number of parameters before] + [number of added]
            await waitForCondition(async () => {
                actualParams = await getParamsFromPage(page);

                return actualParams.length === currentParams.length + paramsString.length;
            });

            // checking the complete match of the old parameters + added with the current ones on the page
            expect(actualParams).toEqual(currentParams.concat(paramsString));

            // adding a new tab with empty parameters
            await page.click(slct(TabMenuQA.Add));

            // click the add chart button on the link
            await page.click(slct(NavigationInputQA.Link));

            // specifying the link
            await page.fill(`${slct(NavigationInputQA.Input)} input`, chartLinkWithParams);

            // adding
            await page.click(slct(NavigationInputQA.Apply));

            actualParams = [];

            // expecting the number of parameters to be equal to [number of added]
            await waitForCondition(async () => {
                actualParams = await getParamsFromPage(page);

                return actualParams.length === paramsString.length;
            });

            // checking the complete match of the added parameters with the current ones on the page
            expect(actualParams).toEqual(paramsString);

            await page.click(slct(DialogDashWidgetQA.Cancel));

            await dashboardPage.exitEditMode();
            await dashboardPage.deleteDashFromViewMode();
        },
    );

    datalensTest(
        'When dragging the chart tabs, the parameters do not change',
        async ({page}: {page: Page}) => {
            const dashboardPage = new DashboardPage({page});

            await dashboardPage.enterEditMode();
            await page.click(slct(ControlQA.controlSettings));

            await addParam(page, {title: 'paramDnDCheck', value: 'valueDnDCheck'});

            // getting the current parameters of the first tab
            const firstTabParams = await getParamsFromPage(page);

            const currentTabMenuItems = await page.$$(slct(TabMenuQA.Item));

            // adding a new tab with empty parameters
            await page.click(slct(TabMenuQA.Add));

            let actualTabMenuItems;

            // expecting that the number of chart tabs has increased by 1
            await waitForCondition(async () => {
                actualTabMenuItems = await page.$$(slct(TabMenuQA.Item));

                return actualTabMenuItems.length === currentTabMenuItems.length + 1;
            });

            // dragging the first tab to the place of the added one
            await dragAndDropListItem(page, {
                listSelector: slct(TabMenuQA.List),
                sourceIndex: 0,
                targetIndex: 1,
            });

            actualTabMenuItems = await page.$$(slct(TabMenuQA.Item));

            // switching to the second tab (with the old parameters)
            await actualTabMenuItems[1].click();

            const actualParams = await getParamsFromPage(page);

            // the parameters should remain unchanged
            expect(actualParams).toEqual(firstTabParams);

            await page.click(slct(DialogDashWidgetQA.Cancel));

            await dashboardPage.exitEditMode();
            await dashboardPage.deleteDashFromViewMode();
        },
    );

    datalensTest('Correct addition/removal of parameters', async ({page}: {page: Page}) => {
        const dashboardPage = new DashboardPage({page});

        await dashboardPage.enterEditMode();
        await page.click(slct(ControlQA.controlSettings));

        // getting the current chart parameters
        const currentParams = await getParamsFromPage(page);

        const newParamForDelete = ['paramDelete', 'valueDelete'];
        const newParam = ['paramNew', 'valueNew'];

        // adding two parameters
        await addParam(page, {title: newParamForDelete[0], value: newParamForDelete[1]});
        await addParam(page, {title: newParam[0], value: newParam[1]});

        // adding a second parameter with the same name and value
        await addParam(page, {title: newParam[0], value: newParam[1]});

        let actualParams = await getParamsFromPage(page);

        // checking for the existence of two parameters on the page
        expect(actualParams).toEqual(
            currentParams.concat([newParamForDelete.join('='), newParam.join('=')]),
        );

        // delete the parameter
        await removeParam(page, newParamForDelete[0]);

        actualParams = await getParamsFromPage(page);

        // checking that there is no longer a remote one in the parameters on the page
        expect(actualParams).toEqual(currentParams.concat([newParam.join('=')]));

        await page.click(slct(DialogDashWidgetQA.Cancel));

        // adding parameters to a new chart
        await dashboardPage.clickAddChart();

        // adding two parameters
        await addParam(page, {title: newParamForDelete[0], value: newParamForDelete[1]});
        await addParam(page, {title: newParam[0], value: newParam[1]});

        // adding a second parameter with the same name and value
        await addParam(page, {title: newParam[0], value: newParam[1]});

        actualParams = await getParamsFromPage(page);

        // checking for the presence of two parameters on the page
        expect(actualParams).toEqual([newParamForDelete.join('='), newParam.join('=')]);

        // deleting the parameter
        await removeParam(page, newParamForDelete[0]);

        actualParams = await getParamsFromPage(page);

        // checking that there is no longer a remote one in the parameters on the page
        expect(actualParams).toEqual([newParam.join('=')]);

        await page.click(slct(DialogDashWidgetQA.Cancel));

        await dashboardPage.exitEditMode();
        await dashboardPage.deleteDashFromViewMode();
    });

    datalensTest(
        'Validation check when adding dashboard and external selector parameters',
        async ({page}: {page: Page}) => {
            const dashboardPage = new DashboardPage({page});

            await addExternalSelector(dashboardPage);

            const validParam = ['check_valid_param', 'value'];
            const invalidParamWithUnderscore = ['_check_error_param', ''];
            const invalidParamWithRestricted = [RESTRICTED_PARAM_NAMES[0], ''];

            // adding a parameter with _ (underline)
            await addParam(page, {
                title: invalidParamWithUnderscore[0],
                value: invalidParamWithUnderscore[1],
            });

            // adding a valid parameter
            await addParam(page, {title: validParam[0], value: validParam[1]});

            let actualParams = await getParamsFromPage(page);

            // checking if there is only one valid parameter on the page
            expect(actualParams).toEqual([validParam.join('=')]);

            // closing the dialog for add a selector
            await page.click(slct(ControlQA.dialogControlCancelBtn));

            const dashboardSettings = new DashboardSettings(page);

            // opening the dashboard settings
            await dashboardSettings.open();

            // adding a parameter with _ (underline)
            await addParam(page, {
                title: invalidParamWithUnderscore[0],
                value: invalidParamWithUnderscore[1],
            });

            // adding a forbidden parameter
            await addParam(page, {
                title: invalidParamWithRestricted[0],
                value: invalidParamWithRestricted[1],
            });

            // adding a valid parameter
            await addParam(page, {title: validParam[0], value: validParam[1]});

            actualParams = await getParamsFromPage(page);

            // checking if there is only one valid parameter on the page
            expect(actualParams).toEqual([validParam.join('=')]);

            // closing the dashboard settings
            await dashboardSettings.close();

            await dashboardPage.exitEditMode();
            await dashboardPage.deleteDashFromViewMode();
        },
    );

    datalensTest(
        'The unreleased=1 parameter shows the unpublished version of the editor chart',
        async ({page}: {page: Page}) => {
            const dashboardPage = new DashboardPage({page});

            await addExternalSelector(dashboardPage);

            await page.click(slct(NavigationInputQA.Link));

            // specifying the link
            await page.fill(
                `${slct(NavigationInputQA.Input)} input`,
                RobotChartsEditorUrls.SelectorWithUnreleasedLogic,
            );

            // adding
            await page.click(slct(NavigationInputQA.Apply));

            // adding the unreleased parameter
            await addParam(page, {title: 'unreleased', value: '1'});

            // adding a selector to the dashboard
            await page.click(slct(ControlQA.dialogControlApplyBtn));

            await dashboardPage.clickSaveButton();

            // checking the correct presence of a selector with unpublished changes
            await dashboardPage.waitForSelector(
                `${slct(ControlQA.controlLabel)} >> text=Unreleased version`,
            );

            await dashboardPage.deleteDashFromViewMode();
        },
    );
});
