import { expect } from 'chai';
import { has, isObject, noop } from 'lodash';
import sinon from 'sinon';

import { ServiceManager } from '@/serviceManager';

const serviceFake = (req, res): void => res.json({ done: 'done' });

const hasConstructor = (obj): boolean => obj.constructor !== Object;

const getFakedInstance = (target) =>
  Object.getOwnPropertyNames(Object.getPrototypeOf(target))
    .filter((key) => key !== 'constructor')
    .reduce((result, key) => {
      result[key] = sinon.stub().callsFake(serviceFake);
      return result;
    }, {});

const getStubbedInstance = (target) =>
  Object.getOwnPropertyNames(Object.getPrototypeOf(target))
    .filter((key) => key !== 'constructor')
    .reduce((result, key) => {
      result[key] = sinon.stub().callsArg(2);
      return result;
    }, {});

const traverseObject = (target, useStub) => {
  if (!target) return target;

  if (hasConstructor(target)) {
    return useStub ? getStubbedInstance(target) : getFakedInstance(target);
  }

  return Object.keys(target).reduce((result, key) => {
    result[key] = traverseObject(target[key], useStub);
    return result;
  }, {});
};

const createFixture = () => {
  sinon.restore();

  const config = { port: 9001, stripeWebhookSecret: 'foo', slackSigningSecret: 'foo' };
  const services = { limiters: {}, responseBuilder: { wrap: (func) => func } } as any;
  const middleware = ServiceManager.buildExpressMiddleware(config as any, {} as any, services);
  const controllers = ServiceManager.buildExpressControllers(config as any, {} as any, services);

  return {
    config,
    start: noop,
    stop: noop,
    expressMiddleware: traverseObject(middleware, true),
    expressControllers: traverseObject(controllers, false),
    clients: {},
  };
};

const checkFixture = (fixture, expected): void => {
  const { expressMiddleware, expressControllers } = fixture;

  const updateExpectationsFromFixture = (fixtureObj, expectedObj) => {
    return Object.keys(fixtureObj).reduce((_expectedObj, key) => {
      const isStub = has(fixtureObj[key], 'callsFake');

      if (isStub) {
        fixtureObj[key] = fixtureObj[key].callCount;
      }

      if (!has(_expectedObj, key)) {
        if (isStub) {
          _expectedObj[key] = 0;
        } else {
          _expectedObj[key] = {};
        }
      }

      if (isObject(fixtureObj[key])) {
        updateExpectationsFromFixture(fixtureObj[key], _expectedObj[key]);
      }

      return _expectedObj;
    }, expectedObj);
  };

  updateExpectationsFromFixture(expressControllers, expected.controllers);
  updateExpectationsFromFixture(expressMiddleware, expected.middleware);

  expect(expressControllers).to.deep.equal(expected.controllers);
  expect(expressMiddleware).to.deep.equal(expected.middleware);
};

export { createFixture, checkFixture };