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

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

* Set AMPLIFI_BASE_URL, AMPLIFI_TEST_DEVICETAG, and SERVER_SECRET in an .env file. 
(Speak to a support representative to be issued client credentials and URL after 
  receiving access to the sandbox.)
* AMPLIFI_TEST_DEVICETAG and SERVER_SECRET are obtained from onboarding a prospect.

*/
const AMPLIFI_BASE_URL = process.env.AMPLIFI_BASE_URL;
const SERVER_SECRET = process.env.SERVER_SECRET; //obtained when onboarding prospect
const DEVICE_TAG = process.env.AMPLIFI_TEST_DEVICETAG; //use same deviceTag from onboarding prospect

const dtsValueString = (new Date()).valueOf().toString();
const INT_TAG = lodash.padStart(dtsValueString, 10, "0") + Math.floor(Math.random() * 1000); //Unique tag to identify a generic request and avoid duplicates, typically a string of digits
const halfRef = `some1RANDOM2string${(dtsValueString / 1).toString()}`;
const cryptotext = CryptoJS.AES.encrypt(DEVICE_TAG + dtsValueString, SERVER_SECRET).toString();

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

const credentialsAndroidChannel = {
  "channel": "android_v1",
  "dtsValueString": dtsValueString,
  "deviceTag": DEVICE_TAG,
  "socket": {
    halfRef: halfRef
  },
  "cryptotext": cryptotext
};

const credentialsIOSChannel = {
  "channel": "ios_v1",
  "dtsValueString": dtsValueString,
  "deviceTag": DEVICE_TAG,
  "socket": {
    halfRef: halfRef
  },
  "cryptotext": cryptotext
};

const credentialsBrowserChannel = {
  "channel": "browser",
  "deviceId": DEVICE_TAG
};

//------------------------------------------------------------------------------------
//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)
    });
  }
}



//------------------------------------------------------------------------------------
//Open Account
/*
A new account may be requested using the "newCurrentAccount" generic request handler 
and the PUT /requests endpoint.

Opening cards and accounts is a segment specific process, so it is handled through
a generic request. Generic Requests can be defined to open vAccounts for businesses or 
individuals, debit cards for businesses or individuals, or credit cards for individuals.
In addition, cards or accounts can be specific to family members, business representatives,
employees, etc. This is just an illustrative example of one type of account that can be opened.

*/
async function openAccount(authToken, intTag) {
  const data = {
    typeId: "newCurrentAccount",
    intTag
  }
  const config = {
    method: 'PUT',
    url: `${AMPLIFI_BASE_URL}/requests`,
    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)
    });
  }
}



//------------------------------------------------------------------------------------
//List Accounts
/*
The GET /accounts endpoint will allow you to get an array of records for all accounts
belonging to the currently logged in user. 


*/
async function listAccounts(authToken) {

  const config = {
    method: 'GET',
    url: `${AMPLIFI_BASE_URL}/accounts`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    }
  };

  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)
    });
  }
}



//------------------------------------------------------------------------------------
//Get Account
/*
The GET /accounts/:AFiAccountId endpoint will retrieve the record for one specific
account. The account is identified by the path parameter and must belong to the 
current user.

*/
async function getAccount(authToken, AFiAccountId) {

  const config = {
    method: 'GET',
    url: `${AMPLIFI_BASE_URL}/accounts/${AFiAccountId}`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    }
  };

  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)
    });
  }
}



//------------------------------------------------------------------------------------
//Modify Account
/*
The POST /accounts/:AFiAccountId endpoint may be used to make changes to an account's
properties. The account is identified by the path parameter, AFiAccountId. Only the 
properties to be modified should be specified in the payload, and the ampliFi API
will only add or update the permitted (editable) properties.

*/
async function modifyAccount(authToken, AFiAccountId) {
  const data = {
    "name": "MainAccount",
    "isMain": true,
    "isHidden": false
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/accounts/${AFiAccountId}`,
    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)
    });
  }
}



//------------------------------------------------------------------------------------
//Close Account
/*
The DELETE /accounts/:AFiAccountId endpoint closes the specified account. The account
is identified by the path parameter and must belong to the current user.

If the closed account was the main account, this will assign another account as the 
main. If the account has a balance, it will be transferred to the new main account. 
If the user has no other accounts, this API will mark the user as inactive and will 
fire an event with the message:

"Account closed money needs to be returned."


*/
async function closeAccount(authToken, AFiAccountId) {

  const config = {
    method: 'DELETE',
    url: `${AMPLIFI_BASE_URL}/accounts/${AFiAccountId}`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    }
  };

  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)
    });
  }
}



//------------------------------------------------------------------------------------
//Get Recent Transactions
/*
The GET /accounts/:AFiAccountId/transactions/latest endpont returns an array containing 
records of the last 30 transactions for a specified account, or all transactions within 
the last 12 months, whichever is fewer. The account is identified by the path parameter 
AFiAccountId.

*/
async function getRecentTransactions(authToken, AFiAccountId) {
  const config = {
    method: 'GET',
    url: `${AMPLIFI_BASE_URL}/accounts/${AFiAccountId}/transactions/latest`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    }
  };

  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)
    });
  }
}



//------------------------------------------------------------------------------------
//Get Transactions by Date Range
/*
The GET /accounts/:AFiAccountId/transactions/:dateStart/:dateEnd endpont an array 
containing records of all transactions for a specified account for a given time period. 
The account is identified by the path parameter AFiAccountId. The time period is defined 
by the path parameters dateStart and dateEnd. The dates are given in ISO date format, 
and the period runs from dateStart to dateEnd, inclusive.

Transaction records include various fields depending on their nature, status, and 
circumstances.

The format used for :dateStart and :dateEnd is the number of milliseconds since midnight
January 1, 1970, obtained by new Date(//desired date).valueOf() 

*/
async function getTransactionsByRange(authToken, AFiAccountId, dateStart, dateEnd) {
  const config = {
    method: 'GET',
    url: `${AMPLIFI_BASE_URL}/accounts/${AFiAccountId}/transactions/${dateStart}/${dateEnd}`,
    headers: {
      'Content-Type': "application/json",
      'token': authToken //previously obtained authorization token is required
    }
  };

  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)
    });
  }
}



//------------------------------------------------------------------------------------
//Modify Transaction
/*
The POST /account/:AFiAccountId/transaction/:transactionId endpont makes changes to a 
transaction record, as long as the specified items can be changed. The account is 
identified by the path parameter AFiAccountId, and the transaction is identified by 
the path parameter transactionId.

NOTE: "account" may also be given as "accounts", and "transaction" may also be given 
as "transactions".

*/
async function modifyTransaction(authToken, AFiAccountId, transactionId) {
  const data = {
    "changes": {
      "comment": "test123",
      "labels": ["test123"]
    }
  };

  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/account/${AFiAccountId}/transaction/${transactionId}`,
    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)
    });
  }
}



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

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


  //Open Account
  console.log(`Start PUT /requests example.\n`);
  let openAccountsResult;
  if (authToken) {
    openAccountsResult = await openAccount(authToken, INT_TAG);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (openAccountsResult) {
    console.log(`Successfully requested new account to be opened: ${JSON.stringify(openAccountsResult)}\n`);
  }
  console.log(`End PUT /requests example.\n`);


  //List Accounts
  console.log(`Start GET /accounts example.\n`);
  let listAccountsResult;
  let AFiAccountId
  if (authToken) {
    listAccountsResult = await listAccounts(authToken);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (listAccountsResult) {
    console.log(`Successfully listed accounts (if any): ${JSON.stringify(listAccountsResult)}\n`);
    AFiAccountId = listAccountsResult.accounts[0] ? listAccountsResult.accounts[0].AFiAccountId : undefined;
    console.log(AFiAccountId ? `AFiAccountId: ${AFiAccountId}\n` : `No accounts found.`)

  }
  console.log(`End GET /accounts example.\n`);


  //Get Account
  console.log(`Start GET /accounts/:AFiAccountId example.\n`);
  let getAccountResult;
  if (authToken && AFiAccountId) {
    getAccountResult = await getAccount(authToken, AFiAccountId);
  } else {
    console.log(`Authorization token and AFiAccountId are required.\n`)
  }
  if (getAccountResult) {
    console.log(`Successfully found account: ${JSON.stringify(getAccountResult)}\n`);
  }
  console.log(`End GET /accounts/:AFiAccountId example.\n`);


  //Modify Account
  console.log(`Start POST /accounts/:AFiAccountId example.\n`);
  let modifyAccountResult;
  if (authToken && AFiAccountId) {
    modifyAccountResult = await modifyAccount(authToken, AFiAccountId);
  } else {
    console.log(`Authorization token and AFiAccountId are required.\n`)
  }
  if (modifyAccountResult) {
    console.log(`Successfully modified account: ${JSON.stringify(modifyAccountResult)}\n`);
  }
  console.log(`End POST /accounts/:AFiAccountId example.\n`);


  //Get Recent Transactions
  console.log(`Start GET /accounts/:AFiAccountId/transactions/latest example.\n`);
  let getRecentTransactionsResult;
  if (authToken && AFiAccountId) {
    getRecentTransactionsResult = await getRecentTransactions(authToken, AFiAccountId);
  } else {
    console.log(`Authorization token and AFiAccountId are required.\n`)
  }
  if (getRecentTransactionsResult) {
    console.log(`Successfully retrieved recent transactions: ${JSON.stringify(getRecentTransactionsResult)}\n`);
  }
  console.log(`End GET /accounts/:AFiAccountId/transactions/latest example.\n`);


  //Get Transactions by Date Range
  console.log(`Start GET /accounts/:AFiAccountId/transactions/:dateStart/:dateEnd example.\n`);
  let getTransactionsByRangeResult;
  let transactionId;
  if (authToken && AFiAccountId) {
    getTransactionsByRangeResult = await getTransactionsByRange(authToken, AFiAccountId, new Date("2024-01-01").valueOf(), new Date("2024-05-28").valueOf());
  } else {
    console.log(`Authorization token and AFiAccountId are required.\n`)
  }
  if (getTransactionsByRangeResult) {
    console.log(`Successfully retrieved transactions by date range: ${JSON.stringify(getTransactionsByRangeResult)}\n`);
    transactionId = getTransactionsByRangeResult[0] ? getTransactionsByRangeResult[0].transactionId : undefined;
    console.log(transactionId ? `transactionId: ${transactionId}\n` : `No transactions found.`)

  }
  console.log(`End GET /accounts/:AFiAccountId/transactions/:dateStart/:dateEnd example.\n`);


  //Modify Transaction
  console.log(`Start POST /account/:AFiAccountId/transaction/:transactionId example.\n`);
  let modifyTransactionResult;
  if (authToken && AFiAccountId && transactionId) {
    modifyTransactionResult = await modifyTransaction(authToken, AFiAccountId, transactionId);
  } else {
    console.log(`Authorization token, AFiAccountId, and transactionId are required.\n`)
  }
  if (modifyTransactionResult) {
    console.log(`Successfully pushed from external account: ${JSON.stringify(modifyTransactionResult)}\n`);
  }
  console.log(`End /POST /account/:AFiAccountId/transaction/:transactionId example.\n`);



  //Close Account
  /*console.log(`Start DELETE /accounts/:AFiAccountId example.\n`);
  let closeAccountResult;
  if (authToken && AFiAccountId) {
    closeAccountResult = await closeAccount(authToken, AFiAccountId);
  } else {
    console.log(`Authorization token and AFiAccountId are required.\n`)
  }
  if (closeAccountResult) {
    console.log(`Successfully closed account: ${JSON.stringify(closeAccountResult)}\n`);
  }
  console.log(`End DELETE /accounts/:AFiAccountId example.\n`);
  */
}


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