import {
  Bech32, fromBase64, fromHex, toHex,
} from '@cosmjs/encoding'
import { sha256, stringToPath } from '@cosmjs/crypto'
// ledger
import TransportWebBLE from '@ledgerhq/hw-transport-web-ble'
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
import CosmosApp from 'ledger-cosmos-js'
import { LedgerSigner } from '@cosmjs/ledger-amino'

import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'
import utc from 'dayjs/plugin/utc'
import localeData from 'dayjs/plugin/localeData'
import { $themeColors } from '@themeConfig'
import PingWalletClient from './signing'

dayjs.extend(localeData)
dayjs.extend(duration)
dayjs.extend(relativeTime)
dayjs.extend(utc)

export function getLocalObject(name) {
  const text = localStorage.getItem(name)
  if (text) {
    return JSON.parse(text)
  }
  return null
}

export function getLocalChains() {
  return getLocalObject('chains')
}

export function getLocalAccounts() {
  return getLocalObject('accounts')
}

export function getLocalTxHistory() {
  return getLocalObject('txHistory')
}

export function setLocalTxHistory(newTx) {
  const txs = getLocalTxHistory()
  if (txs) {
    txs.push(newTx)
    return localStorage.setItem('txHistory', JSON.stringify(txs))
  }
  return localStorage.setItem('txHistory', JSON.stringify([newTx]))
}

export async function connectLedger(transport = 'usb') {
  const trans = await transport === 'usb' ? TransportWebUSB.create() : TransportWebBLE.create()
  return new CosmosApp(trans)
}

export function operatorAddressToAccount(operAddress) {
  const { prefix, data } = Bech32.decode(operAddress)
  if (prefix === 'iva') { // handle special cases
    return Bech32.encode('iaa', data)
  }
  if (prefix === 'crocncl') { // handle special cases
    return Bech32.encode('cro', data)
  }
  return Bech32.encode(prefix.replace('valoper', ''), data)
}

// TODO, not tested
export function pubkeyToAccountAddress(pubkey, prefix) {
  return Bech32.encode(prefix, pubkey, 40)
}

export function addressDecode(address) {
  return Bech32.decode(address)
}

export function addressEnCode(prefix, pubkey) {
  return Bech32.encode(prefix, pubkey)
}

export function getUserCurrency() {
  const currency = localStorage.getItem('currency')
  return currency || 'usd'
}

export function setUserCurrency(currency) {
  localStorage.setItem('currency', currency)
}

export function chartColors() {
  const colors = ['#6610f2', '#20c997', '#000000', '#FF0000',
    '#800000', '#FFFF00', '#808000', '#00FF00', '#008000', '#00FFFF',
    '#008080', '#0000FF', '#000080', '#FF00FF', '#800080']
  return Object.values($themeColors).concat(colors)
}

export function getUserCurrencySign() {
  let s = ''
  switch (getUserCurrency()) {
    case 'cny':
    case 'jpy':
      s = '¥'
      break
    case 'krw':
      s = '₩'
      break
    case 'eur':
      s = '€'
      break
    default:
      s = '$'
  }
  return s
}

export function consensusPubkeyToHexAddress(consensusPubkey) {
  let raw = null
  if (typeof consensusPubkey === 'object') {
    raw = toHex(fromBase64(consensusPubkey.value))
  } else {
    raw = toHex(Bech32.decode(consensusPubkey).data).toUpperCase().replace('1624DE6420', '')
  }
  const address = toHex(sha256(fromHex(raw))).slice(0, 40).toUpperCase()
  return address
}

function toSignAddress(addr) {
  const { data } = addressDecode(addr)
  return addressEnCode('cosmos', data)
}

function getHdPath(address) {
  let hdPath = "m/44'/118/0'/0/0"
  Object.values(getLocalAccounts()).forEach(item => {
    const curr = item.address.find(i => i.addr === address)
    if (curr && curr.hdpath) {
      hdPath = curr.hdpath
    }
  })
  // return [44, 118, 0, 0, 0]
  //  m/0'/1/2'/2/1000000000
  return stringToPath(hdPath)
}

export async function sign(device, chainId, signerAddress, messages, fee, memo, signerData) {
  let transport
  let signer
  switch (device) {
    case 'ledgerBle':
      transport = await TransportWebBLE.create()
      signer = new LedgerSigner(transport, { hdPaths: [getHdPath(signerAddress)] })
      break
    case 'ledgerUSB':
      transport = await TransportWebUSB.create()
      signer = new LedgerSigner(transport, { hdPaths: [getHdPath(signerAddress)] })
      break
    case 'keplr':
    default:
      if (!window.getOfflineSigner || !window.keplr) {
        throw new Error('Please install keplr extension')
      }
      await window.keplr.enable(chainId)
      signer = window.getOfflineSigner(chainId)
      // signer = window.getOfflineSignerOnlyAmino(chainId)
  }

  // if (signer) return signAmino(signer, signerAddress, messages, fee, memo, signerData)

  // Ensure the address has some tokens to spend
  const client = await PingWalletClient.offline(signer)
  return client.signAmino(device === 'keplr' ? signerAddress : toSignAddress(signerAddress), messages, fee, memo, signerData)
  // return signDirect(signer, signerAddress, messages, fee, memo, signerData)
}

export async function getLedgerAddress(transport = 'blu', hdPath = "m/44'/118/0'/0/0") {
  const trans = transport === 'usb' ? await TransportWebUSB.create() : await TransportWebBLE.create()
  const signer = new LedgerSigner(trans, { hdPaths: [stringToPath(hdPath)] })
  return signer.getAccounts()
}

export function toDuration(value) {
  return dayjs.duration(value).humanize()
}

// unit(y M d h m s ms)
export function timeIn(time, amount, unit = 's') {
  const input = dayjs(time).add(amount, unit)
  return dayjs().unix() > input.unix()
}

export function toDay(time, format = 'long') {
  if (format === 'long') {
    return dayjs(time).format('YYYY-MM-DD HH:mm')
  }
  if (format === 'date') {
    return dayjs(time).format('YYYY-MM-DD')
  }
  if (format === 'time') {
    return dayjs(time).format('HH:mm:ss')
  }
  if (format === 'from') {
    return dayjs(time).fromNow()
  }
  if (format === 'to') {
    return dayjs(time).toNow()
  }
  return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
}

export function percent(num) {
  return parseFloat((num * 100).toFixed(2))
}

export function abbr(string, length = 6, suffix = '...') {
  if (string && string.length > length) {
    return `${string.substring(0, length)}${suffix}`
  }
  return string
}

export function abbrRight(string, length = 6, suffix = '...') {
  if (string && string.length > length) {
    return `${string.substring(string.length - length)}${suffix}`
  }
  return string
}

export function abbrMessage(msg) {
  if (Array.isArray(msg)) {
    const sum = msg.map(x => abbrMessage(x)).reduce((s, c) => {
      const sh = s
      if (sh[c]) {
        sh[c] += 1
      } else {
        sh[c] = 1
      }
      return sh
    }, {})
    const output = []
    Object.keys(sum).forEach(k => {
      output.push(`${k}×${sum[k]}`)
    })
    return output.join(', ')
  }
  if (msg.typeUrl) {
    return msg.typeUrl.substring(msg.typeUrl.lastIndexOf('.') + 1).replace('Msg', '')
  }
  return msg.type.substring(msg.type.lastIndexOf('/') + 1).replace('Msg', '')
}

export function abbrAddress(address, length = 10) {
  return address.substring(0, length).concat('...', address.substring(address.length - length))
}

export function isStringArray(value) {
  let is = false
  if (Array.isArray(value)) {
    is = value.findIndex(x => typeof x === 'string') > -1
  }
  return is
}

export function isToken(value) {
  let is = false
  if (Array.isArray(value)) {
    is = value.findIndex(x => Object.keys(x).includes('denom')) > -1
  }
  return is
}

export function formatTokenDenom(tokenDenom) {
  if (tokenDenom) {
    let denom = tokenDenom.denom_trace ? tokenDenom.denom_trace.base_denom.toUpperCase() : tokenDenom.toUpperCase()
    if (denom.charAt(0) === 'U' && denom !== 'USDX') {
      denom = denom.substring(1)
    } else if (denom === 'BASECRO') {
      denom = 'CRO'
    } else if (denom.startsWith('IBC')) {
      denom = 'IBC...'
    } else if (denom.startsWith('NANOLIKE')) {
      denom = 'LIKE'
    }

    return denom
  }
  return ''
}

export function getUnitAmount(amount, denom) {
  if (denom.startsWith('basecro')) {
    return String((Number(amount) * 100000000).toFixed())
  }
  if (denom.startsWith('rowan')) {
    // eslint-disable-next-line no-undef
    return (BigInt(amount) * 1000000000000000000n).toString()
  }
  if (denom.startsWith('nanolike')) {
    // eslint-disable-next-line no-undef
    return String((Number(amount) * 1000000000).toFixed())
  }
  return String((Number(amount) * 1000000).toFixed())
}

export function formatTokenAmount(tokenAmount, fraction = 2, denom = 'uatom') {
  let amount
  if (denom.startsWith('inj')) {
    // eslint-disable-next-line no-undef
    amount = Number(BigInt(Number(tokenAmount)) / 1000000000000000000n)
    // }
  } else if (denom.startsWith('rowan')) {
    // eslint-disable-next-line no-undef
    amount = Number(BigInt(Number(tokenAmount)) / 1000000000000000000n)
    // }
  } else if (denom.startsWith('basecro')) {
    amount = Number(tokenAmount) / 100000000
  } else if (denom.startsWith('nanolike')) {
    amount = Number(tokenAmount) / 1000000000
  } else {
    amount = Number(tokenAmount) / 1000000
  }
  if (amount > 10) {
    return parseFloat(amount.toFixed(fraction))
  }
  return parseFloat(amount)
}

export function isTestnet() {
  return (window.location.hostname.startsWith('testnet')
   || window.location.search.indexOf('testnet') > -1)
}

export function formatToken(token, IBCDenom = {}, decimals = 2, withDenom = true) {
  if (token) {
    if (withDenom) {
      return `${formatTokenAmount(token.amount, decimals, token.denom)} ${formatTokenDenom(IBCDenom[token.denom] || token.denom)}`
    }
    return formatTokenAmount(token.amount, decimals, token.denom)
  }
  return token
}

const COUNT_ABBRS = ['', 'K', 'M', 'B', 't', 'q', 's', 'S', 'o', 'n', 'd', 'U', 'D', 'T', 'Qt', 'Qd', 'Sd', 'St']

export function formatNumber(count, withAbbr = false, decimals = 2) {
  const i = count === 0 ? count : Math.floor(Math.log(count) / Math.log(1000))
  let result = parseFloat((count / (1000 ** i)).toFixed(decimals))
  if (withAbbr && COUNT_ABBRS[i]) {
    result += `${COUNT_ABBRS[i]}`
  }
  return result
}

export function tokenFormatter(tokens, denoms = {}) {
  if (Array.isArray(tokens)) {
    return tokens.map(t => formatToken(t, denoms, 2)).join()
  }
  return formatToken(tokens, denoms, 2)
}

export function getCachedValidators(chainName) {
  const locals = localStorage.getItem(`validators-${chainName}`)
  return locals
}

export function isHexAddress(v) {
  const re = /^[A-Z\d]{40}$/
  return re.test(v)
}

export function getStakingValidatorByHex(chainName, hex) {
  const locals = localStorage.getItem(`validators-${chainName}`)
  if (locals) {
    const val = JSON.parse(locals).find(x => consensusPubkeyToHexAddress(x.consensus_pubkey) === hex)
    if (val) {
      return val.description.moniker
    }
  }
  return abbr(hex)
}

export function getStakingValidatorByAccount(chainName, addr) {
  const locals = localStorage.getItem(`validators-${chainName}`)
  if (locals) {
    const val = JSON.parse(locals).find(x => operatorAddressToAccount(x.operator_address) === addr)
    if (val) {
      return val.description.moniker
    }
  }
  return addr
}

export function getStakingValidatorOperator(chainName, addr, length = -1) {
  const locals = localStorage.getItem(`validators-${chainName}`)
  if (locals) {
    const val = JSON.parse(locals).find(x => x.operator_address === addr)
    if (val) {
      return val.description.moniker
    }
  }
  if (length > 0) {
    return addr.substring(addr.length - length)
  }
  return addr
}

export * from 'compare-versions'

export class Data {

}
