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

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

* Set AMPLIFI_BASE_URL, AMPLIFI_TEST_DEVICETAG, AFICOMPANYID, 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, AFICOMPANYID, 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; //deviceTag from onboarding prospect
const AFICOMPANYID = process.env.AFICOMPANYID; //existing account of the onboarded business user matching the SERVER_SECRET and DEVICE_TAG

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 cardReference = "123";

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 Card
/*
A new card may be opened using the "ccc_admin_new_account_self" generic request handler 
and the PUT /requests endpoint. This particular type of generic request opens a new card
for a business user, so the AFiCompanyId is also required.

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 card that can be opened.

*/
async function openCard(authToken, intTag, AFiCompanyId) {
  const data = {
    typeId: "ccc_admin_new_account_self",
    intTag,
    payload: {
      AFiCompanyId
    }
  }
  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 Cards
/*
This endpoint returns an array of records for all cards that belong to this user.

*/
async function listCards(authToken) {

  const config = {
    method: 'GET',
    url: `${AMPLIFI_BASE_URL}/cards`,
    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 Card
/*
This endpoint returns a record for a single card, identified by a path parameter,
belonging to the logged in user. Some fields may vary depending on the requirements of 
the back office. Also, some fields such as name and pin are stored by the back office 
only and are not saved by ampliFi.

*/
async function getCard(authToken, AFiCardId) {

  const config = {
    method: 'GET',
    url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}`, //or /card/${AFiCardId}
    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 Card
/*
This endpoint modifies the properties of a card, which is identified by the path 
parameter. Only the properties to be changed should be included in the payload, and 
changes will only be made to properties that are permitted to change.

*/
async function modifyCard(authToken, AFiCardId) {
  const data = {
    "name": `testModified${cardReference}`,
    "isMain": true
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}`, //or /card/${AFiCardId}
    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)
    });
  }
}



//------------------------------------------------------------------------------------
//Activate Card
/*
This endpoint activates a card, if it has not already been activated. The card is 
identified by the path parameter. If the card was previously activated, then this 
call has no effect, and the card record is returned. If the call succeeds, the record 
for the newly activated card is returned with the status set to "active" and the 
isActive property set to true.

*/
async function activateCard(authToken, AFiCardId) {
  data = {
    //none
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}/activate`, //or /card/${AFiCardId}/activate
    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)
    });
  }
}



//------------------------------------------------------------------------------------
//Modify Card PIN
/*
This endpoint changes a card's PIN. The card is identified by a path parameter, and 
the new PIN must be given as 8 digits in the JSON formatted body of the request. 
However, only digits 3-6 will be extracted and sent to the back office as the actual 
PIN. The PIN is stored by the back office and cannot be retrieved.

*/
async function modifyCardPIN(authToken, AFiCardId, newPIN) {
  data = {
    "pin": newPIN
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/card/${AFiCardId}/pin`,
    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)
    });
  }
}



//------------------------------------------------------------------------------------
//Toggle Card On/Off
/*
Turning a card off means no transactions will be authorized. The card status will be 
set to "blocked" and the isActive property will be set to false.

Turning a card on means that transactions can be authorized. The card status will be 
set to "active" and the isActive property will be set to true.

The card is identified by a path parameter. There is no request body.


*/
async function toggleCardOnOff(authToken, AFiCardId, OnOff = "on") {
  data = {
    //none
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}/${OnOff}`, //or /card/${AFiCardId}/${OnOff}
    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)
    });
  }
}



//------------------------------------------------------------------------------------
//Reissue Card
/*
This endpoint reissues a card when the existing card is lost, damaged or stolen. The 
card is identified by a path parameter. The reason for the reissue is given in the 
request body and can be either "lost" or "stolen".

If the reason is "stolen", then the card will be given a new number. The reissue 
reason "lost" means that the card will have the same card number, but it may get a 
different expiration date. Unless the card number was compromised one way or 
another, use "lost".

*/
async function reissueCard(authToken, AFiCardId) {
  data = {
    reason: "lost"
  };
  const config = {
    method: 'POST',
    url: `${AMPLIFI_BASE_URL}/card/${AFiCardId}/reissue`,
    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 Card
/*
This endpoint closes a card. The card is identified by the path parameter.

*/
async function closeCard(authToken, AFiCardId) {

  const config = {
    method: 'DELETE',
    url: `${AMPLIFI_BASE_URL}/cards/${AFiCardId}`, //or /card/${AFiCardId}
    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)
    });
  }
}



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

  //Get Authorization Token
  console.log(`Start /token example.\n`);
  const authObjectTest = await getAuthToken(credentialsAndroidChannel);
  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 Card
  console.log(`Start PUT /requests example.\n`);
  let openTempCardResult;
  if (authToken) {
    openTempCardResult = await openCard(authToken, INT_TAG, AFICOMPANYID);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (openTempCardResult) {
    console.log(`Successfully created new business debit card: ${JSON.stringify(openTempCardResult)}\n`);
  }
  console.log(`End PUT /requests example.\n`);


  //List Cards
  console.log(`Start GET /cards example.\n`);
  let listCardsResult;
  let AFiCardId
  if (authToken) {
    listCardsResult = await listCards(authToken);
  } else {
    console.log(`Authorization token is required.\n`)
  }
  if (listCardsResult) {
    console.log(`Successfully listed cards (if any): ${JSON.stringify(listCardsResult)}\n`);

    //Find the card we created earlier
    const testDate = moment(new Date()).add(-1, "minute");
    const AFiCard = lodash.find(listCardsResult.cards, (card) => {
      return card.isActivated === false && card.isActive === null && moment(card.dtsOpened) > testDate;
    });
    console.log(AFiCard ? `AFiCard: ${JSON.stringify(AFiCard)}\n` : `No card found.`)
    AFiCardId = AFiCard ? AFiCard.AFiCardId : undefined;
    console.log(AFiCardId ? `AFiCardId: ${AFiCardId}\n` : `No card id found.`)

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


  //Get Card
  console.log(`Start GET /cards/:AFiCardId example.\n`);
  let getCardResult;
  if (authToken && AFiCardId) {
    getCardResult = await getCard(authToken, AFiCardId);
  } else {
    console.log(`Authorization token and AFiCardId are required.\n`)
  }
  if (getCardResult) {
    console.log(`Successfully found card: ${JSON.stringify(getCardResult)}\n`);
  }
  console.log(`End GET /cards/:AFiCardId example.\n`);


  //Modify Card
  console.log(`Start POST /cards/:AFiCardId example.\n`);
  let modifyCardResult;
  if (authToken && AFiCardId) {
    modifyCardResult = await modifyCard(authToken, AFiCardId);
  } else {
    console.log(`Authorization token and AFiCardId are required.\n`)
  }
  if (modifyCardResult) {
    console.log(`Successfully modified card: ${JSON.stringify(modifyCardResult)}\n`);
  }
  console.log(`End POST /cards/:AFiCardId example.\n`);


  //Activate Card
  console.log(`Start POST /cards/:AFiCardId/activate example.\n`);
  let activateCardResult;
  if (authToken && AFiCardId) {
    activateCardResult = await activateCard(authToken, AFiCardId);
  } else {
    console.log(`Authorization token and AFiCardId are required.\n`)
  }
  if (activateCardResult) {
    console.log(`Successfully activated card: ${JSON.stringify(activateCardResult)}\n`);
  }
  console.log(`End POST /cards/:AFiCardId/activate example.\n`);


  //Modify Card PIN
  console.log(`Start POST /card/:AFiCardId/pin example.\n`);
  let modifyCardPINResult;
  if (authToken && AFiCardId) {
    modifyCardPINResult = await modifyCardPIN(authToken, AFiCardId, "99123499");
  } else {
    console.log(`Authorization token and AFiCardId are required.\n`)
  }
  if (modifyCardPINResult) {
    console.log(`Successfully modified card PIN: ${JSON.stringify(modifyCardPINResult)}\n`);

  }
  console.log(`End GET POST /card/:AFiCardId/pin example.\n`);


  //Toggle Card Off
  console.log(`Start POST /cards/:AFiCardId/:OnOff example.\n`);
  let toggleCardOffResult;
  if (authToken && AFiCardId) {
    toggleCardOffResult = await toggleCardOnOff(authToken, AFiCardId, "off");
  } else {
    console.log(`Authorization token and AFiCardId are required.\n`)
  }
  if (toggleCardOffResult) {
    console.log(`Successfully toggled card off: ${JSON.stringify(toggleCardOffResult)}\n`);
  }
  console.log(`End /POST /cards/:AFiCardId/:OnOff example.\n`);


  //Toggle Card On
  console.log(`Start POST /cards/:AFiCardId/:OnOff example.\n`);
  let toggleCardOnResult;
  if (authToken && AFiCardId) {
    toggleCardOnResult = await toggleCardOnOff(authToken, AFiCardId, "on");
  } else {
    console.log(`Authorization token and AFiCardId are required.\n`)
  }
  if (toggleCardOnResult) {
    console.log(`Successfully toggled card on: ${JSON.stringify(toggleCardOnResult)}\n`);
  }
  console.log(`End /POST /cards/:AFiCardId/:OnOff example.\n`);


  //Reissue Card
  console.log(`Start POST /card/:AFiCardId/reissue example.\n`);
  let reissueCardResult;
  if (authToken && AFiCardId) {
    reissueCardResult = await reissueCard(authToken, AFiCardId);
  } else {
    console.log(`Authorization token and AFiCardId are required.\n`)
  }
  if (reissueCardResult) {
    console.log(`Successfully reissued card: ${JSON.stringify(reissueCardResult)}\n`);
  }
  console.log(`End /POST /card/:AFiCardId/reissue example.\n`);


  //Close Card
  console.log(`Start DELETE /cards/:AFiCardId example.\n`);
  let closeCardResult;
  if (authToken && AFiCardId) {
    closeCardResult = await closeCard(authToken, AFiCardId);
  } else {
    console.log(`Authorization token and AFiCardId are required.\n`)
  }
  if (closeCardResult) {
    console.log(`Successfully closed card: ${JSON.stringify(closeCardResult)}\n`);
  }
  console.log(`End DELETE /cards/:AFiCardId example.\n`);

}


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