# Source: core-adapter.js

    /**
     * @module core-adapter
    */

    const md5 = require('md5');
    const PouchDB = require('pouchdb');
    const uuid = require('uuid/v4');

    PouchDB.plugin(require('pouchdb-adapter-memory'));

    class CoreAdapter {
      constructor(core, appSettings) {
        this.appSettings = appSettings;
        this.pouchdb = new PouchDB(`medic-conf-test-harness-${uuid()}`, { adapter: 'memory' });
        this.core = core;
        this.rulesEngine = core.RulesEngineCore(this.pouchdb);
        this.pouchdbStateHash = {};
        this.lineageLib = core.Lineage(Promise, this.pouchdb);
      }

      async fetchTargets(user, state) {
        this.pouchdbStateHash = await prepare(this.core, this.rulesEngine, this.appSettings, this.pouchdb, this.pouchdbStateHash, user, state);

        const uhcMonthStartDate = getMonthStartDate(this.appSettings);
        const relevantInterval = this.core.CalendarInterval.getCurrent(uhcMonthStartDate);
        return this.rulesEngine.fetchTargets(relevantInterval);
      }

      async fetchTasksFor(user, state) {
        this.pouchdbStateHash = await prepare(this.core, this.rulesEngine, this.appSettings, this.pouchdb, this.pouchdbStateHash, user, state);
        return this.rulesEngine.fetchTasksFor();
      }

      async fetchTaskDocs() {
        const options = { startkey: `task~`, endkey: `task~\ufff0`, include_docs: true };
        const result = await this.pouchdb.allDocs(options);
        return result.rows.map(row => row.doc);
      }

      async fetchHydratedDoc(id, state) {
        const { updatedSubjectIds, newPouchdbState } = await syncPouchWithState(this.core, this.pouchdb, this.pouchdbStateHash, state);
        if (this.rulesEngine.isEnabled()) {
          await this.rulesEngine.updateEmissionsFor(updatedSubjectIds);
        }
        this.pouchdbStateHash = newPouchdbState;

        try {
          return await this.lineageLib.fetchHydratedDoc(id);
        } catch (err) {
          throw Error(`fetchHydratedDoc failed for id:${id} error: ${err}`);
        }
      }

      async buildLineage(id, state) {
        const { updatedSubjectIds, newPouchdbState } = await syncPouchWithState(this.core, this.pouchdb, this.pouchdbStateHash, state);
        if (this.rulesEngine.isEnabled()) {
          await this.rulesEngine.updateEmissionsFor(updatedSubjectIds);
        }
        this.pouchdbStateHash = newPouchdbState;

        const lineage = await this.lineageLib.fetchLineageById(id);
        const contactDocs = await this.lineageLib.fetchContacts(lineage);
        await this.lineageLib.fillContactsInDocs(lineage, contactDocs);
        lineage.shift();
        return lineage;
      }

      minify(doc) {
        return this.lineageLib.minify(doc);
      }
    }

    const prepare = async (chtCore, rulesEngine, appSettings, pouchdb, pouchdbStateHash, user, state) => {
      await prepareRulesEngine(chtCore, rulesEngine, appSettings, user, pouchdb.name);
      const { updatedSubjectIds, newPouchdbState } = await syncPouchWithState(chtCore, pouchdb, pouchdbStateHash, state);
      await rulesEngine.updateEmissionsFor(updatedSubjectIds);
      return newPouchdbState;
    };

    const prepareRulesEngine = async (chtCore, rulesEngine, appSettings, user, sessionId) => {
      const rulesSettings = getRulesSettings(appSettings, user, sessionId);
      if (!rulesEngine.isEnabled()) {
        await rulesEngine.initialize(rulesSettings);
      } else {
        // Handle scenarios where the "user" object has changed
        await rulesEngine.rulesConfigChange(rulesSettings);
      }

      /*
      The Date object inside Nools doesn't work with sinon useFakeTimers (closure?)
      So this is a terribly vicious hack to reset that internal component and restart the nools session
      */
      if (chtCore.RulesEmitter.isEnabled()) {
        chtCore.RulesEmitter.shutdown();
        chtCore.RulesEmitter.initialize({
          rules: appSettings.tasks.rules,
          contact: user,
        });
      }
    };

    const syncPouchWithState = async (chtCore, pouchdb, pouchdbStateHash, state) => {
      await pouchdb.bulkDocs(chtCore.ddocs);

      // build a summary of documents in pouchdb
      const newPouchdbState = {};
      const docs = [...state.contacts, ...state.reports];
      for (const doc of docs) {
        const docId = doc._id;
        if (!docId) {
          throw Error(`Doc is missing attribute _id`);
        }
        delete doc._rev; // ignore _rev entirely and permanently

        if (newPouchdbState[docId]) {
          throw Error(`Harness state contains docs with duplicate id ${docId}.`);
        }

        const subjectIds = chtCore.RegistrationUtils.getSubjectIds(doc);
        newPouchdbState[docId] = {
          subjectId: subjectIds && subjectIds[0],
          docHash: md5(JSON.stringify(doc)),
        };
      }

      // sync added/changed docs to pouchdb
      const updatedSubjects = new Set();
      for (const doc of docs) {
        const docId = doc._id;
        const { docHash, subjectId } = newPouchdbState[docId];
        // new or changed
        if (!pouchdbStateHash[docId] || pouchdbStateHash[docId].docHash !== docHash) {
          await upsert(pouchdb, doc);
          if (subjectId) {
            updatedSubjects.add(subjectId);
          }
        }
      }

      // sync documents that were deleted into pouchdb
      const removedDocIds = Object.keys(pouchdbStateHash).filter(docId => !newPouchdbState[docId]);
      if (removedDocIds.length) {
        const impactedSubjects = removedDocIds.map(docId => pouchdbStateHash[docId].subjectId);
        updatedSubjects.add(...impactedSubjects);

        const deleteDoc = docId => upsert(pouchdb, { _id: docId, _deleted: true });
        await Promise.all(removedDocIds.map(deleteDoc));
      }

      return {
        updatedSubjectIds: Array.from(updatedSubjects),
        newPouchdbState,
      };
    };

    const upsert = async (pouchdb, doc) => {
      const existing = await pouchdb.get(doc._id)
        .catch(err => {
          if (err.status !== 404) {
            throw err;
          }
        });

      const docWithRev = Object.assign({}, doc, { _rev: existing && existing._rev });
      await pouchdb.put(docWithRev);
    };

    // cht-core/src/ts/services/uhc-settings.service.ts
    const getMonthStartDate = settings => {
      return settings &&
        settings.uhc &&
        (
          settings.uhc.month_start_date ||
          settings.uhc.visit_count &&
          settings.uhc.visit_count.month_start_date
        );
    };

    const getRulesSettings = (settingsDoc, userContactDoc, sessionId) => {
      const settingsTasks = settingsDoc && settingsDoc.tasks || {};
      // https://github.com/medic/medic-conf-test-harness/issues/106
      // const filterTargetByContext = (target) => target.context ? !!this.parseProvider.parse(target.context)({ user: userContactDoc }) : true;
      const targets = settingsTasks.targets && settingsTasks.targets.items || [];

      return {
        rules: settingsTasks.rules,
        taskSchedules: settingsTasks.schedules,
        targets: targets,
        enableTasks: true,
        enableTargets: true,
        contact: userContactDoc, // <- this goes to rules emitter
        user: { _id: `org.couchdb.user:${userContactDoc ? userContactDoc._id : 'default'}` },
        monthStartDate: getMonthStartDate(settingsDoc),
        sessionId,
      };
    };

    module.exports = CoreAdapter;