//------------------------------------------------------------------------------------
//npm install --save axios dotenv lodash moment
const axios = require("axios");
require('dotenv').config()
const lodash = require("lodash");
const moment = require("moment");

//------------------------------------------------------------------------------------
/*
BEFORE RUNNING THIS EXAMPLE: 

* Set AMPLIFI_BASE_URL and PAN in an .env file. (Speak to a support 
representative to be issued client credentials and URL after receiving access to the 
sandbox.)
* Set DEVICE_TAG using value obtained from onboarding a prospect.

*/
const AMPLIFI_BASE_URL = process.env.AMPLIFI_BASE_URL;
const PAN = process.env.PAN;
const DEVICE_TAG = "my_deviceTag1713"; //use same deviceTag from onboarding prospect
const AFIACCOUNTID = "qwegalv2jpwm7vgwd"; //use AFiAccountId that belongs to user corresponding to DEVICE_TAG


const todaysDate = new Date();
const dtsValueString = (todaysDate).valueOf().toString();
const accountNumber = lodash.padStart(dtsValueString, 16, "0"); //simulate accountNumber for external bank account

const credentialsTestChannel = {
  channel: "test",
  deviceTag: DEVICE_TAG,
  deviceData: {
    "platform": "test"
  }
};



//------------------------------------------------------------------------------------
//Get Authorization Token
/*
The ampliFi system exposes a REST API and expects calls directly from the front-end.
After onboarding, ampliFi authenticates each individual customer and performs 
transactions only explicitly allowed to that specific customer. Requests 
must have a valid authorization token in the request headers, so your first request 
in any workflow should be to /token in order to receive an authorization token. A 
valid token should be included in the headers of all subsequent requests.

*/
async function getAuthToken(credentials) {
  const data = credentials;
  const config = {
    method: 'PUT',
    url: `${AMPLIFI_BASE_URL}/token`,
    headers: {
      'Content-Type': "application/json"
    },
    data
  };

  let result;
  try {
    result = await axios.request(config);
    if (result.status === 200) {
      return Promise.resolve(result.data);
    }
  } catch (err) {
    console.log({
      errCode: err.code,
      responseStatus: err.response && err.response.status,
      data: err.response && JSON.stringify(err.response.data)
    });
  }
}



//------------------------------------------------------------------------------------
//Sync External Accounts
/*
The /externalaccounts/sync endpoint will allow you to add, modify, or list external
accounts. The action taken will depend on the request body. 

* To add a new account, you will include the external account details as an external
account object in the externalAccounts array request body property. The external
account object properties included will vary depending on the type of account. Do
not include the AFiExternalAccountId property when adding a new external account.
* To modify an existing external account, you will include an external account object
in the externalAccounts array containing the AFiExternalAccountId of the external
account to be modified and any allowed changes, such as the name property, comment
property, or isActive property.
* To list external accounts, the externalAccounts property will be an empty array.

*/
async function syncExternalAccounts(authToken, requesttype, AFiExternalAccountId) {
  const dataOptions = {
    newExternalDebitCardAccount: { //Request body of /externalaccounts/sync for a new external debit card
      "externalAccounts": [
        {
          "pan": PAN,
          "expiryYYYYMM": "202610",
          "cvv": "123",
          "name_on_card": "John Doe",
          "type": "DEBITCARD"
        }
      ]
    },
    newExternalBankAccount: { //Request body of /externalaccounts/sync for a new external bank account
      "externalAccounts": [
        {
          "isActive": true,
          "accountNumber": accountNumber,
          "routingNumber": "031101279",
          "type": "CHECKING",
          "title": "John Testman",
          "bankName": "Bank of America",
          "isOwn": true,
          "countryCode": "USA",
          "currency": "USD",
          "comment": "External checking account"
        }
      ]
    },
    modifyExternalAccount: { //Request body of /externalaccounts/sync to modify an external account
      "externalAccounts": [
        {
          "AFiExternalAccountId": AFiExternalAccountId,
          "backOfficeId": "connectFiBOIS",
          "backOfficeName": "connectFiBOIS",
          "isActive": true,
          "name": `BANameTest${(todaysDate / 1).toString()}`,
          "dtsModified": new Date(),
          "comment": "New comment"
        }
      ]
    },
    listExternalAccounts: { //Request body of /externalaccounts/sync to list external accounts
      "externalAccounts": []
    }
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/externalaccounts/sync`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    },
    data: dataOptions[requesttype]
  };

  let result;
  try {
    result = await axios.request(config);

    if (result.status === 200) {
      return Promise.resolve(result.data);
    }
  } catch (err) {
    console.log({
      errCode: err.code,
      responseStatus: err.response && err.response.status,
      data: err.response && JSON.stringify(err.response.data)
    });
  }
}



//------------------------------------------------------------------------------------
//Verify External Account
/*
Account will be verified using microdeposits.

*/
async function verifyExternalAccount(authToken, AFiExternalAccountId) {
  const data = {
    AFiExternalAccountId,
    "amount1": 1.01,
    "amount2": 1.01
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/externalaccount/verify`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    },
    data
  };

  let result;
  try {
    result = await axios.request(config);

    if (result.status === 200) {
      return Promise.resolve(result.data);
    }
  } catch (err) {
    console.log({
      errCode: err.code,
      responseStatus: err.response && err.response.status,
      data: err.response && JSON.stringify(err.response.data)
    });
  }
}



//------------------------------------------------------------------------------------
//Pull From External Account
/*
The /externalaccount/pull endpoint will pull funds from the specified external account
and deposit the funds into the account specified by AFiAccountId or the user's main
account. The amount transferred may be subject to configured maximums and/or minimums.
It is possible to create a recurring transaction by using "isRecurring": true.

*/
async function pullExternalAccount(authToken, AFiExternalAccountId, AFiAccountId) {
  const data = {
    "AFiExternalAccountId": AFiExternalAccountId,
    "AFiAccountId": AFiAccountId,
    "amount": 20,
    "currency": "USD",
    "isRecurring": false
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/externalaccount/pull`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    },
    data
  };

  let result;
  try {
    result = await axios.request(config);

    if (result.status === 200) {
      return Promise.resolve(result.data);
    }
  } catch (err) {
    console.log({
      errCode: err.code,
      responseStatus: err.response && err.response.status,
      data: err.response && JSON.stringify(err.response.data)
    });
  }
}



//------------------------------------------------------------------------------------
//Push to External Account
/*
The /externalaccount/push endpoint will push funds to the specified external account
after withdrawing the funds from the account specified by AFiAccountId or the user's main
account. The amount transferred may be subject to configured maximums and/or minimums.
It is possible to create a recurring transaction by using "isRecurring": true.


*/
async function pushExternalAccount(authToken, AFiExternalAccountId, AFiAccountId) {
  const data = {
    "AFiExternalAccountId": AFiExternalAccountId,
    "AFiAccountId": AFiAccountId,
    "amount": 0.01,
    "currency": "USD",
    "recurring": false
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/externalaccount/push`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    },
    data
  };

  let result;
  try {
    result = await axios.request(config);

    if (result.status === 200) {
      return Promise.resolve(result.data);
    }
  } catch (err) {
    console.log({
      errCode: err.code,
      responseStatus: err.response && err.response.status,
      data: err.response && JSON.stringify(err.response.data)
    });
  }
}



//------------------------------------------------------------------------------------
//Autofunding From External Account
/*
The /externalaccount/autofunding endpoint can be used to set up recurring topup
transactions. Auto-funding will transfer the specified set amount on a regular
schedule, or cadence. This example will transfer $100 from the specified external 
account when the user's main account reaches a "when_low" threshold of $10.

*/
async function autofundingExternalAccount(authToken, AFiExternalAccountId) {
  const data = {
    "AFiExternalAccountId": AFiExternalAccountId,
    "cadence": "when_low",
    "amount": 100,
    "currency": "USD",
    "lowAmount": 10.00
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/externalaccount/autofunding`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    },
    data
  };

  let result;
  try {
    result = await axios.request(config);

    if (result.status === 200) {
      return Promise.resolve(result.data);
    }
  } catch (err) {
    console.log({
      errCode: err.code,
      responseStatus: err.response && err.response.status,
      data: err.response && JSON.stringify(err.response.data)
    });
  }
}



async function removeResponseB64Images(response) {
  let shortenedExternalAccounts = [];
  //Removing B64 images from external accounts to shorten response
  if (response && response.externalAccounts && !lodash.isEmpty(response.externalAccounts)) {
    response.externalAccounts.forEach((externalAccount) => {
      shortenedExternalAccounts.push({
        ...externalAccount,
        logoB64: externalAccount.logoB64 ? "" : undefined
      })
    });
  }
  return {
    ...response,
    externalAccounts: shortenedExternalAccounts
  };
}



async function findExternalAccount(response, propertyType, propertyValue) {
  const externalAccount = response.externalAccounts.find((externalAccount) => {
    const createdAfterDate = moment(todaysDate).add(-1, "second")
      .toISOString();
    const todaysISODate = moment(todaysDate).toISOString();
    return externalAccount[propertyType] === propertyValue && externalAccount["dtsCreated"] >= createdAfterDate;
  });
  return externalAccount;
}

//------------------------------------------------------------------------------------
//Run the walkthrough
async function ampliFiExternalAccountsWalkthrough() {

  //Get Authorization Token credentialsTestChannel
  console.log(`Start /token example credentialsTestChannel.\n`);
  const authObjectTest = await getAuthToken(credentialsTestChannel);
  let authToken = undefined;
  if (authObjectTest) {
    console.log(`Successfully obtained test user authorization: ${JSON.stringify(authObjectTest)}\n`);
    authToken = authObjectTest.token;
    console.log(`Authorization token: ${authToken}\n`);
  } else {
    console.log(`Error getting Test authorization token\n`)
  }
  console.log(`End /token example credentialsTestChannel.\n`);


  //Sync External Accounts newExternalDebitCardAccount
  console.log(`Start /externalaccounts/sync newExternalDebitCardAccount example.\n`);
  let syncEAsResultDC;
  let aFiEAIdDC
  if (authToken) {
    const result = await syncExternalAccounts(authToken, "newExternalDebitCardAccount", undefined);
    syncEAsResultDC = result && await removeResponseB64Images(result);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (syncEAsResultDC) {
    console.log(`Successfully synced external account: ${JSON.stringify(syncEAsResultDC)}\n`);
    const aFiEA = await findExternalAccount(syncEAsResultDC, "panMasked", `${PAN.substring(0, 1)}...${PAN.substring(PAN.length - 4)}`);
    aFiEAIdDC = aFiEA && aFiEA.AFiExternalAccountId;
    console.log(`AFiExternalAccountId for new debit card: ${aFiEAIdDC}\n`);
  }
  console.log(`End /externalaccounts/sync newExternalDebitCardAccount example.\n`);


  //Sync External Accounts newExternalBankAccount
  console.log(`Start /externalaccounts/sync newExternalBankAccount example.\n`);
  let syncEAsResultBA;
  let aFiEAIdBA
  if (authToken) {
    const result = await syncExternalAccounts(authToken, "newExternalBankAccount", undefined);
    syncEAsResultBA = result && await removeResponseB64Images(result);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (syncEAsResultBA) {
    console.log(`Successfully synced external account: ${JSON.stringify(syncEAsResultBA)}\n`);
    const aFiEA = await findExternalAccount(syncEAsResultBA, "accountNumber", accountNumber);
    aFiEAIdBA = aFiEA && aFiEA.AFiExternalAccountId;
    console.log(`AFiExternalAccountId for new bank account: ${aFiEAIdBA}\n`);
  }
  console.log(`End /externalaccounts/sync newExternalBankAccount example.\n`);


  //Sync External Accounts modifyExternalAccount
  console.log(`Start /externalaccounts/sync modifyExternalAccount example.\n`);
  let syncEAsResultMod;
  if (authToken) {
    const result = await syncExternalAccounts(authToken, "modifyExternalAccount", aFiEAIdBA);
    syncEAsResultMod = result && await removeResponseB64Images(result);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (syncEAsResultMod) {
    console.log(`Successfully modified external account: ${JSON.stringify(syncEAsResultMod)}\n`);
    const aFiEA = await findExternalAccount(syncEAsResultMod, "AFiExternalAccountId", aFiEAIdBA);
    console.log(`Modified account: ${JSON.stringify(aFiEA)}\n`);
  }
  console.log(`End /externalaccounts/sync modifyExternalAccount example.\n`);


  //Sync External Accounts listExternalAccounts
  console.log(`Start /externalaccounts/sync listExternalAccounts example.\n`);
  let syncEAsResultList;
  if (authToken) {
    const result = await syncExternalAccounts(authToken, "listExternalAccounts", undefined);
    syncEAsResultList = result && await removeResponseB64Images(result);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (syncEAsResultList) {
    console.log(`Successfully listed external accounts: ${JSON.stringify(syncEAsResultList)}\n`);
  }
  console.log(`End /externalaccounts/sync listExternalAccounts example.\n`);


  //Verify External Account
  console.log(`Start /externalaccounts/verify example.\n`);
  let verifyEAsResult;
  if (authToken) {
    verifyEAsResult = await verifyExternalAccount(authToken, aFiEAIdBA);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (verifyEAsResult) {
    console.log(`Successfully verified external accounts: ${JSON.stringify(verifyEAsResult)}\n`);
  }
  console.log(`End /externalaccounts/verify example.\n`);


  //Pull From External Account
  console.log(`Start /externalaccount/pull example.\n`);
  let pullEAsResult;
  if (authToken) {
    pullEAsResult = await pullExternalAccount(authToken, aFiEAIdBA, AFIACCOUNTID);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (pullEAsResult) {
    console.log(`Successfully pulled from external account: ${JSON.stringify(pullEAsResult)}\n`);
  }
  console.log(`End /externalaccounts/pull example.\n`);


  //Push From External Account
  console.log(`Start /externalaccount/push example.\n`);
  let pushEAsResult;
  if (authToken) {
    pushEAsResult = await pushExternalAccount(authToken, aFiEAIdBA, AFIACCOUNTID);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (pushEAsResult) {
    console.log(`Successfully pushed from external account: ${JSON.stringify(pushEAsResult)}\n`);
  }
  console.log(`End /externalaccounts/push example.\n`);


  //Auto-funding From External Account
  console.log(`Start /externalaccount/autofunding example.\n`);
  let autofundingEAsResult;
  if (authToken) {
    autofundingEAsResult = await autofundingExternalAccount(authToken, aFiEAIdBA);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (autofundingEAsResult) {
    console.log(`Successfully set up autofunding from external account: ${JSON.stringify(autofundingEAsResult)}\n`);
  }
  console.log(`End /externalaccounts/autofunding example.\n`);

}


if (process.env.PAN && process.env.AMPLIFI_BASE_URL) {
  ampliFiExternalAccountsWalkthrough();
} else {
  console.log("Before running the walkthrough, set the required .env variables.");
}