const fs = require('fs')
const path = require('path')
const fixtures = require('../../xahau-binary-codec/test/fixtures/codec-fixtures.json')

const NORMAL_TYPES = ['number', 'string']
const NUMBERS = ['0', '1']

function getTx(txName) {
  transactions = fixtures.transactions
  const validTxs = fixtures.transactions
    .filter((tx) => tx.json.TransactionType === txName)
    .map((tx) => tx.json)
  if (validTxs.length == 0) {
    return '{ /* TODO: add sample transaction */ }'
  }
  const validTx = validTxs[0]
  delete validTx.TxnSignature
  delete validTx.SigningPubKey
  return JSON.stringify(validTx, null, 2)
}

function main(modelName) {
  const filename = path.join(
    path.dirname(__filename),
    `../src/models/transactions/${modelName}.ts`,
  )
  const [model, txName] = getModel(filename)
  return processModel(model, txName)
}

// Extract just the model from the file
function getModel(filename) {
  let model = ''
  let started = false
  let ended = false
  const data = fs.readFileSync(filename, 'utf8')
  const lines = data.split('\n')
  for (let line of lines) {
    if (ended) {
      continue
    }
    if (!started && !line.startsWith('export')) {
      continue
    }
    if (!started && line.includes('Flags')) {
      continue
    }
    if (!started) {
      started = true
    }
    model += line + '\n'
    if (line === '}') {
      ended = true
    }
  }
  const name_line = model.split('\n')[0].split(' ')
  const txName = name_line[2]
  return [model, txName]
}

function getInvalidValue(paramTypes) {
  if (paramTypes.length === 1) {
    const paramType = paramTypes[0]
    if (paramType == 'number') {
      return "'number'"
    } else if (paramType == 'string') {
      return 123
    } else if (paramType == 'IssuedCurrency') {
      return JSON.stringify({ test: 'test' })
    } else if (paramType == 'Currency') {
      return JSON.stringify({ test: 'test' })
    } else if (paramType == 'Amount') {
      return JSON.stringify({ currency: 'ETH' })
    } else if (paramType == 'XChainBridge') {
      return JSON.stringify({ XChainDoor: 'test' })
    } else {
      return '/*TODO*/'
    }
  }

  const simplifiedParamTypes = paramTypes.filter(
    (_paramType, index) => index % 2 == 0,
  )
  if (JSON.stringify(simplifiedParamTypes) === '["0","1"]') {
    return 2
  } else if (JSON.stringify(simplifiedParamTypes) === '["number","string"]') {
    return JSON.stringify({ currency: 'ETH' })
  } else {
    return '/*TODO*/'
  }
}

// Process the model and build the tests

function processModel(model, txName) {
  let output = ''
  for (let line of model.split('\n')) {
    if (line == '') {
      continue
    }
    if (line.startsWith('export')) {
      continue
    }
    if (line == '}') {
      continue
    }
    line = line.trim()
    if (line.startsWith('TransactionType')) {
      continue
    }
    if (line.startsWith('Flags')) {
      // TODO: support flag checking
      continue
    }
    if (line.startsWith('/**')) {
      continue
    }
    if (line.startsWith('*')) {
      continue
    }

    const split = line.split(' ')
    const param = split[0].replace('?:', '').replace(':', '').trim()
    const paramTypes = split.slice(1)
    const optional = split[0].endsWith('?:')

    if (!optional) {
      output += `  it("throws w/ missing ${param}", function () {
    delete tx.${param}

    assert.throws(
      () => validate${txName}(tx),
      ValidationError,
      '${txName}: missing field ${param}',
    )
    assert.throws(
      () => validate(tx),
      ValidationError,
      '${txName}: missing field ${param}',
    )
  })

`
    }

    const fakeValue = getInvalidValue(paramTypes)
    output += `  it('throws w/ invalid ${param}', function () {
    tx.${param} = ${fakeValue}

    assert.throws(
      () => validate${txName}(tx),
      ValidationError,
      '${txName}: invalid field ${param}',
    )
    assert.throws(
      () => validate(tx),
      ValidationError,
      '${txName}: invalid field ${param}',
    )
  })

`
  }

  output = output.substring(0, output.length - 2)
  output += '\n})\n'
  output =
    `import { assert } from 'chai'

import { validate, ValidationError } from '../../src'
import { validate${txName} } from '../../src/models/transactions/${txName}'

/**
 * ${txName} Transaction Verification Testing.
 *
 * Providing runtime verification testing for each specific transaction type.
 */
describe('${txName}', function () {
  let tx

  beforeEach(function () {
    tx = ${getTx(txName)} as any
  })

  it('verifies valid ${txName}', function () {
    assert.doesNotThrow(() => validate${txName}(tx))
    assert.doesNotThrow(() => validate(tx))
  })

` + output

  return output
}

if (require.main === module) {
  if (process.argv.length < 3) {
    console.log(`Usage: ${process.argv[0]} ${process.argv[1]} TxName`)
    process.exit(1)
  }
  console.log(main(process.argv[2]))
}

module.exports = main
