const config = require('./config');
const assert = require('assert');
const scope = require('./scope');
const pages = require('./pages');
const selectors = require('./selectors');

const pending = callback => {
    callback(null, 'pending');
};

const delay = duration => new Promise(resolve => setTimeout(resolve, duration));

const wait = async timeInSeconds => {
    const time = Math.round(parseFloat((''+timeInSeconds).replace(',', '.')) * 1000);
    await delay(time);
};

const clearStorages = async () => {
    if (scope.context && scope.context.page) {
        await scope.context.page.evaluate(() => {
            localStorage.clear();
            sessionStorage.clear();
        });
    }
}

const visitPage = async (page) => {
    if (!scope.browser) {
        scope.browser = await scope.driver.launch({
            headless: config.headless,
            slowMo: config.slowMo,
            args: [
            ],
        });
    }
    scope.context.page = await scope.browser.newPage();
    scope.context.page.setViewport({ width: 1280, height: 1024 });
    const url = scope.host + pages[page];
    const visit = await scope.context.page.goto(url, {
        waitUntil: 'networkidle2'
    });
    return visit;
};

const visitPageIncognito = async (page) => {
    if (!scope.browser) {
        scope.browser = await scope.driver.launch({
            headless: config.headless,
            slowMo: config.slowMo,
            args: [
                '--incognito',
            ],
        });
    }
    scope.context.page = await scope.browser.newPage();
    scope.context.page.setViewport({ width: 1280, height: 1024 });
    const url = scope.host + pages[page];
    const visit = await scope.context.page.goto(url, {
        waitUntil: 'networkidle2'
    });
    return visit;
};

const clickLink = async (text) => {
    const { page } = scope.context;
    const [link] = await page.$x(`//button[contains(., '${text}')]|//a[contains(., '${text}')]`);
    if (link) {
        await link.click();
    } else {
        throw new Error(
            `Can not find link with text '${text}'`
        );
    }
};

const clickLinkAt = async (text, wrapper) => {
    const { page } = scope.context;
    const [link] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]|a[contains(., '${text}')]`);
    if (link) {
        await link.click();
    } else {
        throw new Error(
            `Can not find link with text '${text}' in the wrapper ${wrapper}`
        );
    }
};

const pressButton = async text => {
    const { page } = scope.context;
    const [button] = await page.$x(`//button[contains(., '${text}')]|//a[contains(., '${text}')]`);
    if (button) {
        await button.click();
    } else {
        throw new Error(
            `Can not find button with text '${text}'`
        );
    }
};

const pressButtonAt = async (text, wrapper) => {
    const { page } = scope.context;
    const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]|a[contains(., '${text}')]`);
    if (button) {
        await button.click();
    } else {
        throw new Error(
            `Can not find button with text '${text}' in the wrapper ${wrapper}`
        );
    }
};

const modalOpened = async text => {
    await delay(250);
    const { page } = scope.context;
    const waitModal = await page.waitForSelector('.modal');
    if (!waitModal) {
        throw new Error(
            `Can not find modal`
        );
    }
    await waitModal;
    const [modalWithTitle] = await page.$x(`//*[contains(@class, 'modal')]//*[contains(@class, 'modal-title')][contains(., '${text}')]`);
    if (!modalWithTitle) {
        throw new Error(
            `Can not find modal with title '${text}'`
        );
    }
}

const modalClosed = async text => {
    await delay(250);
    const { page } = scope.context;
    const [modal] = await page.$x(`//*[contains(@class, 'modal')]`);
    if (modal) {
        const [modalWithTitle] = await page.$x(`//*[contains(@class, 'modal')]//*[contains(@class, 'modal-title')][contains(., '${text}')]`);
        if (modalWithTitle) {
            throw new Error(
                `Modal with title '${text}' still opened`
            );
        }
    }
}

const shouldSeeText = async text => {
    const { page } = scope.context;
    const content = await page.content();
    if (!content.includes(text))
        throw new Error(
            `Expected page to contain text: ${text}, but page contains only: ${content}`
        );
};

const shouldSeeTextAt = async (text, wrapper) => {
    const { page } = scope.context;
    const [el] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//*[contains(., '${text}')]`);
    if (!el) {
        const content = await page.content();
        throw new Error(
            `Expected wrapper ${wrapper} to contain text: ${text}, but page contains only: ${content}`
        );
    }
};

const shouldNotSeeText = async text => {
    const { page } = scope.context;
    const content = await page.content();
    if (content.includes(text))
        throw new Error(
            `Expected page to not contain text: ${text}, but page contains: ${content}`
        );
};

const shouldNotSeeTextAt = async (text, wrapper) => {
    const { page } = scope.context;
    const [el] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//*[contains(., '${text}')]`);
    if (el) {
        const content = await page.content();
        throw new Error(
            `Expected wrapper ${wrapper} to not contain text: ${text}, but page contains: ${content}`
        );
    }
};

const buttonShouldBeDisabled = async text => {
    const { page } = scope.context;
    const [button] = await page.$x(`//button[contains(., '${text}')]`);
    if (button) {
        let valueHandle = await button.getProperty('disabled');
        assert.strictEqual(await valueHandle.jsonValue(), true);
    } else {
        throw new Error(
            `Can not find page button with text '${text}'`
        );
    }
};

const buttonShouldBeEnabled = async text => {
    const { page } = scope.context;
    const [button] = await page.$x(`//button[contains(., '${text}')]`);
    if (button) {
        let valueHandle = await button.getProperty('disabled');
        assert.strictEqual(await valueHandle.jsonValue(), false);
    } else {
        throw new Error(
            `Can not find page button with text '${text}'`
        );
    }
};

const buttonShouldBeDisabledAt = async (text, wrapper) => {
    const { page } = scope.context;
    const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]`);
    if (button) {
        let valueHandle = await button.getProperty('disabled');
        assert.strictEqual(await valueHandle.jsonValue(), true);
    } else {
        throw new Error(
            `Can not find page button with text '${text}' in the wrapper ${wrapper}`
        );
    }
};

const buttonShouldBeEnabledAt = async (text, wrapper) => {
    const { page } = scope.context;
    const [button] = await page.$x(`//*[contains(@class, '${selectors.wrappers[wrapper]}')]//button[contains(., '${text}')]`);
    if (button) {
        let valueHandle = await button.getProperty('disabled');
        assert.strictEqual(await valueHandle.jsonValue(), false);
    } else {
        throw new Error(
            `Can not find page button with text '${text}' in the wrapper ${wrapper}`
        );
    }
};

const fillInputField = async (value, field) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
    await fieldPresent;
    await page.focus(`input[name='${field}']`);
    const inputValue = await page.$eval(selectors.inputs[field], el => el.value);
    for (let i = 0; i < inputValue.length; i++) {
        await page.keyboard.press('Backspace');
    }
    await page.type(selectors.inputs[field], value, { delay: 1 });
    await page.$eval(selectors.inputs[field], e => e.blur());
    return;
};


const fillInputFieldAt = async (value, field, wrapper) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
    await fieldPresent;
    await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
    const inputValue = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.value);
    for (let i = 0; i < inputValue.length; i++) {
        await page.keyboard.press('Backspace');
    }
    await page.type(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, value, { delay: 1 });
    await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
    return;
};

const setCheckboxField = async (field) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
    await fieldPresent;
    const isChecked = await page.$eval(selectors.inputs[field], el => el.checked)
    if (!isChecked) {
        await page.focus(selectors.inputs[field]);
        await page.$eval(selectors.inputs[field], check => check.click());
        await page.$eval(selectors.inputs[field], e => e.blur());
    }
    return;
};

const unsetCheckboxField = async (field) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    const fieldPresent = await page.waitForSelector(selectors.inputs[field]);
    await fieldPresent;
    const isChecked = await page.$eval(selectors.inputs[field], el => el.checked)
    if (isChecked) {
        await page.focus(selectors.inputs[field]);
        await page.$eval(selectors.inputs[field], check => check.click());
        await page.$eval(selectors.inputs[field], e => e.blur());
    }
    return;
};

const setCheckboxFieldAt = async (field, wrapper) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
    await fieldPresent;
    const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.checked)
    if (!isChecked) {
        await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, check => check.click());
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
    }
    return;
};

const unsetCheckboxFieldAt = async (field, wrapper) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    const fieldPresent = await page.waitForSelector(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
    await fieldPresent;
    const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, el => el.checked)
    if (isChecked) {
        await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`);
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, check => check.click());
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.inputs[field]}`, e => e.blur());
    }
    return;
};

const chooseRadioOption = async (option, field) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    if (!selectors.radio_groups[field]) {
        throw new Error(
            `Radio group '${field}' has no registered selector`
        );
    }
    await shouldSeeText(option);
    if (!selectors.radio_groups[field].options[option]) {
        throw new Error(
            `Radio group '${field}' has no registered option ${option}`
        );
    }
    const isChecked = await page.$eval(`${selectors.radio_groups[field].options[option]}`, el => el.checked)
    if (!isChecked) {
        await page.focus(`${selectors.radio_groups[field].options[option]}`);
        await page.$eval(`${selectors.radio_groups[field].options[option]}`, check => check.click());
        await page.$eval(`${selectors.radio_groups[field].options[option]}`, e => e.blur());
    }
};

const chooseRadioOptionAt = async (option, field, wrapper) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    if (!selectors.radio_groups[field]) {
        throw new Error(
            `Radio group '${field}' has no registered selector`
        );
    }
    await shouldSeeText(option);
    if (!selectors.radio_groups[field].options[option]) {
        throw new Error(
            `Radio group '${field}' has no registered option ${option}`
        );
    }
    const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, el => el.checked)
    if (!isChecked) {
        await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`);
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, check => check.click());
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.radio_groups[field].options[option]}`, e => e.blur());
    }
};

const setCheckboxGroupOption = async (option, field) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    if (!selectors.checkbox_groups[field]) {
        throw new Error(
            `Checkbox group '${field}' has no registered selector`
        );
    }
    await shouldSeeText(option);
    if (!selectors.checkbox_groups[field].options[option]) {
        throw new Error(
            `Checkbox group '${field}' has no registered option ${option}`
        );
    }
    const isChecked = await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
    if (!isChecked) {
        await page.focus(`${selectors.checkbox_groups[field].options[option]}`);
        await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, check => check.click());
        await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
    }
};

const unsetCheckboxGroupOption = async (option, field) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    if (!selectors.checkbox_groups[field]) {
        throw new Error(
            `Checkbox group '${field}' has no registered selector`
        );
    }
    await shouldSeeText(option);
    if (!selectors.checkbox_groups[field].options[option]) {
        throw new Error(
            `Checkbox group '${field}' has no registered option ${option}`
        );
    }
    const isChecked = await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
    if (isChecked) {
        await page.focus(`${selectors.checkbox_groups[field].options[option]}`);
        await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, check => check.click());
        await page.$eval(`${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
    }
};

const setCheckboxGroupOptionAt = async (option, field, wrapper) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    if (!selectors.checkbox_groups[field]) {
        throw new Error(
            `Checkbox group '${field}' has no registered selector`
        );
    }
    await shouldSeeText(option);
    if (!selectors.checkbox_groups[field].options[option]) {
        throw new Error(
            `Checkbox group '${field}' has no registered option ${option}`
        );
    }
    const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
    if (!isChecked) {
        await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`);
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, check => check.click());
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
    }
};

const unsetCheckboxGroupOptionAt = async (option, field, wrapper) => {
    const { page } = scope.context;
    await shouldSeeText(field);
    if (!selectors.checkbox_groups[field]) {
        throw new Error(
            `Checkbox group '${field}' has no registered selector`
        );
    }
    await shouldSeeText(option);
    if (!selectors.checkbox_groups[field].options[option]) {
        throw new Error(
            `Checkbox group '${field}' has no registered option ${option}`
        );
    }
    const isChecked = await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, el => el.checked)
    if (isChecked) {
        await page.focus(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`);
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, check => check.click());
        await page.$eval(`.${selectors.wrappers[wrapper]} ${selectors.checkbox_groups[field].options[option]}`, e => e.blur());
    }
};

const jsonViewContains = async (field, value) => {
    const { page } = scope.context;
    const jsonViewPresent = await page.waitForSelector('.react-json-view');
    await jsonViewPresent;
    const aRes = await page.$$eval('.variable-row', options => options.map(option => option.textContent))
    const keyWrap = isNaN(field) ? '"' : ''
    const valWrap = isNaN(value) ? '"' : ''
    let res = false;
    aRes.forEach(e => {
        if ('true' === value || 'false' === value) {
            if (e.includes(keyWrap + field + keyWrap) && e.includes('bool' + value)) {
                res = true;
            }
        } else {
            if (e.includes(keyWrap + field + keyWrap) && e.includes(valWrap + value + valWrap)) {
                res = true;
            }
        }
    });
    if (!res) {
        throw new Error(
            `Expected jsonView var: ${field} should contains ${value}, but given ${aRes}`
        );
    }
};

const jsonViewContainsAt = async (fieldName, rowKey, rowValue) => {
    const { page } = scope.context;
    const [fieldWrapper] = await page.$x(`//*[contains(@class, 'react-json-view')]//*[contains(., '${fieldName}')]//preceding::*[contains(@class, 'object-key-val')][1]`);
    const aRes = await fieldWrapper.$$eval('.variable-row', options => options.map(option => option.textContent))
    const keyWrap = isNaN(rowKey) ? '"' : ''
    const valWrap = isNaN(rowValue) ? '"' : ''
    let res = false;
    aRes.forEach(e => {
        if ('true' === rowValue || 'false' === rowValue) {
            if (e.includes(keyWrap + rowKey + keyWrap) && e.includes('bool' + rowValue)) {
                res = true;
            }
        } else {
            if (e.includes(keyWrap + rowKey + keyWrap) && e.includes(valWrap + rowValue + valWrap)) {
                res = true;
            }
        }
    });
    if (!res) {
        throw new Error(
            `Expected jsonView var: ${fieldName} should contains: ${rowKey} => ${rowValue}, but given ${aRes}`
        );
    }
};

module.exports = {
    pending,
    delay,
    wait,
    clearStorages,
    visitPage,
    visitPageIncognito,
    shouldSeeText,
    shouldSeeTextAt,
    shouldNotSeeText,
    shouldNotSeeTextAt,
    clickLink,
    clickLinkAt,
    pressButton,
    pressButtonAt,
    buttonShouldBeDisabled,
    buttonShouldBeEnabled,
    buttonShouldBeDisabledAt,
    buttonShouldBeEnabledAt,
    fillInputField,
    setCheckboxField,
    unsetCheckboxField,
    fillInputFieldAt,
    setCheckboxFieldAt,
    unsetCheckboxFieldAt,
    chooseRadioOption,
    chooseRadioOptionAt,
    setCheckboxGroupOption,
    unsetCheckboxGroupOption,
    setCheckboxGroupOptionAt,
    unsetCheckboxGroupOptionAt,
    modalOpened,
    modalClosed,
    jsonViewContains,
    jsonViewContainsAt,
};