import {ChildProcess, spawn} from 'child_process';
import {assert, driver, IMochaServer, Key, useServer} from 'mocha-webdriver';
import fetch from 'node-fetch';
// tslint:disable:no-console

class MkdocsServer implements IMochaServer {
  public _proc?: ChildProcess;
  private _exitPromise: Promise<number|string> = Promise.resolve("not-started");

  public async start() {
    this._proc = spawn(process.env.MKDOCS_BINARY || 'env/bin/mkdocs', ['serve', '-a', 'localhost:8000'],
      {cwd: '..', stdio: 'inherit'});
    this._exitPromise = exitPromise(this._proc);
    await this.waitServerReady(20000);
  }
  public async stop() {
    if (this._proc) {
      this._proc.kill('SIGINT');
      const res = await this._exitPromise;
      if (res !== 0) { console.log("ERROR: could not run mkdocs: %s", res); }
    }
  }
  /**
   * Wait for the server to be up and responsitve, for up to `ms` milliseconds.
   */
  public async waitServerReady(ms: number): Promise<void> {
    await driver.wait(() => Promise.race([
      this.isServerReady(),
      this._exitPromise.then(() => { throw new Error("Server exited while waiting for it"); }),
    ]), ms);
  }
  public isServerReady(): Promise<boolean> {
    return fetch(this.getHost()).then((r) => r.ok).catch(() => false);
  }
  public getHost() {
    return "http://localhost:8000";
  }
}

describe('mkdocs_windmill', () => {
  const server = new MkdocsServer();
  useServer(server);

  before(async function() {
    this.timeout(60000);      // Set a longer default timeout.
    await driver.get(server.getHost());
  });

  beforeEach(async () => {
    await driver.switchTo().defaultContent();
  });

  it('should load the homepage', async () => {
    // Menu has links for items, including the first one.
    assert.equal(await driver.findContent('.wm-toc-pane .wm-toc-text', /Usage/).getTagName(), 'a');
    assert.equal(await driver.findContent('.wm-toc-pane .wm-toc-text', /Cust/).getTagName(), 'a');

    // In certain configurations, the first page isn't selected until clicked in
    // (this could be fixed, but for now we just test that it works once clicked).
    await driver.findContent('.wm-toc-pane .wm-article-link', /Cust/).click();
    await driver.findContent('.wm-toc-pane .wm-article-link', /Usage/).click();
    assert.equal(await driver.find('.wm-current').getText(), 'Usage');

    // The page table-of-contents is expanded in the menu.
    assert.includeMembers(await driver.findAll('.wm-page-toc .wm-article-link',
      (el) => el.getText()), ['About', 'Installation']);

    // Check that homepage is visible in iframe
    await driver.switchTo().frame(driver.find('.wm-article'));
    assert.equal(await driver.find('h1').getText(), 'Windmill theme');
  });

  it('should go through all pages with nav links', async function() {
    await driver.switchTo().frame(driver.find('.wm-article'));
    const next = () => driver.findContent('.wm-article-nav a', /Next/).click();
    const prev = () => driver.findContent('.wm-article-nav a', /Previous/).click();
    await next();
    assert.equal(await driver.find('h1').getText(), 'Customization');
    await next();
    assert.equal(await driver.find('h1').getText(), 'Heading A');
    await next();
    assert.equal(await driver.find('h1').getText(), 'Heading A1');
    await next();
    assert.equal(await driver.find('h1').getText(), 'Heading A2');
    await prev();
    assert.equal(await driver.find('h1').getText(), 'Heading A1');
    await prev();
    assert.equal(await driver.find('h1').getText(), 'Heading A');
    await prev();
    assert.equal(await driver.find('h1').getText(), 'Customization');
    await prev();
    assert.equal(await driver.find('h1').getText(), 'Windmill theme');
  });

  it('should follow links in search dropdown', async function() {
    await driver.find('#mkdocs-search-query').doSendKeys('extra');
    await assert.includeMembers(await driver.findAll('#mkdocs-search-results .search-title',
      (el) => el.getText()), ['Extra configuration options', 'Usage']);
    await driver.findContent('#mkdocs-search-results .search-title', /Extra conf/).click();

    await driver.switchTo().frame(driver.find('.wm-article'));
    assert.match(await driver.find('h1').getText(), /Customization/);
    await driver.switchTo().defaultContent();

    await driver.navigate().back();
    await driver.find('#mkdocs-search-query').doClick();
    await driver.findContent('#mkdocs-search-results .search-title', /Usage/).click();

    await driver.switchTo().frame(driver.find('.wm-article'));
    assert.match(await driver.find('h1').getText(), /Windmill theme/);
    await driver.switchTo().defaultContent();
  });

  it('should follow links in search results page', async function() {
    await driver.find('#mkdocs-search-query').doClear().doSendKeys('extra', Key.ENTER);

    // Switch to the iframe with results. The actual elements to interact with are similar to the
    // case above, because the search code is actually reused.
    await driver.switchTo().frame(driver.find('.wm-article'));

    await assert.includeMembers(await driver.findAll('#mkdocs-search-results .search-title',
      (el) => el.getText()), ['Extra configuration options', 'Usage']);
    await driver.findContent('#mkdocs-search-results .search-title', /Extra conf/).click();

    assert.match(await driver.find('h1').getText(), /Customization/);

    await driver.switchTo().defaultContent();
    await driver.navigate().back();

    await driver.find('#mkdocs-search-query').doClick().doSendKeys(Key.ENTER);

    await driver.switchTo().frame(driver.find('.wm-article'));
    await driver.findContent('#mkdocs-search-results .search-title', /Usage/).click();

    assert.match(await driver.find('h1').getText(), /Windmill theme/);
  });
});

export function exitPromise(child: ChildProcess): Promise<number|string> {
  return new Promise((resolve, reject) => {
    // Called if process could not be spawned, or could not be killed(!), or sending a message failed.
    child.on('error', reject);
    child.on('exit', (code: number, signal: string) => resolve(signal || code));
  });
}