/* @flow */
/* eslint no-console: off, import/no-nodejs-modules: off */

import { join } from 'path';

// $FlowFixMe
import puppeteer from 'puppeteer';
import { withMock, methods } from 'mocketeer';

import { HEADLESS, DEVTOOLS, DOMAIN, RETRIES, LOG, TIMEOUT } from './config';

export function log(...args : $ReadOnlyArray<mixed>) {
    if (LOG) {
        console.info(...args);
    }
}

function normalizeString(str : string, reg : RegExp) : string {
    return str.replace(reg, '_').replace(/^_+/, '').replace(/_+$/, '');
}

function getDate() : string {
    return normalizeString(new Date().toISOString(), /[^0-9]+/g);
}

export async function screenshot(page : Object, name : string) : Promise<void> {
    if (!page.screenshot && page._frameManager) {
        page = page._frameManager.page();
    }

    await page.screenshot({
        path: join(__dirname, 'screenshots', `${ getDate() }_${ normalizeString(name, /[^a-zA-Z_0-9-]+/g) }.png`)
    });
}

export async function getElement(page : Object, selector : string) : Promise<Object> {
    const element = await page.$(selector);

    if (!element) {
        throw new Error(`Could not find element: ${ selector }`);
    }

    return element;
}

export async function retry<T>(handler : () => Promise<T>, attempts : number) : Promise<T> {
    try {
        return await handler();
    } catch (err) {
        attempts -= 1;
        if (!attempts) {
            throw err;
        }

        log(`Failed with error, retrying (${ attempts } attempts remaining):`, err);
        return retry(handler, attempts);
    }
}

export async function withPage(handler : ({ page : Object }) => Promise<void>) : Promise<void> {
    const browser = await puppeteer.launch({
        headless:          HEADLESS,
        devtools:          DEVTOOLS,
        ignoreHTTPSErrors: true,
        args:              [
            '--no-sandbox'
        ]
    });

    try {
        const page = await browser.newPage();

        await withMock(
            methods.get(DOMAIN, {
                status: 200,
                body:   '<head></head><body></body>'
            }),
            page,
            async () => {
                await retry(async () => {
                    await page.goto(DOMAIN);
                    await handler({ browser, page });
                }, RETRIES);
            }
        );

        
    } catch (err) {
        await browser.close();
        throw err;
    }
    
    await browser.close();
}

export async function findFrameByName(page : Object, name : string) : Object {
    log('FIND FRAME', name);
    const namedFrame = (await page.frames()).find(frame => {
        return frame.name().startsWith(name);
    });

    if (namedFrame) {
        return namedFrame;
    }

    const element = await page.$(`iframe[name=${ name }]`);

    try {
        const frame = await element.contentFrame();
        if (frame) {
            log('FOUND FRAME', name);
            await screenshot(page, `frame_opened`);
            return page;
        }
    } catch (err) {
        // pass
    }

    throw new Error(`Could not find frame with name: ${ name }`);
}

export async function waitForPopup(page : Object, opts? : { timeout? : number } = {}) : Promise<Object> {
    const { timeout = TIMEOUT * 1000 } = opts;
    log('WAIT FOR POPUP');
    const popupPage = await new Promise((resolve, reject) => {
        const timer = setTimeout(() => {
            reject(new Error(`Timed out waiting for popup window for ${ timeout }ms`));
        }, timeout);
        page.once('popup', (popup) => {
            clearTimeout(timer);
            log('POPUP OPENED');
            resolve(popup);
        });
    });
    await screenshot(popupPage, `popup_opened`);
    return popupPage;
}

export async function delay(time : number) : Promise<void> {
    return await new Promise((resolve) => {
        setTimeout(resolve, time);
    });
}

export async function waitForElement(page : Object, selector : string, opts? : { timeout? : number } = {}) : Promise<void> {
    const { timeout = TIMEOUT * 1000 } = opts;
    log('WAIT FOR', selector);
    await screenshot(page, `wait_for_${ selector }`);
    await page.waitForSelector(selector, { timeout });
}

export async function waitAndType(page : Object, selector : string, text : string, opts? : { timeout? : number } = {}) : Promise<void> {
    const { timeout = TIMEOUT * 1000 } = opts;
    await waitForElement(page, selector, { timeout });
    await delay(1000);
    await screenshot(page, `pre_type_${ selector }`);
    log('TYPE', selector, text);
    await screenshot(page, `post_click_${ selector }`);
    await page.type(selector, text);
}

export async function waitAndClick(page : Object, selector : string, opts? : { timeout? : number } = {}) : Promise<void> {
    const { timeout = TIMEOUT * 1000 } = opts;
    await waitForElement(page, selector, { timeout });
    await delay(1000);
    await screenshot(page, `pre_click_${ selector }`);
    log('CLICK', selector);
    await screenshot(page, `post_click_${ selector }`);
    await page.click(selector);
}

export async function elementExists(page : Object, selector : string) : Promise<boolean> {
    log('CHECK EXISTS', selector);

    try {
        await waitForElement(page, selector, { timeout: 1000 });
    } catch (err) {
        // pass
    }

    try {
        if (await page.$(selector)) {
            log('EXISTS', selector);
            return true;
        }
    } catch (err) {
        // pass
    }

    log('DOES NOT EXIST', selector);
    return false;
}

export async function waitForClose(page : Object, opts? : { timeout? : number } = {}) : Promise<void> {
    const { timeout = TIMEOUT * 1000 } = opts;

    const start = Date.now();

    log('WAIT FOR CLOSE');

    await screenshot(page, `wait_close`);

    return await new Promise((resolve, reject) => {
        const interval = setInterval(async () => {
            const elapsed = Date.now() - start;

            if (await page.isClosed()) {
                clearInterval(interval);
                resolve();
            }

            if (elapsed > timeout) {
                clearInterval(interval);
                return reject(new Error(`Timed out after ${ timeout }ms waiting for page to close`));
            }
        }, 500);
    });
}

export async function pause() : Promise<void> {
    return await new Promise(() => {
        // pass
    });
}

@@ -4152,32 +4152,46 @@
 %7B selector %7D%60);%0A
+    try %7B%0A    
     await page.w
@@ -4225,24 +4225,196 @@
 timeout %7D);%0A
+    %7D catch (err) %7B%0A        await screenshot(page, %60wait_for_$%7B selector %7D_failed%60);%0A        throw err;%0A    %7D%0A    await screenshot(page, %60wait_for_$%7B selector %7D_success%60);%0A
 %7D%0A%0Aexport as

Add screenshots for success and error case of wait for element