const R = require('ramda');
const { cpanel } = require('./lib/cpanel');
const { DOMAIN_DOMAIN, VALID_RECORD_TYPES } = require('./constants');
const { then, log, print, lazyTask, batchLazyTasks } = require('./helpers');

const BATCH_SIZE = 1;

const recordToRedirection = ({ name, address }) => ({
  domain: name === '@' ? DOMAIN_DOMAIN : `${name}.${DOMAIN_DOMAIN}`,
  redirect: address,
  type: 'permanent',
  redirect_wildcard: 1,
  redirect_www: 1,
});
const recordToZone = ({ name, type, address, id, priority }) => ({
  line: id,
  name: name === '@' ? `${DOMAIN_DOMAIN}.` : name,
  type,
  address,
  ...(type === 'MX' ? { priority } : {}),
  ...(type === 'CNAME' ? { cname: address } : {}),
  ...(type === 'TXT' ? { txtdata: address } : {}),
});

const cleanName = name =>
  [DOMAIN_DOMAIN, `${DOMAIN_DOMAIN}.`].includes(name) ? '@' : `${name}`.replace(new RegExp(`\\.${DOMAIN_DOMAIN}\\.?$`), '').toLowerCase();

const zoneToRecord = ({
  name,
  type,
  cname,
  address,
  priority,
  preference,
  exchange,
  record,
  line: id
}) =>
  ({
    id,
    name: cleanName(name),
    type: `${type}`,
    address: `${exchange || cname || address || record}`.replace(/\.$/g, '').toLowerCase(),
    priority: priority || preference,
  });
const redirectionToRecord = ({ domain, destination }) => ({
  id: domain,
  name: cleanName(domain),
  type: 'URL',
  address: `${destination}`.replace(/\/$/g, ''),
});

const recordToEmailMx = ({ name, address, priority }) => ({
  domain: `${name}.is-a.dev`,
  exchanger: address,
  priority,
})

const getHostKey = host =>
  `${host.name.toLowerCase()}##${host.type.toLowerCase()}##${host.address.toLowerCase()}`;

const isReserved = (domain) =>
  domain.name.startsWith('*') || !VALID_RECORD_TYPES.includes(domain.type)

const diffRecords = (oldRecords, newRecords) => {
  const isMatchingRecord = (a, b) => getHostKey(a) === getHostKey(b);

  const remove = R.differenceWith(isMatchingRecord, oldRecords, newRecords);
  const add = R.differenceWith(isMatchingRecord, newRecords, oldRecords);

  return { add, remove };
};

const executeBatch = (batches) => batches.reduce((promise, batch, index) => {
  return promise.then(async () => {
    log('>>> Running batch number:', index + 1, `(size: ${batch.length})`);

    const values = await Promise.all(batch.map(fn => fn().catch(e => console.error(e))));

    const results = values.map(data => R.pathOr({ result: data }, ['cpanelresult', 'data', 0], data));
    const failed = results.filter(x => (x.result || {}).status != 1);

    log(`${values.length - failed.length}/${values.length}`);
    failed.length && log(JSON.stringify(failed, null, 2));

    return null;
  });
}, Promise.resolve());

const getDomainService = ({ cpanel }) => {
  const fetchZoneRecords = R.compose(
    then(R.reject(isReserved)),
    then(R.map(zoneToRecord)),
    cpanel.zone.fetch
  );
  const fetchRedirections = R.compose(
    then(R.map(redirectionToRecord)),
    cpanel.redirection.fetch
  );

  const addZoneRecord = lazyTask(R.compose(
    R.ifElse(R.propEq('type', 'MX'),
      R.compose(cpanel.email.add, recordToEmailMx),
      cpanel.zone.add
    ),
    recordToZone,
    print(r => `Adding zone for ${r.name}: (${r.type} ${r.address})...`),
  ));
  const removeZoneRecord = lazyTask(R.compose(
    R.ifElse(R.propEq('type', 'MX'),
      R.compose(cpanel.email.remove, recordToEmailMx),
      R.compose(cpanel.zone.remove, R.pick(['line']))
    ),
    recordToZone,
    print(r => `Deleting zone for ${r.name}: (${r.type} ${r.address})...`),
  ));
  const addRedirection = lazyTask(R.compose(
    cpanel.redirection.add,
    recordToRedirection,
    print(({ name }) => `Adding redirection for ${name}`),
  ));
  const removeRedirection = lazyTask(R.compose(
    cpanel.redirection.remove,
    R.pick(['domain']),
    recordToRedirection,
    print(({ name }) => `Deleting redirection for ${name}`),
  ));

  const getHosts = () =>
    Promise.all([fetchZoneRecords(), fetchRedirections()]).then(R.flatten);

  const addRecords = R.compose(
    batchLazyTasks(BATCH_SIZE),
    R.filter(Boolean),
    R.map(R.cond([
      [R.propEq('type', 'URL'), addRedirection],
      [R.T, addZoneRecord],
    ])),
  );
  const removeRecords = R.compose(
    batchLazyTasks(BATCH_SIZE),
    R.map(R.cond([ [ R.propEq('type', 'URL'), removeRedirection ], [ R.T, removeZoneRecord ] ])),
    R.sort((a, b) => b.id - a.id)
  );

  const updateHosts = async hosts => {
    const remoteHostList = await getHosts();
    const { add, remove } = diffRecords(remoteHostList, hosts);
    console.log(`Adding ${add.length}; Removing ${remove.length}`)

    await executeBatch([
      ...removeRecords(remove),
      ...addRecords(add),
    ]);
    return { added: add.length, removed: remove.length };
  };

  return { getHosts, get: cpanel.zone.fetch, addZoneRecord, removeZoneRecord, updateHosts };
};

const domainService = getDomainService({ cpanel });

module.exports = {
  getDomainService,
  domainService,
  diffRecords,
};
