'use strict';

// Much of this file was based on the `le-challenge-ddns` library (which we are not using
// here because it's method of setting records requires things we don't really want).
module.exports.create = function (deps, conf, utils) {

  function getReleventSessionId(domain) {
    var sessId;

    utils.iterateAllModules(function (mod, domainList) {
      // We return a truthy value in these cases because of the way the iterate function
      // handles modules grouped by domain. By returning true we are saying these domains
      // are "handled" and so if there are multiple modules we won't be given the rest.
      if (sessId) { return true; }
      if (domainList.indexOf(domain) < 0) { return true; }

      // But if the domains are relevant but we don't know how to handle the module we
      // return false to allow us to look at any other modules that might exist here.
      if (mod.type !== 'dns@oauth3.org')  { return false; }

      sessId = mod.tokenId || mod.token_id;
      return true;
    });

    return sessId;
  }

  function get(args, domain, challenge, done) {
    done(new Error("Challenge.get() does not need an implementation for dns-01. (did you mean Challenge.loopback?)"));
  }
  // same as get, but external
  function loopback(args, domain, challenge, done) {
    var challengeDomain = (args.test || '') + args.acmeChallengeDns + domain;
    require('dns').resolveTxt(challengeDomain, done);
  }

  var activeChallenges = {};
  async function removeAsync(args, domain) {
    var data = activeChallenges[domain];
    if (!data) {
      console.warn(new Error('cannot remove DNS challenge for ' + domain + ': already removed'));
      return;
    }

    var session = await utils.getSession(data.sessId);
    var directives = await deps.OAUTH3.discover(session.token.aud);
    var apiOpts = {
      api: 'dns.unset'
    , session: session
    , type: 'TXT'
    , value: data.keyAuthDigest
    };
    await deps.OAUTH3.api(directives.api, Object.assign({}, apiOpts, data.splitDomain));

    delete activeChallenges[domain];
  }
  async function setAsync(args, domain, challenge, keyAuth) {
    if (activeChallenges[domain]) {
      await removeAsync(args, domain, challenge);
    }

    var sessId = getReleventSessionId(domain);
    if (!sessId) {
      throw new Error('no DDNS module handles the domain ' + domain);
    }
    var session = await utils.getSession(sessId);
    var directives = await deps.OAUTH3.discover(session.token.aud);

    // I'm not sure what role challenge is supposed to play since even in the library
    // this code is based on it was never used, but check for it anyway because ...
    if (!challenge || keyAuth) {
      console.warn(new Error('DDNS challenge missing challenge or keyAuth'));
    }
    var keyAuthDigest = require('crypto').createHash('sha256').update(keyAuth || '').digest('base64')
      .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

    var challengeDomain = (args.test || '') + args.acmeChallengeDns + domain;
    var splitDomain = (await utils.splitDomains(directives.api, [challengeDomain]))[0];

    var apiOpts = {
      api: 'dns.set'
    , session: session
    , type: 'TXT'
    , value: keyAuthDigest
    , ttl: args.ttl || 0
    };
    await deps.OAUTH3.api(directives.api, Object.assign({}, apiOpts, splitDomain));

    activeChallenges[domain] = {
      sessId
    , keyAuthDigest
    , splitDomain
    };

    return new Promise(res => setTimeout(res, 1000));
  }

  // It might be slightly easier to use arguments and apply, but the library that will use
  // this function counts the arguments we expect.
  function set(a, b, c, d, done) {
    setAsync(a, b, c, d).then(result => done(null, result), done);
  }
  function remove(a, b, c, done) {
    removeAsync(a, b, c).then(result => done(null, result), done);
  }

  function getOptions() {
    return {
      oauth3: 'oauth3.org'
    , debug: conf.debug
    , acmeChallengeDns: '_acme-challenge.'
    };
  }

  return {
    getOptions
  , set
  , get
  , remove
  , loopback
  };
};