/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: BUSL-1.1
 */

import domEventSourceBlocking, {
  validateCursor,
  createErrorBackoff,
} from 'consul-ui/utils/dom/event-source/blocking';
import { module, test } from 'qunit';
import sinon from 'sinon';

module('Unit | Utility | dom/event-source/blocking', function () {
  const createEventSource = function () {
    const EventSource = function (cb) {
      this.readyState = 1;
      this.source = cb;
    };
    const o = EventSource.prototype;
    ['addEventListener', 'removeEventListener', 'dispatchEvent', 'close'].forEach(function (item) {
      o[item] = function () {};
    });
    return EventSource;
  };
  const createPromise = function (resolve = function () {}) {
    class PromiseMock {
      constructor(cb = function () {}) {
        cb(resolve);
      }
      then(cb) {
        setTimeout(() => cb.bind(this)(), 0);
        return this;
      }
      catch(cb) {
        cb({ message: 'error' });
        return this;
      }
    }
    PromiseMock.resolve = function () {
      return new PromiseMock();
    };
    return PromiseMock;
  };
  test('it creates an BlockingEventSource class implementing EventSource', function (assert) {
    const EventSource = createEventSource();
    const BlockingEventSource = domEventSourceBlocking(EventSource, function () {});
    assert.ok(BlockingEventSource instanceof Function);
    const source = new BlockingEventSource(function () {
      return createPromise().resolve();
    });
    assert.ok(source instanceof EventSource);
  });
  test("the 5xx backoff continues to throw when it's not a 5xx", function (assert) {
    assert.expect(11);

    const backoff = createErrorBackoff();
    [
      undefined,
      null,
      new Error(),
      { statusCode: 404 },
      { errors: [] },
      { errors: [{ status: '401' }] },
      { errors: [{ status: '500' }] },
      { errors: [{ status: '5' }] },
      { errors: [{ status: '50' }] },
      { errors: [{ status: '5000' }] },
      { errors: [{ status: '5050' }] },
    ].forEach(function (item) {
      assert.throws(function () {
        backoff(item);
      });
    });
  });
  test('the 5xx backoff returns a resolve promise on a 5xx (apart from 500)', function (assert) {
    assert.expect(18);

    [
      { statusCode: 501 },
      { errors: [{ status: 501 }] },
      { errors: [{ status: '501' }] },
      { errors: [{ status: '503' }] },
      { errors: [{ status: '504' }] },
      { errors: [{ status: '524' }] },
    ].forEach((item) => {
      const timeout = sinon.stub().callsArg(0);
      const resolve = sinon.stub().withArgs(item);
      const Promise = createPromise(resolve);
      const backoff = createErrorBackoff(undefined, Promise, timeout);
      const promise = backoff(item);
      assert.ok(promise instanceof Promise, 'a promise was returned');
      assert.ok(resolve.calledOnce, 'the promise was resolved with the correct arguments');
      assert.ok(timeout.calledOnce, 'timeout was called once');
    });
  });
  test("the cursor validation always returns undefined if the cursor can't be parsed to an integer", function (assert) {
    assert.expect(4);

    ['null', null, '', undefined].forEach((item) => {
      const actual = validateCursor(item);
      assert.equal(actual, undefined);
    });
  });
  test('the cursor validation always returns a cursor greater than zero', function (assert) {
    assert.expect(5);

    [
      {
        cursor: 0,
        expected: 1,
      },
      {
        cursor: -10,
        expected: 1,
      },
      {
        cursor: -1,
        expected: 1,
      },
      {
        cursor: -1000,
        expected: 1,
      },
      {
        cursor: 10,
        expected: 10,
      },
    ].forEach((item) => {
      const actual = validateCursor(item.cursor);
      assert.equal(actual, item.expected, 'cursor is greater than zero');
    });
  });
  test('the cursor validation resets to 1 if its less than the previous cursor', function (assert) {
    assert.expect(4);

    [
      {
        previous: 100,
        cursor: 99,
        expected: 1,
      },
      {
        previous: 100,
        cursor: -10,
        expected: 1,
      },
      {
        previous: 100,
        cursor: 0,
        expected: 1,
      },
      {
        previous: 100,
        cursor: 101,
        expected: 101,
      },
    ].forEach((item) => {
      const actual = validateCursor(item.cursor, item.previous);
      assert.equal(actual, item.expected);
    });
  });
});
