import { DocAPI, UserAPI } from "app/common/UserAPI";
import difference from 'lodash/difference';
import { assert, driver } from "mocha-webdriver";
import * as gu from "test/nbrowser/gristUtils";
import { setupTestSuite } from "test/nbrowser/testUtils";

describe("Timing", function () {
  this.timeout(20000);
  const cleanup = setupTestSuite();

  let docApi: DocAPI;
  let userApi: UserAPI;
  let docId: string;
  let session: gu.Session;

  before(async () => {
    session = await gu.session().teamSite.login();
    docId = await session.tempNewDoc(cleanup);
    userApi = session.createHomeApi();
    docApi = userApi.getDocAPI(docId);
  });

  async function assertOn() {
    await gu.waitToPass(async () => {
      assert.equal(await timingText.text(), "Timing is on...");
    });

    assert.isTrue(await stopTiming.visible());
    assert.isFalse(await startTiming.present());
  }

  async function assertOff() {
    await gu.waitToPass(async () => {
      assert.equal(await timingText.text(), "Find slow formulas");
    });
    assert.isTrue(await startTiming.visible());
    assert.isFalse(await stopTiming.present());
  }


  it("should allow to start session", async function () {
    await gu.openDocumentSettings();
    // Make sure we see the timing button.
    await assertOff();

    // Start timing.
    await startTiming.click();

    // Wait for modal.
    await modal.wait();

    // We have two options.
    assert.isTrue(await optionStart.visible());
    assert.isTrue(await optionReload.visible());

    // Start is selected by default.
    assert.isTrue(await optionStart.checked());
    assert.isFalse(await optionReload.checked());

    await modalConfirm.click();

    await assertOn();
  });

  it('should reflect that in the API', async function() {
    assert.equal(await docApi.timing().then(x => x.status), 'active');
  });

  it('should stop session from outside', async function() {
    await docApi.stopTiming();
    await assertOff();
  });

  it('should start session from API', async function() {
    await docApi.startTiming();

    // Add new record through the API (to trigger formula calculations).
    await userApi.applyUserActions(docId, [
      ['AddRecord', 'Table1', null, {}]
    ]);
  });

  it('should show result and stop session', async function() {
    // The stop button is actually stop and show results, and it will open new window in.
    const myTab = await gu.myTab();
    const tabs = await driver.getAllWindowHandles();
    await stopTiming.click();

    // Now new tab will be opened, and the timings will be stopped.
    await gu.waitToPass(async () => {
      assert.equal(await docApi.timing().then(x => x.status), 'disabled');
    });

    // Find the new tab.
    const newTab = difference(await driver.getAllWindowHandles(), tabs)[0];
    assert.isDefined(newTab);
    await driver.switchTo().window(newTab);

    // Sanity check that we see some results.
    assert.isTrue(await driver.findContentWait('div', 'Formula timer', 1000).isDisplayed());

    await gu.waitToPass(async () => {
      assert.equal(await gu.getCell(0, 1).getText(), 'Table1');
    });

    // Switch back to the original tab.
    await myTab.open();

    // Make sure controls are back to the initial state.
    await assertOff();

    // Close the new tab.
    await driver.switchTo().window(newTab);
    await driver.close();
    await myTab.open();
  });

  it("should allow to time the document load", async function () {
    await assertOff();

    await startTiming.click();
    await modal.wait();

    // Check that cancel works.
    await modalCancel.click();
    assert.isFalse(await modal.present());
    await assertOff();

    // Open modal once again but this time select reload.
    await startTiming.click();
    await optionReload.click();
    assert.isTrue(await optionReload.checked());
    await modalConfirm.click();

    // We will see spinner.
    await gu.waitToPass(async () => {
      await driver.findContentWait('div', 'Loading timing data.', 1000);
    });

    // We land on the timing page in the same tab.
    await gu.waitToPass(async () => {
      assert.isTrue(await driver.findContentWait('div', 'Formula timer', 1000).isDisplayed());
      assert.equal(await gu.getCell(0, 1).getText(), 'Table1');
    });

    // Refreshing this tab will move us to the settings page.
    await driver.navigate().refresh();
    await gu.waitForUrl('/settings');
  });

  it('clears virtual table when navigated away', async function() {
    // Start timing and go to results.
    await startTiming.click();
    await modal.wait();
    await optionReload.click();
    await modalConfirm.click();

    // Wait for the results page.
    await gu.waitToPass(async () => {
      assert.isTrue(await driver.findContentWait('div', 'Formula timer', 1000).isDisplayed());
      assert.equal(await gu.getCell(0, 1).getText(), 'Table1');
    });

    // Now go to the raw data page, and make sure we see only Table1.
    await driver.find('.test-tools-raw').click();
    await driver.findWait('.test-raw-data-list', 2000);
    assert.deepEqual(await driver.findAll('.test-raw-data-table-id', e => e.getText()), ['Table1']);
  });

  it('should be disabled for non-owners', async function() {
    await userApi.updateDocPermissions(docId, {users: {
      [gu.translateUser('user2').email]: 'editors',
    }});

    const session = await gu.session().teamSite.user('user2').login();
    await session.loadDoc(`/doc/${docId}`);
    await gu.openDocumentSettings();

    const start = driver.find('.test-settings-timing-start');
    assert.equal(await start.isPresent(), true);

    // Check that we have an informative tooltip.
    await start.mouseMove();
    assert.match(await driver.findWait('.test-tooltip', 2000).getText(), /Only available to document owners/);

    // Nothing should happen on click. We click the location rather than the element, since the
    // element isn't actually clickable.
    await start.mouseMove();
    await driver.withActions(a => a.press().release());
    await driver.sleep(100);
    assert.equal(await driver.find(".test-settings-timing-modal").isPresent(), false);
  });
});

const element = (testId: string) => ({
  element() {
    return driver.find(testId);
  },
  async wait() {
    await driver.findWait(testId, 1000);
  },
  async visible() {
    return await this.element().isDisplayed();
  },
  async present() {
    return await this.element().isPresent();
  }
});

const label = (testId: string) => ({
  ...element(testId),
  async text() {
    return this.element().getText();
  },
});

const button = (testId: string) => ({
  ...element(testId),
  async click() {
    await gu.scrollIntoView(this.element());
    await this.element().click();
  },
});

const option = (testId: string) => ({
  ...button(testId),
  async checked() {
    return 'true' === await this.element().findClosest("label").find("input[type='checkbox']").getAttribute('checked');
  }
});

const startTiming = button(".test-settings-timing-start");
const stopTiming = button(".test-settings-timing-stop");
const timingText = label(".test-settings-timing-desc");
const modal = element(".test-settings-timing-modal");
const optionStart = option('.test-settings-timing-modal-option-adhoc');
const optionReload = option('.test-settings-timing-modal-option-reload');
const modalConfirm = button('.test-settings-timing-modal-confirm');
const modalCancel = button('.test-settings-timing-modal-cancel');
