const fs = require('fs-extra')
const path = require('path')
const { firefox, devices } = require('playwright')


// spaces to be used for prettify/json.stringify
const indent = 4

// By market capitalization
// Ref: https://coinmarketcap.com/all/views/all/
let topCryptoCurrency = ["BTC","ETH","ADA","BNB","USDT","XRP","SOL","DOT","DOGE","USDC","UNI","LUNA","LINK","AVAX","LTC","BUSD","AAVE","FRAX","HBAR",
                         "BCH","ALGO","WBTC","ICP","MATIC","FIL","TRX","FTT","XLM","VET","ATOM","ETC","THETA","DAI","XMR","MANA","ZEC","TUSD","EOS","AXS",
                         "ONE","EGLD","CHZ","GRT","1INCH","INJ","ENJ","KSM","CRO","SHIB","LEO","NEAR","BTCB","FLOW","XTZ","KCS", "SAND","KLAY","MKR",
                        "HT","DFI","TTT","WAVES","HNT","BSV","USDP","MIOTA","FTM","XEC","RUNE","QNT","NEO","CAKE","STX","LRC","OKB","NEXO","ZIL","DASH",
                        "PAXG","CELO","BAT","CVX","CRV","KAVA","GALA","DCR","GNO","AMP","XDC","WEMIX","XEM","MINA","HOT","AR","GT","FEI","COMP","QTUM",
                        "KNC","BTG","KDA","mBTC","uBTC","mETH","XCH","BSW","BAKE","KAS", "TON", "RVN","LDO","MNT","APT","ARB","OP","USDD","IMX","APE","SNX",
                         "RNDR","RPL","XAUt","FXS","PEPE","CFX","CSPR","BTT","SUI","LUNC","GUSD","TWT","GMX","AKT","NFT","FLR","DYDX","WOO","MBX","AGIX",
                         "ORDI","1000SATS"]

let currencyCodesToRemove = ["CLF"]

let currLink, currLink2, currLink3, currLink3Key, cryptoLink
if (process.env.CI) {
  currLink = process.env.currlink
  currLink2 = process.env.currlink2
  currLink3 = process.env.currlink3
  currLink3Key = process.env.currlink3key
  cryptoLink = process.env.cryptolink
} else {
  [currLink, currLink2, currLink3, currLink3Key, cryptoLink] = fs.readFileSync(path.join(__dirname, 'links.ini')).toString().split(/\r?\n/).map(e => e.trim())
}
// curr means currency
// stores consolidated currencies in currcode:currname format i.e USD:US Dollar
let allcurr = fs.readFileSync(path.join(__dirname, 'allcurrencies.min.json')).toString()
allcurr = JSON.parse(allcurr)

// Takes allcurr and have all the keys in uppercase
const allcurrKeyUpper = {}
for (const [key, value] of Object.entries(allcurr)) { allcurrKeyUpper[key.toUpperCase()] = value }

// Takes allcurr and store in lowercase currname:currcode format i.e. us dollar:usd
const allcurrLower = {}
for (const [key, value] of Object.entries(allcurr)) { allcurrLower[value.toLowerCase()] = key.toLowerCase() }

const dateToday = new Date().toISOString().substring(0, 10)


begin()
// Begins the program
async function begin() {
  // launch the browser
 // await launchBrowser()

  const latestDir = path.join(__dirname, 'latest')

  const currJSON = await getCurrencies()
  // Get & Save All the available currencies in api
  const availCurrListObj = await getAvailCurrencyJSON(currJSON)
  fs.writeFileSync(path.join(latestDir, 'currencies.min.json'), JSON.stringify(availCurrListObj))
  fs.writeFileSync(path.join(latestDir, 'currencies.json'), JSON.stringify(availCurrListObj, null, indent))

  // Generate API files
  await generateFiles(currJSON)

  // Backup the latest currency files to today's date folder, for historical currency access
  const dateDir = path.join(__dirname, dateToday)
  fs.mkdirSync(dateDir, {
      recursive: true
    })
  fs.copySync(latestDir, dateDir)

  // Close the browser
//  await browser.close()
}


// Returns all the available currencies in the API
async function getAvailCurrencyJSON(CurrObj) {
  const availCurrListObj = {}
  for (const key of Object.keys(CurrObj)) { 
    availCurrListObj[key] = allcurrKeyUpper[key.toUpperCase()] || ""
    if(!allcurrKeyUpper[key.toUpperCase()])
    console.log(key,"currency code doesn't exist in allcurrencies.min.json")
   }

  return availCurrListObj
}

async function getCurrencies() {
let currDataObj = await getCurrData(currLink)
currDataObj = toLowerCaseKeysBaseCurr(currDataObj)

let currDataObj2 = await getCurrData(currLink2)
// we need to convert base usd to eur
currDataObj2 = toLowerCaseKeysBaseCurr(currDataObj2, currDataObj['usd'])

let currDataObj3 = toLowerCaseKeysBaseCurr( (await getCurrData3()) )
// we need to convert base usd to eur
currDataObj3 = 'eur' in currDataObj3 ? toLowerCaseKeysBaseCurr(currDataObj3, 1/currDataObj3['eur']) : {}

let cryptoDataObj = await getCryptoData()
// we also need to convert base usd to eur
cryptoDataObj = toLowerCaseKeysBaseCurr(cryptoDataObj,currDataObj['usd'])
  let CurrJSON = {...currDataObj2, ...currDataObj, ...cryptoDataObj, ...currDataObj3, eur: 1 }
  currencyCodesToRemove.forEach(e=>delete CurrJSON?.[e.toLowerCase()])
  // return sorted object
  return sortObjByKeys(CurrJSON)
}

// Euro as base rates
async function getCurrData(link){
  let response = await fetch(link)
  let data = await response.json()
  return data.rates
}

// USD as base rates
async function getCryptoData(){
  let response = await fetch(cryptoLink)
  let data = await response.json()
  let cleanJSON = {}
  for(let value of data.data.reverse())
    cleanJSON[value.symbol] = value.quote.USD.price

  return Object.fromEntries(                                                           // Dividing value by 1 to convert to 1 USD as base rate
    Object.entries(cleanJSON).filter(([k, v]) => topCryptoCurrency.includes(k)).map(([k,v])=>[k,1/v]) )
}

async function getCurrData3(){

    const browser = await firefox.launch({headless: true});
    let arrRes = []
    try{

    const context = await browser.newContext({ ...devices['Desktop Firefox'] });
    const page = await context.newPage();

    const reg = new RegExp(currLink3Key, "gi")
    page.on('response', async res => reg.test(res.url()) ? arrRes.push(await res.json().catch(console.error)) : '' );
    for(let i=0;i<3;i++){
        try{
            await page.goto(currLink3)
            break;
        }catch(e){}
    }

    arrRes = arrRes.filter(e=>e?.rates?.USD == 1)
    return arrRes.length > 0 ? arrRes[0].rates : {}

    }catch(e){
        console.error(e)
        return {}
    }finally{
        await browser.close()
    }
}

// convert object keys to lowercase and values to float
// tocurr parameter
// convert object having a base currency value to object of another base currency value
// For example: pass object with base currency as usd, pass tocurr as 1Eur to 1.17USD i.e 1.17 to convert object to base currency as EUR
function toLowerCaseKeysBaseCurr(obj, tocurr=1){
let newobj = {}
for(let [key,value] of Object.entries(obj)){
    let tempValue = parseFloat(value)*parseFloat(tocurr)
    if(Number.isFinite(tempValue))
    newobj[key.toLowerCase()] = tempValue
}
return newobj
}


// Sorts an object by keys and returns the sorted object
function sortObjByKeys(obj) {
  const sortedObj = {}
  const sortedKeys = Object.keys(obj).sort()
  for (const key of sortedKeys) { sortedObj[key] = obj[key] }
  return sortedObj
}

// Generates the api files
async function generateFiles(googBingCurrJSON) {
  const currenciesDir = path.join(__dirname, 'latest', 'currencies')
  for (const [fromKey, fromValue] of Object.entries(googBingCurrJSON)) {
    const tempObj = {}
    tempObj['date'] = dateToday;
    tempObj[fromKey] = {}
    const fromKeyDir = path.join(__dirname, 'latest', 'currencies', fromKey)
    fs.mkdirSync(fromKeyDir, {
      recursive: true
    })

    for (const [toKey, toValue] of Object.entries(googBingCurrJSON)) {
      const tempSingleObj = {}
      tempObj[fromKey][toKey] = currencyValue(fromValue, toValue)
      tempSingleObj['date'] = dateToday;
      tempSingleObj[toKey] = tempObj[fromKey][toKey]
      fs.writeFileSync(path.join(fromKeyDir, toKey + '.min.json'), JSON.stringify(tempSingleObj))
      fs.writeFileSync(path.join(fromKeyDir, toKey + '.json'), JSON.stringify(tempSingleObj, null, indent))
    }
    fs.writeFileSync(path.join(currenciesDir, fromKey + '.min.json'), JSON.stringify(tempObj))
    fs.writeFileSync(path.join(currenciesDir, fromKey + '.json'), JSON.stringify(tempObj, null, indent))
  }
}

// return 1 fromCurr as base currency for toCurr
// fromCurr & toCurr is against 1 USD or 1 EUR or something common
// For example, if you pass 74 INR & 0.84 EUR and 1 INR = 0.011 Eur
// It returns 0.011 , with numbers upto 20 decimal places
function currencyValue(fromCurr, toCurr) {
  return getSignificantNum(toCurr / fromCurr)
}

// If the value is very small, then return more significant digits
function getSignificantNum(num){
    let minSignificantDigits = 8
    if(num >= 0.1)
        return parseFloat(num.toFixed(minSignificantDigits))
    let strNum = num.toFixed(100)
    let numOfZeros = strNum.match(/^0\.0+/i)[0].length-2
    return parseFloat(num.toFixed(minSignificantDigits+numOfZeros))
}
