// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { assert } from 'chai';
import * as sinon from 'sinon';
import { EventEmitter } from 'events';

import { ActiveWindowService } from '../../services/ActiveWindowService';

describe('ActiveWindowService', () => {
  const fakeIpcEvent = {};

  beforeEach(function beforeEach() {
    this.clock = sinon.useFakeTimers({ now: 1000 });
  });

  afterEach(function afterEach() {
    this.clock.restore();
  });

  function createFakeDocument() {
    return document.createElement('div');
  }

  it('is inactive at the start', () => {
    const service = new ActiveWindowService();
    service.initialize(createFakeDocument(), new EventEmitter());

    assert.isFalse(service.isActive());
  });

  it('becomes active after focusing', () => {
    const fakeIpc = new EventEmitter();
    const service = new ActiveWindowService();
    service.initialize(createFakeDocument(), fakeIpc);

    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);

    assert.isTrue(service.isActive());
  });

  it('becomes inactive after 15 seconds without interaction', function test() {
    const fakeIpc = new EventEmitter();
    const service = new ActiveWindowService();
    service.initialize(createFakeDocument(), fakeIpc);

    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);

    this.clock.tick(5000);
    assert.isTrue(service.isActive());

    this.clock.tick(9999);
    assert.isTrue(service.isActive());

    this.clock.tick(1);
    assert.isFalse(service.isActive());
  });

  ['click', 'keydown', 'mousedown', 'mousemove', 'touchstart', 'wheel'].forEach(
    (eventName: string) => {
      it(`is inactive even in the face of ${eventName} events if unfocused`, function test() {
        const fakeDocument = createFakeDocument();
        const fakeIpc = new EventEmitter();
        const service = new ActiveWindowService();
        service.initialize(fakeDocument, fakeIpc);

        fakeIpc.emit('set-window-focus', fakeIpcEvent, false);

        fakeDocument.dispatchEvent(new Event(eventName));
        assert.isFalse(service.isActive());
      });

      it(`stays active if focused and receiving ${eventName} events`, function test() {
        const fakeDocument = createFakeDocument();
        const fakeIpc = new EventEmitter();
        const service = new ActiveWindowService();
        service.initialize(fakeDocument, fakeIpc);

        fakeIpc.emit('set-window-focus', fakeIpcEvent, true);

        fakeDocument.dispatchEvent(new Event(eventName));
        assert.isTrue(service.isActive());

        this.clock.tick(8000);
        fakeDocument.dispatchEvent(new Event(eventName));
        assert.isTrue(service.isActive());

        this.clock.tick(8000);
        fakeDocument.dispatchEvent(new Event(eventName));
        assert.isTrue(service.isActive());
      });
    }
  );

  it('calls callbacks when going from unfocused to focused', () => {
    const fakeIpc = new EventEmitter();
    const service = new ActiveWindowService();
    service.initialize(createFakeDocument(), fakeIpc);

    const callback = sinon.stub();
    service.registerForActive(callback);

    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);

    sinon.assert.calledOnce(callback);
  });

  it('calls callbacks when receiving a click event after being focused', function test() {
    const fakeDocument = createFakeDocument();
    const fakeIpc = new EventEmitter();
    const service = new ActiveWindowService();
    service.initialize(fakeDocument, fakeIpc);

    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);

    this.clock.tick(20000);

    const callback = sinon.stub();
    service.registerForActive(callback);

    fakeDocument.dispatchEvent(new Event('click'));

    sinon.assert.calledOnce(callback);
  });

  it('only calls callbacks every 5 seconds; it is throttled', function test() {
    const fakeIpc = new EventEmitter();
    const service = new ActiveWindowService();
    service.initialize(createFakeDocument(), fakeIpc);

    const callback = sinon.stub();
    service.registerForActive(callback);

    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
    fakeIpc.emit('set-window-focus', fakeIpcEvent, false);
    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
    fakeIpc.emit('set-window-focus', fakeIpcEvent, false);
    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);
    fakeIpc.emit('set-window-focus', fakeIpcEvent, false);

    sinon.assert.calledOnce(callback);

    this.clock.tick(15000);

    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);

    sinon.assert.calledTwice(callback);
  });

  it('can remove callbacks', () => {
    const fakeDocument = createFakeDocument();
    const fakeIpc = new EventEmitter();
    const service = new ActiveWindowService();
    service.initialize(fakeDocument, fakeIpc);

    const callback = sinon.stub();
    service.registerForActive(callback);
    service.unregisterForActive(callback);

    fakeIpc.emit('set-window-focus', fakeIpcEvent, true);

    sinon.assert.notCalled(callback);
  });
});
