/* eslint no-console: "off" */
import Inflector from 'ember-inflector';
import yadda from './helpers/yadda';
import { currentURL, click, triggerKeyEvent, fillIn, find } from '@ember/test-helpers';
import getDictionary from '@hashicorp/ember-cli-api-double/dictionary';
import pages from 'consul-ui/tests/pages';
import api from 'consul-ui/tests/helpers/api';
// const dont = `( don't| shouldn't| can't)?`;
const pluralize = function(str) {
  return Inflector.inflector.pluralize(str);
};
const create = function(number, name, value) {
  // don't return a promise here as
  // I don't need it to wait
  api.server.createList(name, number, value);
};
const lastRequest = function(method) {
  return api.server.history
    .slice(0)
    .reverse()
    .find(function(item) {
      return item.method === method;
    });
};
const fillInElement = function(page, name, value) {
  const cm = document.querySelector(`textarea[name="${name}"] + .CodeMirror`);
  if (cm) {
    cm.CodeMirror.setValue(value);
    return page;
  } else {
    return page.fillIn(name, value);
  }
};
var currentPage;
export default function(assert) {
  return (
    yadda.localisation.English.library(
      getDictionary(function(model, cb) {
        switch (model) {
          case 'datacenter':
          case 'datacenters':
          case 'dcs':
            model = 'dc';
            break;
          case 'services':
            model = 'service';
            break;
          case 'nodes':
            model = 'node';
            break;
          case 'kvs':
            model = 'kv';
            break;
          case 'acls':
            model = 'acl';
            break;
          case 'sessions':
            model = 'session';
            break;
          case 'intentions':
            model = 'intention';
            break;
        }
        cb(null, model);
      }, yadda)
    )
      // doubles
      .given(['$number $model model[s]?', '$number $model models'], function(number, model) {
        return create(number, model);
      })
      .given(['$number $model model[s]? with the value "$value"'], function(number, model, value) {
        return create(number, model, value);
      })
      .given(
        ['$number $model model[s]? from yaml\n$yaml', '$number $model model[s]? from json\n$json'],
        function(number, model, data) {
          return create(number, model, data);
        }
      )
      .given(["I'm using a legacy token"], function(number, model, data) {
        window.localStorage['consul:token'] = JSON.stringify({ AccessorID: null, SecretID: 'id' });
      })
      // TODO: Abstract this away from HTTP
      .given(['the url "$url" responds with a $status status'], function(url, status) {
        return api.server.respondWithStatus(url.split('?')[0], parseInt(status));
      })
      .given(['the url "$url" responds with from yaml\n$yaml'], function(url, data) {
        api.server.respondWith(url.split('?')[0], data);
      })
      // interactions
      .when('I visit the $name page', function(name) {
        currentPage = pages[name];
        return currentPage.visit();
      })
      .when('I visit the $name page for the "$id" $model', function(name, id, model) {
        currentPage = pages[name];
        return currentPage.visit({
          [model]: id,
        });
      })
      .when(
        ['I visit the $name page for yaml\n$yaml', 'I visit the $name page for json\n$json'],
        function(name, data) {
          currentPage = pages[name];
          // TODO: Consider putting an assertion here for testing the current url
          // do I absolutely definitely need that all the time?
          return pages[name].visit(data);
        }
      )
      .when('I click "$selector"', function(selector) {
        return click(selector);
      })
      // TODO: Probably nicer to think of better vocab than having the 'without " rule'
      .when('I click (?!")$property(?!")', function(property) {
        try {
          return currentPage[property]();
        } catch (e) {
          console.error(e);
          throw new Error(`The '${property}' property on the page object doesn't exist`);
        }
      })
      .when('I click $prop on the $component', function(prop, component) {
        // Collection
        var obj;
        if (typeof currentPage[component].objectAt === 'function') {
          obj = currentPage[component].objectAt(0);
        } else {
          obj = currentPage[component];
        }
        const func = obj[prop].bind(obj);
        try {
          return func();
        } catch (e) {
          throw new Error(
            `The '${prop}' property on the '${component}' page object doesn't exist.\n${e.message}`
          );
        }
      })
      .when('I submit', function(selector) {
        return currentPage.submit();
      })
      .then('I fill in "$name" with "$value"', function(name, value) {
        return currentPage.fillIn(name, value);
      })
      .then(['I fill in with yaml\n$yaml', 'I fill in with json\n$json'], function(data) {
        return Object.keys(data).reduce(function(prev, item, i, arr) {
          return fillInElement(prev, item, data[item]);
        }, currentPage);
      })
      .then(
        ['I fill in the $form form with yaml\n$yaml', 'I fill in the $form with json\n$json'],
        function(form, data) {
          return Object.keys(data).reduce(function(prev, item, i, arr) {
            const name = `${form}[${item}]`;
            return fillInElement(prev, name, data[item]);
          }, currentPage);
        }
      )
      .then(['I type "$text" into "$selector"'], function(text, selector) {
        return fillIn(selector, text);
      })
      .then(['I type with yaml\n$yaml'], function(data) {
        const keys = Object.keys(data);
        return keys
          .reduce(function(prev, item, i, arr) {
            return prev.fillIn(item, data[item]);
          }, currentPage)
          .then(function() {
            return Promise.all(
              keys.map(function(item) {
                return triggerKeyEvent(`[name="${item}"]`, 'keyup', 83); // TODO: This is 's', be more generic
              })
            );
          });
      })
      // debugging helpers
      .then('print the current url', function(url) {
        console.log(currentURL());
        return Promise.resolve();
      })
      .then('log the "$text"', function(text) {
        console.log(text);
        return Promise.resolve();
      })
      .then('pause for $milliseconds', function(milliseconds) {
        return new Promise(function(resolve) {
          setTimeout(resolve, milliseconds);
        });
      })
      // assertions
      .then('a $method request is made to "$url" with the body from yaml\n$yaml', function(
        method,
        url,
        data
      ) {
        const request = api.server.history[api.server.history.length - 2];
        assert.equal(
          request.method,
          method,
          `Expected the request method to be ${method}, was ${request.method}`
        );
        assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
        const body = JSON.parse(request.requestBody);
        Object.keys(data).forEach(function(key, i, arr) {
          assert.deepEqual(
            body[key],
            data[key],
            `Expected the payload to contain ${key} equaling ${data[key]}, ${key} was ${body[key]}`
          );
        });
      })
      // TODO: This one can replace the above one, it covers more use cases
      // also DRY it out a bit
      .then('a $method request is made to "$url" from yaml\n$yaml', function(method, url, yaml) {
        const request = api.server.history[api.server.history.length - 2];
        assert.equal(
          request.method,
          method,
          `Expected the request method to be ${method}, was ${request.method}`
        );
        assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
        let data = yaml.body || {};
        const body = JSON.parse(request.requestBody);
        Object.keys(data).forEach(function(key, i, arr) {
          assert.equal(
            body[key],
            data[key],
            `Expected the payload to contain ${key} to equal ${body[key]}, ${key} was ${data[key]}`
          );
        });
        data = yaml.headers || {};
        const headers = request.requestHeaders;
        Object.keys(data).forEach(function(key, i, arr) {
          assert.equal(
            headers[key],
            data[key],
            `Expected the payload to contain ${key} to equal ${headers[key]}, ${key} was ${
              data[key]
            }`
          );
        });
      })
      .then('a $method request is made to "$url" with the body "$body"', function(
        method,
        url,
        data
      ) {
        const request = api.server.history[api.server.history.length - 2];
        assert.equal(
          request.method,
          method,
          `Expected the request method to be ${method}, was ${request.method}`
        );
        assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
        const body = request.requestBody;
        assert.equal(body, data, `Expected the request body to be ${data}, was ${body}`);
      })
      .then('a $method request is made to "$url" with no body', function(method, url) {
        const request = api.server.history[api.server.history.length - 2];
        assert.equal(
          request.method,
          method,
          `Expected the request method to be ${method}, was ${request.method}`
        );
        assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
        const body = request.requestBody;
        assert.equal(body, null, `Expected the request body to be null, was ${body}`);
      })

      .then('a $method request is made to "$url"', function(method, url) {
        const request = api.server.history[api.server.history.length - 2];
        assert.equal(
          request.method,
          method,
          `Expected the request method to be ${method}, was ${request.method}`
        );
        assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
      })
      .then('the last $method request was made to "$url"', function(method, url) {
        const request = lastRequest(method);
        assert.equal(
          request.method,
          method,
          `Expected the request method to be ${method}, was ${request.method}`
        );
        assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
      })
      .then('the last $method request was made to "$url" with the body from yaml\n$yaml', function(
        method,
        url,
        data
      ) {
        const request = lastRequest(method);
        assert.ok(request, `Expected a ${method} request`);
        assert.equal(
          request.method,
          method,
          `Expected the request method to be ${method}, was ${request.method}`
        );
        assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
        const body = JSON.parse(request.requestBody);
        Object.keys(data).forEach(function(key, i, arr) {
          assert.deepEqual(
            body[key],
            data[key],
            `Expected the payload to contain ${key} equaling ${data[key]}, ${key} was ${body[key]}`
          );
        });
      })
      .then('the last $method requests were like yaml\n$yaml', function(method, data) {
        const requests = api.server.history.reverse().filter(function(item) {
          return item.method === method;
        });
        data.reverse().forEach(function(item, i, arr) {
          assert.equal(
            requests[i].url,
            item,
            `Expected the request url to be ${item}, was ${requests[i].url}`
          );
        });
      })
      .then('the url should be $url', function(url) {
        // TODO: nice! $url should be wrapped in ""
        if (url === "''") {
          url = '';
        }
        const current = currentURL() || '';
        assert.equal(current, url, `Expected the url to be ${url} was ${current}`);
      })
      .then(['I see $num $model', 'I see $num $model model', 'I see $num $model models'], function(
        num,
        model
      ) {
        const len = currentPage[pluralize(model)].filter(function(item) {
          return item.isVisible;
        }).length;

        assert.equal(len, num, `Expected ${num} ${pluralize(model)}, saw ${len}`);
      })
      // TODO: I${ dont } see
      .then([`I see $num $model model[s]? with the $property "$value"`], function(
        // negate,
        num,
        model,
        property,
        value
      ) {
        const len = currentPage[pluralize(model)].filter(function(item) {
          return item.isVisible && item[property] == value;
        }).length;
        assert.equal(
          len,
          num,
          `Expected ${num} ${pluralize(model)} with ${property} set to "${value}", saw ${len}`
        );
      })
      // TODO: Make this accept a 'contains' word so you can search for text containing also
      .then('I have settings like yaml\n$yaml', function(data) {
        // TODO: Inject this
        const settings = window.localStorage;
        Object.keys(data).forEach(function(prop) {
          const actual = settings.getItem(prop);
          const expected = data[prop];
          assert.strictEqual(actual, expected, `Expected settings to be ${expected} was ${actual}`);
        });
      })
      .then('I see $property on the $component like yaml\n$yaml', function(
        property,
        component,
        yaml
      ) {
        const _component = currentPage[component];
        const iterator = new Array(_component.length).fill(true);
        // this will catch if we get aren't managing to select a component
        assert.ok(iterator.length > 0);
        iterator.forEach(function(item, i, arr) {
          const actual =
            typeof _component.objectAt(i)[property] === 'undefined'
              ? null
              : _component.objectAt(i)[property];

          // anything coming from the DOM is going to be text/strings
          // if the yaml has numbers, cast them to strings
          // TODO: This would get problematic for deeper objects
          // will have to look to do this recursively
          const expected = typeof yaml[i] === 'number' ? yaml[i].toString() : yaml[i];

          assert.deepEqual(
            actual,
            expected,
            `Expected to see ${property} on ${component}[${i}] as ${JSON.stringify(
              expected
            )}, was ${JSON.stringify(actual)}`
          );
        });
      })
      .then(['I see $property on the $component'], function(property, component) {
        // TODO: Time to work on repetition
        // Collection
        var obj;
        if (typeof currentPage[component].objectAt === 'function') {
          obj = currentPage[component].objectAt(0);
        } else {
          obj = currentPage[component];
        }
        let _component;
        if (typeof obj === 'function') {
          const func = obj[property].bind(obj);
          try {
            _component = func();
          } catch (e) {
            console.error(e);
            throw new Error(
              `The '${property}' property on the '${component}' page object doesn't exist`
            );
          }
        } else {
          _component = obj;
        }
        assert.ok(_component[property], `Expected to see ${property} on ${component}`);
      })
      .then(["I don't see $property on the $component"], function(property, component) {
        // Collection
        var obj;
        if (typeof currentPage[component].objectAt === 'function') {
          obj = currentPage[component].objectAt(0);
        } else {
          obj = currentPage[component];
        }
        const func = obj[property].bind(obj);
        assert.throws(
          function() {
            func();
          },
          function(e) {
            return e.toString().indexOf('Element not found') !== -1;
          },
          `Expected to not see ${property} on ${component}`
        );
      })
      .then(["I don't see $property"], function(property) {
        assert.throws(
          function() {
            currentPage[property]();
          },
          function(e) {
            return e.toString().indexOf('Element not found') !== -1;
          },
          `Expected to not see ${property}`
        );
      })
      .then(['I see $property'], function(property) {
        assert.ok(currentPage[property], `Expected to see ${property}`);
      })
      .then(['I see $property like "$value"'], function(property, value) {
        assert.equal(
          currentPage[property],
          value,
          `Expected to see ${property}, was ${currentPage[property]}`
        );
      })
      .then(['I see the text "$text" in "$selector"'], function(text, selector) {
        assert.ok(
          find(selector).textContent.indexOf(text) !== -1,
          `Expected to see "${text}" in "${selector}"`
        );
      })
      // TODO: Think of better language
      // TODO: These should be mergeable
      .then(['"$selector" has the "$class" class'], function(selector, cls) {
        // because `find` doesn't work, guessing its sandboxed to ember's container
        assert.ok(
          document.querySelector(selector).classList.contains(cls),
          `Expected [class] to contain ${cls} on ${selector}`
        );
      })
      .then(['"$selector" doesn\'t have the "$class" class'], function(selector, cls) {
        assert.ok(
          !document.querySelector(selector).classList.contains(cls),
          `Expected [class] not to contain ${cls} on ${selector}`
        );
      })
      .then('ok', function() {
        assert.ok(true);
      })
  );
}
