import { assert, driver } from 'mocha-webdriver';
import { $, gu, test } from 'test/nbrowser/gristUtil-nbrowser';

describe('Dates.ntest', function() {
  const cleanup = test.setupTestSuite(this);
  let doc;
  before(async function() {
    await gu.supportOldTimeyTestCode();
    doc = await gu.useFixtureDoc(cleanup, "Hello.grist", true);
    await gu.toggleSidePanel("left", "close");
  });

  afterEach(function() {
    return gu.checkForErrors();
  });

  it('should allow correct datetime reformatting', async function() {
    await gu.openSidePane('field');

    var cell = await gu.getCellRC(0, 0);

    // Move to the first column
    await cell.click();
    await gu.sendKeys('2008-01-10 9:20pm', $.ENTER);

    // Change type to 'DateTime'
    await gu.setType('DateTime');
    await $('.test-tz-autocomplete').wait().click();
    await gu.sendKeys($.DELETE, 'UTC', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), '2008-01-10 9:20pm');
    await $('.test-type-transform-apply').wait().click();
    await gu.waitForServer();

    // Change timezone to 'America/Los_Angeles' and check that the date is correct
    await $('.test-tz-autocomplete').wait().click();
    await gu.sendKeys('Los An', $.ENTER);
    await gu.waitForServer();
    assert.equal(await $('.test-tz-autocomplete input').val(), 'America/Los_Angeles');
    assert.equal(await cell.text(), '2008-01-10 1:20pm');

    // Change format and check that date is reformatted
    await gu.dateFormat('MMMM Do, YYYY');
    await gu.timeFormat('HH:mm:ss z');
    assert.equal(await gu.getCellRC(0, 0).text(), 'January 10th, 2008 13:20:00 PST');

    // Change to custom format and check that the date is reformatted
    await gu.dateFormat('Custom');

    await $('$Widget_dateCustomFormat .kf_text').click();
    await gu.sendKeys($.SELECT_ALL, 'dddd', $.ENTER);
    await gu.timeFormat("Custom");
    await $('$Widget_timeCustomFormat .kf_text').click();
    await gu.sendKeys($.SELECT_ALL, 'Hmm', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'Thursday 1320');
  });

  it('should include a functioning datetime editor', async function() {
    var cell = await gu.getCellRC(0, 0);

    // DateTime editor should open, separate date and time, and replace incomplete format
    // with YYYY-MM-DD
    await cell.click();
    await gu.sendKeys($.ENTER);
    assert.equal(await $('.celleditor_text_editor').first().val(), '2008-01-10');

    // Date should be changable by clicking the calendar dates
    await $('.celleditor_text_editor').first().sendKeys($.DOWN);   // Opens date picker even if window has no focus.
    await $('.datepicker .day:contains(19)').wait().click();
    await gu.sendKeys($.ENTER);
    assert.equal(await cell.text(), 'Saturday 1320');

    // Date editor should convert Moment formats to datepicker safe formats
    // Date editor should allow tabbing between date and time entry boxes
    await gu.dateFormat('MMMM Do, YYYY');
    await gu.timeFormat('h:mma');
    await cell.click();
    await gu.sendKeys($.ENTER);
    assert.deepEqual(await $('.celleditor_text_editor').array().val(),
      ['January 19th, 2008', '1:20pm']);
    await gu.sendKeys($.SELECT_ALL, 'February 20th, 2009', $.TAB, '8:15am', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'February 20th, 2009 8:15am');

    // DateTime editor should close and save value when the user clicks away
    await cell.click();
    await gu.sendKeys($.ENTER, $.SELECT_ALL, $.DELETE);
    await gu.getCellRC(0, 3).click(); // click away
    await gu.waitForServer();
    // Since only the date value was removed, the cell should give AltText of the time value
    assert.equal(await cell.text(), '8:15am');
    assert.hasClass(await cell.find('.field_clip'), 'invalid');

    // DateTime editor should close and revert value when the user presses escape
    await cell.click();
    await gu.sendKeys($.ENTER, 'April 2, 1993', $.ESCAPE);
    assert.equal(await cell.text(), '8:15am');
  });

  it('should allow correct date reformatting', async function() {
    var cell = await gu.getCellRC(0, 1);

    // Move to the first column
    await cell.click();
    await gu.sendKeys('2016-01-08', $.ENTER);

    // Change type to 'Date'
    await gu.setType('Date');
    await $('.test-type-transform-apply').wait().click();
    await gu.waitForServer(); // Make sure type is set

    // Check that the date is correct
    await $('$Widget_dateFormat').wait();
    assert.equal(await cell.text(), '2016-01-08');

    // Change format and check that date is reformatted
    await gu.dateFormat('MMMM Do, YYYY');
    await gu.waitForServer();
    assert.equal(await cell.text(), 'January 8th, 2016');

    // Try another format
    await gu.dateFormat('DD MMM YYYY');
    await gu.waitForServer();
    assert.equal(await cell.text(), '08 Jan 2016');

    // Change to custom format and check that the date is reformatted
    await gu.dateFormat('Custom');
    await $('$Widget_dateCustomFormat .kf_text').click();
    await gu.sendKeys($.SELECT_ALL, 'dddd', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'Friday');
  });

  it('should include a functioning date editor', async function() {
    var cell = await gu.getCellRC(0, 1);

    // Date editor should open and replace incomplete format with YYYY-MM-DD
    await cell.click();
    await gu.sendKeys($.ENTER);
    assert.equal(await $('.celleditor_text_editor').val(), '2016-01-08');

    // Date should be changable by clicking the calendar dates
    await $('.celleditor_text_editor').sendKeys($.DOWN);   // Opens date picker even if window has no focus.
    await $('.datepicker .day:contains(19)').wait().click();
    await gu.sendKeys($.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'Tuesday');

    // Date editor should convert Moment formats to datepicker safe formats
    // Date editor should save the date on enter press
    await gu.dateFormat('MMMM Do, YYYY');
    await cell.click();
    await gu.sendKeys($.ENTER);
    assert.equal(await $('.celleditor_text_editor').val(), 'January 19th, 2016');
    await gu.sendKeys($.SELECT_ALL, 'February 20th, 2016', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'February 20th, 2016');

    // Date editor should close and save value when the user clicks away
    await cell.click();
    await gu.sendKeys($.ENTER, $.SELECT_ALL, $.DELETE);
    await gu.getCellRC(0, 3).click(); // click away
    await gu.waitForServer();
    assert.equal(await cell.text(), '');

    // Date editor should close and revert value when the user presses escape
    await cell.click();
    await gu.sendKeys($.ENTER, 'April 2, 1993', $.ESCAPE);
    assert.equal(await cell.text(), '');
  });

  it('should reload values correctly after reopen', async function() {
    await gu.getCellRC(0, 0).click();
    await gu.sendKeys('February 20th, 2009', $.TAB, '8:15am', $.ENTER);
    await gu.getCellRC(0, 1).click();
    await gu.sendKeys('January 19th, 1968', $.ENTER);
    await gu.getCellRC(1, 1).click();
    await gu.sendKeys($.DELETE);
    await gu.waitForServer();
    await gu.getCellRC(0, 2).click();
    await gu.waitAppFocus(true);
    await gu.sendKeys('=');
    await $('.test-editor-tooltip-convert').click();
    await gu.sendKeys('$A', $.ENTER);
    await gu.waitForServer();
    await gu.waitAppFocus(true);
    await gu.getCellRC(0, 3).click();
    await gu.sendKeys('=');
    await gu.waitAppFocus(false);
    await gu.sendKeys('$B', $.ENTER);
    await gu.waitForServer();

    assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: ['A', 'B', 'C', 'D']}), [
      'February 20th, 2009 8:15am',
      'January 19th, 1968',
      '2009-02-20 08:15:00-08:00',
      '1968-01-19',
      '', '', '', ''
    ]);

    // We don't have a quick way to shutdown a document and reopen from scratch. So instead, we'll
    // make a copy of the document, and open that to test that values got saved correctly.
    // TODO: it would be good to add a way to reload document from scratch, perhaps by reloading
    // with a special URL fragment.
    await gu.copyDoc(doc.id, true);

    assert.deepEqual(await gu.getGridValues({rowNums: [1, 2], cols: ['A', 'B', 'C', 'D']}), [
      'February 20th, 2009 8:15am',
      'January 19th, 1968',
      '2009-02-20 08:15:00-08:00',
      '1968-01-19',
      '', '', '', ''
    ]);
  });

  it('should support shortcuts to insert date/time', async function() {
    await gu.openSidePane('field');
    // Check the types of the first two columns.
    await gu.clickCellRC(0, 0);
    await gu.assertType('DateTime');
    await gu.clickCellRC(0, 1);
    await gu.assertType('Date');
    // Insert a few more columns: empty, Text, Numeric.
    await addColumn();
    await addColumn();
    await addColumn();
    await gu.clickCellRC(0, 3);
    await gu.setType('Numeric');
    await gu.clickCellRC(0, 4);
    await gu.setType('Text');

    // Override Date.now() and timezone in the current browser page to return a consistent value,
    // used e.g. for the default for the year and month.
    await driver.executeScript(
      "Date.now = () => 1477548296087; " +      // This value is 2016-10-27 02:04:56.087 EST
      "exposeModulesForTests().then(() => { " +
        "window.exposedModules.moment.tz.setDefault('America/New_York');" +
      "});"
    );

    async function fillWithShortcuts() {
      await gu.toggleSidePanel('right', 'close');

      // Type the Date-only shortcut into each cell in the second row.
      await gu.clickCellRC(1, 0);
      for (var i = 0; i < 6; i++) {
        await gu.sendKeys([$.MOD, ';'], $.TAB);
      }

      // Type the Date-Time shortcut into each cell in the third row.
      await gu.clickCellRC(2, 0);
      for (i = 0; i < 6; i++) {
        await gu.sendKeys([$.MOD, $.SHIFT, ';'], $.TAB);
      }
    }

    // Change document timezone to US/Hawaii (3 hours behind LA, which is TZ of the first column).
    // We check that shortcuts for Text/Any columns use the document timezone.
    await setGlobalTimezone('US/Hawaii');
    await fillWithShortcuts();
    // Compare the values. NOTE: this assumes EST timezone for the browser's local time.
    assert.deepEqual(await gu.getGridValues({rowNums: [2, 3], cols: [0, 1, 2, 3, 4]}), [
      // Note that column A has Los_Angeles timezone set, so time differs from Hawaii.
      // Note that Date column gets the date in Hawaii, not local or UTC (both 2016-10-27).
      // The originally empty column had its type guessed as Date when the current date was first entered,
      // hence "2016-10-26" appears in both rows.
      "October 26th, 2016 11:04pm", "October 26th, 2016", "2016-10-26", "0", "2016-10-26",
      "October 26th, 2016 11:04pm", "October 26th, 2016", "2016-10-26", "0", "2016-10-26 20:04:56",
    ]);

    // Undo the 8 cells we actually filled in, and check that the empty column reverted to Any
    await gu.undo(8);
    await gu.clickCellRC(1, 2);
    await gu.assertType('Any');

    // Change document timezone back to America/New_York.
    await setGlobalTimezone('America/New_York');
    await fillWithShortcuts();
    // Compare the values. NOTE: this assumes EST timezone for the browser's local time.
    assert.deepEqual(await gu.getGridValues({rowNums: [2, 3], cols: [0, 1, 2, 3, 4]}), [
      // Note that column A has Los_Angeles timezone set, so date differs by one from New_York.
      "October 26th, 2016 11:04pm", "October 27th, 2016", "2016-10-27", "0", "2016-10-27",
      "October 26th, 2016 11:04pm", "October 27th, 2016", "2016-10-27", "0", "2016-10-27 02:04:56",
    ]);
  });

  it('should allow navigating the datepicker with the keyboard', async function() {
    // Change the date using the datepicker.
    let cell = await gu.getCellRC(0, 1);
    await cell.scrollIntoView({inline: "end"}).click();
    await gu.sendKeys($.ENTER);
    await gu.waitAppFocus(false);
    await gu.sendKeys($.UP, $.UP, $.LEFT, $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'January 11th, 1968');

    // Do the same in the datetime editor.
    cell = await gu.getCellRC(1, 0);
    await cell.click();
    await gu.sendKeys($.ENTER);
    await gu.waitAppFocus(false);
    await gu.sendKeys($.UP, $.RIGHT, $.RIGHT, $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'October 28th, 2016 11:04pm');

    // Start navigating the datepicker, then start typing to return to using the cell editor.
    cell = await gu.getCellRC(1, 1);
    await cell.click();
    // The first backspace should return to cell edit mode, then the following keys should
    // change the year to 2009.
    await gu.sendKeys($.ENTER);
    await gu.waitAppFocus(false);
    await gu.sendKeys($.DOWN, $.RIGHT, $.BACK_SPACE, '9', $.LEFT, $.BACK_SPACE, '0', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'October 27th, 2009');
  });

  // NOTE: This addresses a bug where typical date entry formats were not recognized.
  // See https://phab.getgrist.com/T308
  it('should allow using common formats to enter the date', async function() {
    let cell = await gu.getCellRC(2, 1);
    await cell.click();
    await gu.sendKeys('April 2 1993', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'April 2nd, 1993');

    cell = await gu.getCellRC(1, 0);
    await cell.click();
    await gu.sendKeys('December', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), `December 1st, 2016 11:04pm`);

    cell = await gu.getCellRC(0, 1);
    await cell.click();
    await gu.sendKeys('7-Sep', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), `September 7th, 2016`);

    await cell.click();
    await gu.sendKeys('6/8', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), `June 8th, 2016`);

    // The selected format should take precedence over the default format when
    // parsing the date. Entering the same thing as before (6/8) will yield a different
    // result after changing the format.
    await gu.openSidePane('field');
    cell = await gu.getCellRC(1, 1);
    await cell.click();
    await gu.dateFormat('DD-MM-YYYY');
    await cell.click();
    await gu.sendKeys('6/8', $.ENTER);
    await gu.waitForServer();
    await gu.dateFormat('MMMM Do, YYYY');
    assert.equal(await cell.text(), `August 6th, 2016`);

    cell = await gu.getCellRC(2, 1);
    await cell.click();
    await gu.sendKeys('1937', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), `January 1st, 1937`);
  });

  it('should not attempt to parse non-dates', async function() {
    // Should allow AltText
    let cell = await gu.getCellRC(2, 1);
    await cell.click();
    await gu.sendKeys('Applesauce', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), 'Applesauce');
    await assert.hasClass(cell.find('.field_clip'), 'invalid');
    // Should allow AltText even of numbers that cannot be parsed as dates.
    // Manually entered numbers should not be read as timestamps.
    cell = await gu.getCellRC(1, 0);
    await cell.click();
    await gu.sendKeys('100000', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), '100000 11:04pm');
    await assert.hasClass(cell.find('.field_clip'), 'invalid');
    // Should give AltText if just the time is entered but not the date.
    cell = await gu.getCellRC(1, 0);
    await cell.click();
    await gu.sendKeys($.ENTER, $.TAB, '3', $.ENTER);
    await gu.waitForServer();
    assert.equal(await cell.text(), '100000 11:04pm 3');
    await assert.hasClass(cell.find('.field_clip'), 'invalid');
  });

  it("should allow working with naive date object", async function() {
    await gu.clickCellRC(0, 1);
    await gu.sendKeys([$.ALT, '=']);
    await gu.waitForServer();
    await gu.sendKeys("Diff", $.ENTER);
    await gu.waitForServer();
    await gu.sendKeys('=');
    await gu.waitAppFocus(false);
    await gu.sendKeys('($A-DTIME($B)).total_seconds()', $.ENTER);
    await gu.waitForServer();
    await gu.waitAppFocus();
    assert.deepEqual(await gu.getCellRC(0, 2).text(), '-230211900');

    // change global timezone should recompute formula
    await setGlobalTimezone('Paris');
    assert.deepEqual(await gu.getCellRC(0, 2).text(), '-230190300');
  });

  // NOTE: This tests a specific bug where AltText values in a column that has been coverted
  // to a date column do not respond to updates until refresh. This bug was exposed via the
  // error dom in FieldBuilder not being re-evaluated after a column transform.
  it('should allow deleting AltText values in a newly changed Date column', async function() {
    // Change the type to text and enter a text value.
    await gu.clickCellRC(0, 1);
    await gu.setType('Text');
    await gu.applyTypeConversion();
    await gu.clickCellRC(2, 1);
    await gu.sendKeys('banana', $.ENTER);
    await gu.waitForServer();
    assert.equal(await gu.getCellRC(2, 1).text(), 'banana');

    // Change back to Date and try to remove the text.
    await gu.setType('Date');
    await $('.test-type-transform-apply').wait().click();
    await gu.waitForServer();
    assert.equal(await gu.getCellRC(2, 1).text(), 'banana');
    await gu.clickCellRC(2, 1);
    await gu.sendKeys($.BACK_SPACE);
    await gu.waitForServer();
    assert.equal(await gu.getCellRC(2, 1).text(), '');
    await gu.undo();
  });

  it("should report informative error when AltText is used for date", async function() {
    // Enter a formula column that uses a date.
    await gu.clickCellRC(0, 1);
    await gu.sendKeys([$.ALT, '=']);
    await gu.waitForServer();
    await gu.sendKeys("Month", $.ENTER);
    await gu.waitForServer();
    await gu.sendKeys("=$B.month", $.ENTER);
    await gu.waitForServer();

    assert.deepEqual(await gu.getGridValues({rowNums: [1, 2, 3, 4], cols: ['B', 'Month']}), [
      "June 8th, 2016",     "6",
      "August 6th, 2016",   "8",
      "banana",             "#Invalid Date: banana",
      "",                   "#AttributeError",
    ]);
  });

  it('should default timezone to document\'s timezone', async function() {
    // add a DateTime column
    await addDateTimeColumn();
    await gu.timeFormat('HH:mm:ss');
    // BUG: it is required to click somewhere after setting the type of a column for the shortcut to
    // work
    // TODO: removes gu.getCellRC(1, 3).click() below when its fixed
    await gu.getCellRC(1, 3).click();
    // get the current date
    await gu.sendKeys([$.MOD, $.SHIFT, ';']);
    await gu.waitForServer();
    const date1 = await gu.getCellRC(1, 3).text();
    // check default timezone
    assert.equal(await $('.test-tz-autocomplete input').val(), 'Europe/Paris');
    // set global document timezone to 'Europe/Paris'
    await setGlobalTimezone('America/Los_Angeles');
    // add another DateTime column
    await addDateTimeColumn();
    await gu.timeFormat('HH:mm:ss');
    // todo: same as for gu.getCellRC(1, 3).click();
    await gu.getCellRC(1, 4).click();
    // get the current date
    await gu.sendKeys([$.MOD, $.SHIFT, ';']);
    await gu.waitForServer();
    const date2 = await gu.getCellRC(1, 4).text();
    // check default timezone
    assert.equal(await $('.test-tz-autocomplete input').val(), 'America/Los_Angeles');
    // check that the delta between date1 and date2 is coherent with the delta between
    // 'Europe/Paris' and 'America/Los_Angeles' timezones.
    const delta = (new Date(date1) - new Date(date2)) / 1000 / 60 / 60;
    assert.isAbove(delta, 6);
    assert.isBelow(delta, 12);
  });
});

async function addDateTimeColumn() {
  await addColumn();
  return gu.setType('DateTime');
}

async function addColumn() {
  await gu.sendKeys([$.ALT, '=']);
  await gu.waitForServer();
  return gu.sendKeys($.ESCAPE);
}

async function setGlobalTimezone(name) {
  await $('.test-user-icon').click();   // open the user menu
  await $('.test-dm-doc-settings').click();
  await $('.test-tz-autocomplete').click();
  await $(`.test-acselect-dropdown li:contains(${name})`).click();
  await gu.waitForServer();
  await driver.navigate().back();
}
