// 

import { consensus, hd, networks, primitives } from 'bcoin'
import { Buffer } from 'buffer'


import {
  addressFromKey,
  getPrivateFromSeed,
  setKeyType
} from '../utils/coinUtils.js'

const witScale = consensus.WITNESS_SCALE_FACTOR




























export const SUPPORTED_BIPS = ['bip32', 'bip44', 'bip49', 'bip84']

export const getAllKeyRings = (
  privateKeys,
  network
) => {
  const keysPromises = []
  const { formats, serializers = {} } = networks[network] || {}
  for (const bip of formats) {
    for (const key of privateKeys) {
      const fSelector = formatSelector(bip, network)
      const standardKey = serializers.wif ? serializers.wif.decode(key) : key
      const keyRing = primitives.KeyRing.fromSecret(standardKey, network)
      keysPromises.push(Promise.resolve(keyRing).then(fSelector.setKeyType))
    }
  }
  return Promise.all(keysPromises)
}

export const getAllAddresses = (
  privateKeys,
  network
) =>
  getAllKeyRings(privateKeys, network).then(keyRings =>
    Promise.all(keyRings.map(key => addressFromKey(key, network)))
  )

export const getXPubFromSeed = async ({
  seed,
  format = 'bip32',
  network = 'main',
  account = 0,
  coinType = 0
}) => {
  const masterKey = await getPrivateFromSeed(seed, network)
  const fSelector = formatSelector(format, network)
  const masterPath = fSelector.createMasterPath(account, coinType)
  const privateKey = await masterKey.derivePath(masterPath)
  const xpubKey = await privateKey.xpubkey()
  return xpubKey
}

export const formatSelector = (
  format = 'bip32',
  network = 'main'
) => {
  if (!SUPPORTED_BIPS.includes(format)) throw new Error('Unknown bip type')
  const bip = parseInt(format.split('bip')[1])

  const branches = { '0': 'receive' }
  if (bip !== 32) Object.assign(branches, { '1': 'change' })
  const nested = bip === 49
  const witness = bip === 49 || bip === 84
  const { scriptTemplates = {} } = networks[network] || {}
  for (const scriptName in scriptTemplates) {
    const template = scriptTemplates[scriptName]()
    const defaultScript = typeof template === 'function' ? template() : template
    const branchNum = parseInt(defaultScript.slice(-8), 16)
    branches[`${branchNum}`] = scriptName
  }

  const setKeyTypeWrap = (key, redeemScript) =>
    setKeyType(key, nested, witness, network, redeemScript)
  const deriveHdKey = (parentKey, index) =>
    Promise.resolve(parentKey.derive(index))

  return {
    branches,
    setKeyType: setKeyTypeWrap,

    sign: (tx, keys) =>
      Promise.resolve(tx.template(keys))
        .then(() => {
          tx.network = network
          return tx.sign(keys, networks[network].replayProtection)
        })
        .then(() => {
          const { serializers = {} } = networks[network] || {}
          if (serializers.txHash) {
            tx._hash = serializers.txHash(tx.toNormal().toString('hex'))
          }
          const txid = tx.rhash()
          return { txid, signedTx: tx.toRaw().toString('hex') }
        }),

    getMasterKeys: async (seed, masterPath, privKey) => {
      if (!privKey) {
        const privateKey = await getPrivateFromSeed(seed, network)
        privKey = await privateKey.derivePath(masterPath)
      }
      const pubKey = await privKey.toPublic()
      return { privKey, pubKey }
    },

    hasScript: (branch, scriptObj) => {
      if (scriptObj) {
        if (!scriptTemplates[scriptObj.type]) {
          throw new Error('Unkown script template')
        }
        return true
      }
      if (scriptTemplates[branches[`${branch}`]]) return true
      return false
    },

    parseSeed:
      bip === 32
        ? (seed) => Buffer.from(seed, 'base64').toString('hex')
        : (seed) => seed,

    createMasterPath: (account, coinType) =>
      bip === 32
        ? 'm/0'
        : `m/${bip}'/${
            coinType >= 0 ? coinType : networks[network].keyPrefix.coinType
          }'/${account}'`,

    deriveHdKey,
    deriveAddress: (parentKey, index) =>
      deriveHdKey(parentKey, index)
        .then(key => setKeyTypeWrap(key))
        .then(key => addressFromKey(key, network)),

    deriveKeyRing: (
      parentKey,
      index,
      redeemScript
    ) =>
      deriveHdKey(parentKey, index).then(derivedKey =>
        setKeyTypeWrap(derivedKey, redeemScript)
      ),

    deriveScriptAddress: async (
      parentKey,
      index,
      branch,
      scriptObj
    ) => {
      const branchName = branches[`${branch}`]
      const childKey = await deriveHdKey(parentKey, index)
      let redeemScript = null
      if (scriptObj) {
        const scriptTemplate = scriptTemplates[scriptObj.type]
        redeemScript = scriptTemplate(childKey)(scriptObj.params)
      } else if (scriptTemplates[branchName]) {
        const scriptTemplate = scriptTemplates[branchName]
        const temp = scriptTemplate(childKey)
        if (typeof temp === 'string') redeemScript = temp
      }
      if (!redeemScript) return null
      const typedKey = await setKeyTypeWrap(childKey, redeemScript)
      const address = await addressFromKey(typedKey, network)
      return { ...address, redeemScript }
    },

    keysFromRaw: (rawKeys = {}) => {
      const keyRings = {}
      const branchesNames = ['master']
      for (const n in branches) branchesNames.push(branches[n])
      for (const branchName of branchesNames) {
        const { xpub, xpriv } = rawKeys[branchName] || {}
        keyRings[branchName] = {
          pubKey: xpub ? hd.PublicKey.fromBase58(xpub, network) : null,
          privKey: xpriv ? hd.PrivateKey.fromBase58(xpriv, network) : null,
          children: []
        }
      }
      return keyRings
    },

    estimateSize: (prev) => {
      const address = prev.getAddress()
      if (!address) return -1

      let size = 0

      if (prev.isScripthash()) {
        if (bip === 49) {
          size += 23 // redeem script
          size *= 4 // vsize
          // Varint witness items length.
          size += 1
          // Calculate vsize
          size = ((size + witScale - 1) / witScale) | 0
          // witness portion
          // OP_PUSHDATA0 [signature]
          let witness = 1 + 73
          // OP_PUSHDATA0 [key]
          witness += 1 + 33
          size += witness / witScale
        }
      }

      // P2PKH
      if (bip !== 49) {
        // varint script size
        size += 1
        // OP_PUSHDATA0 [signature]
        size += 1 + 73
        // OP_PUSHDATA0 [key]
        size += 1 + 33
      }

      return size || -1
    }
  }
}
