const sdk = require("../dist")
const Neon = require("@cityofzion/neon-core")
const fs = require("fs")
var assert = require('assert');


describe("Test basic TMN functionality", function() {
    this.timeout(60000);

    const TIME_CONSTANT = 4000

    let tmn, network, NODE

    beforeEach( async function() {
        NODE = 'http://localhost:50012'

        tmn = await new sdk.TMN({node: NODE})
        await tmn.init()


        //load any wallets and network settings we may want later (helpful if we're local)
        network = JSON.parse(fs.readFileSync("../default.neo-express").toString());
        network.wallets.forEach( (walletObj) => {
            walletObj.wallet = new Neon.wallet.Account(walletObj.accounts[0]['private-key'])
        })
    })


    it("should get the token symbol", async() => {
        const symbol = await tmn.symbol()
        assert.equal(symbol, 'TMN')
    })

    it("should get the decimals", async() => {
        const decimals = await tmn.decimals()
        assert.equal(decimals, 8)
    })

    it("should get the total supply", async() => {
        const totalSupply = await tmn.totalSupply()
        assert(totalSupply >= 0)
    })

    it("should get the balance of an account that doesn't exist", async() => {
        const newAccount = new Neon.wallet.Account()
        const balance = await tmn.balanceOf(newAccount.address)
        assert.equal(balance, 0)
    })

    it("should mint tokens to the coz account and verify the supply and balance", async() => {
        const cozWallet = network.wallets[0].wallet

        const mintAmount = 10000

        const mintPayload = {
            "address": cozWallet.address,
            "amount": mintAmount
        }
        const initialBalance = await tmn.balanceOf(cozWallet.address)
        const initialTS = await tmn.totalSupply()

        const txid = await tmn.mint(mintPayload, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        const res = await sdk.helpers.txDidComplete(NODE, txid)
        assert(res[0], 1)

        //verify the event presented
        const events = await sdk.helpers.getEvents(NODE, txid)
        assert.equal(events.length, 1)
        assert.equal(events[0].eventName, 'Transfer')
        assert.equal(events[0].value[2], mintAmount)

        //check the total supply and balance
        const totalSupply = await tmn.totalSupply()
        assert.equal(totalSupply, initialTS + mintAmount)

        const balance = await tmn.balanceOf(cozWallet.address)
        assert.equal(balance, initialBalance + mintAmount)
    })

    it("should throw and exception when minting above the max supply", async() => {
        const cozWallet = network.wallets[0].wallet

        const initialBalance = await tmn.balanceOf(cozWallet.address)
        const initialTS = await tmn.totalSupply()

        const mintAmount = 2000000001

        const mintPayload = {
            "address": cozWallet.address,
            "amount": mintAmount
        }

        try {
            const txid = await tmn.mint(mintPayload, cozWallet)

            await sdk.helpers.sleep(TIME_CONSTANT)

            await sdk.helpers.txDidComplete(NODE, txid, true)
        } catch(e) {
        }
        //check the total supply and balance
        const totalSupply = await tmn.totalSupply()
        assert.equal(totalSupply, initialTS)

        const balance = await tmn.balanceOf(cozWallet.address)
        assert.equal(balance, initialBalance)
    })

    it("should mint tokens to another user", async() => {
        const cozWallet = network.wallets[0].wallet
        const newUser = new Neon.wallet.Account()

        const initialTS = await tmn.totalSupply()

        const mintAmount = 10000

        const mintPayload = {
            "address": newUser.address,
            "amount": mintAmount
        }

        let txid = await tmn.mint(mintPayload, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        let res = await sdk.helpers.txDidComplete(NODE, txid, true)
        assert(res[0], 1)

        //check the total supply and balance
        const totalSupply = await tmn.totalSupply()
        assert.equal(totalSupply, initialTS + mintAmount)

        const balance = await tmn.balanceOf(newUser.address)
        assert.equal(balance, mintAmount)
    })

    it("transfer tokens to a user", async() => {
        const cozWallet = network.wallets[0].wallet
        const newUser = new Neon.wallet.Account()


        const amount = 10000

        const mintPayload = {
            "address": cozWallet.address,
            "amount": amount
        }
        console.log("minting")
        let txid = await tmn.mint(mintPayload, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        const res = await sdk.helpers.txDidComplete(NODE, txid, true)
        assert(res[0], 1)

        //get the total supply
        const initialTS = await tmn.totalSupply()
        const initialUserBalance = await tmn.balanceOf(newUser.address)
        const initialCOZBalance = await tmn.balanceOf(cozWallet.address)


        //transfer tokens
        console.log("transferring")
        txid = await tmn.transfer(cozWallet, newUser.address, amount)
        await sdk.helpers.sleep(TIME_CONSTANT)
        await sdk.helpers.txDidComplete(NODE, txid)
        assert(res[0], 1)

        //verify the event presented
        const events = await sdk.helpers.getEvents(NODE, txid)
        assert.equal(events.length, 1)
        assert.equal(events[0].eventName, 'Transfer')
        assert.equal(events[0].value[2], amount)


        //verify accounting
        const totalSupply = await tmn.totalSupply()
        assert.equal(totalSupply, initialTS)

        const balance = await tmn.balanceOf(newUser.address)
        assert.equal(balance, amount)

        const cozBalance = await tmn.balanceOf(cozWallet.address)
        assert.equal(cozBalance, initialCOZBalance - amount)
    })

    it("should 'transfer' securely to the user", async() => {
        const cozWallet = network.wallets[0].wallet


        const amount = 10000

        const mintPayload = {
            "address": cozWallet.address,
            "amount": amount
        }
        console.log("minting")
        let txid = await tmn.mint(mintPayload, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        const res = await sdk.helpers.txDidComplete(NODE, txid, true)
        assert(res[0], 1)

        //get the total supply
        const initialTS = await tmn.totalSupply()
        const initialCOZBalance = await tmn.balanceOf(cozWallet.address)


        //transfer tokens
        console.log("transferring")
        txid = await tmn.transfer(cozWallet, cozWallet.address, amount)
        await sdk.helpers.sleep(TIME_CONSTANT)
        await sdk.helpers.txDidComplete(NODE, txid, true)
        assert(res[0], 1)


        //verify accounting
        const totalSupply = await tmn.totalSupply()
        assert.equal(totalSupply, initialTS)

        const cozBalance = await tmn.balanceOf(cozWallet.address)
        assert.equal(cozBalance, initialCOZBalance)
    })

    it("should attempt to transfer more than the user's holdings", async() => {
        const cozWallet = network.wallets[0].wallet
        const newUser = new Neon.wallet.Account()

        const amount = 10000

        const mintPayload = {
            "address": newUser.address,
            "amount": amount
        }
        console.log("minting")
        let txid = await tmn.mint(mintPayload, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        let res = await sdk.helpers.txDidComplete(NODE, txid, true)
        assert(res[0], 1)

        //get the total supply
        const initialTS = await tmn.totalSupply()
        const initialCOZBalance = await tmn.balanceOf(cozWallet.address)
        const initialBalance = await tmn.balanceOf(newUser.address)


        //transfer tokens
        console.log("transferring")
        txid = await tmn.transfer(cozWallet, newUser.address, initialCOZBalance + 1)
        await sdk.helpers.sleep(TIME_CONSTANT)
        res = await sdk.helpers.txDidComplete(NODE, txid, true)
        assert.equal(res[0], 0)

        //verify accounting
        const totalSupply = await tmn.totalSupply()
        assert.equal(totalSupply, initialTS)

        const cozBalance = await tmn.balanceOf(cozWallet.address)
        assert.equal(cozBalance, initialCOZBalance)

        const newBalance = await tmn.balanceOf(newUser.address)
        assert.equal(newBalance, initialBalance)
    })

    it("should attempt to mint from an account without permissions", async() => {
        const cozWallet = network.wallets[0].wallet
        const newUser = new Neon.wallet.Account()

        const mintAmount = 10000

        const mintPayload = {
            "address": newUser.address,
            "amount": mintAmount
        }

        const initialTS = await tmn.totalSupply()
        const initialBalance = await tmn.balanceOf(newUser.address)

        try {
            const txid = await tmn.mint(mintPayload, newUser)

            await sdk.helpers.sleep(TIME_CONSTANT)

            const res = await sdk.helpers.txDidComplete(NODE, txid, true)
            assert(res[0], 0)
        } catch (e) {}

        //check the total supply and balance
        const totalSupply = await tmn.totalSupply()
        assert.equal(totalSupply, initialTS)

        const balance = await tmn.balanceOf(newUser.address)
        assert.equal(balance, initialBalance)
    })

    it("should batch mint tokens to users", async() => {
        const cozWallet = network.wallets[0].wallet

        const users = []
        const userCount = 100
        const mintAmount = 10000

        const initialTS = await tmn.totalSupply()

        const mintPayload = []
        for (let i = 0; i < userCount; i++) {
            let user = new Neon.wallet.Account()
            users.push(user)
            mintPayload.push({
                "address": user.address,
                "amount": mintAmount
            })
        }

        console.log("minting")
        let res = await tmn.mintBatch(mintPayload, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        res = await sdk.helpers.txDidComplete(NODE, res, true)
        assert.equal(res[0], 1)

        for (let user of users) {
            let balance = await tmn.balanceOf(user.address)
            assert.equal(balance, mintAmount)
        }

        const totalSupply = await tmn.totalSupply()
        assert.equal(totalSupply, initialTS + (userCount * mintAmount))
    })

    //get a user
    it("should get the root account and verify its permissions", async() => {
        const cozWallet = network.wallets[0].wallet
        const user = await tmn.getUserJSON(cozWallet.address)
        assert.equal(user.permissions.set_permissions, 1)
        assert.equal(user.permissions.mint, 1)
        assert.equal(user.permissions.update, 1)
    })

    it("should toggle permissions and mint", async() => {
        const cozWallet = network.wallets[0].wallet
        const newUser = new Neon.wallet.Account()
        const innocentBystander = new Neon.wallet.Account()

        const mintAmount = 10000

        const mintPayload = {
            "address": innocentBystander.address,
            "amount": mintAmount
        }

        //transfer some gas to the new test account
        await tmn.arbitraryTransfer(
            '0xd2a4cff31913016155e38e474a2c06d08be276cf',
            cozWallet,
            newUser.address,
            10000 * 10**8)
        await sdk.helpers.sleep(TIME_CONSTANT)

        const initialBalance = await tmn.balanceOf(innocentBystander.address)

        // set a new user's permissions and verify
        let txid = await tmn.setUserPermission(newUser.address, 'mint', true, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        await sdk.helpers.txDidComplete(NODE, txid, true)
        let user = await tmn.getUserJSON(newUser.address)
        assert.equal(user.permissions.set_permissions, 0)
        assert.equal(user.permissions.mint, 1)
        assert.equal(user.permissions.update, 0)

        // use the new user to mint tokens to the bystander
        txid = await tmn.mint(mintPayload, newUser)
        await sdk.helpers.sleep(TIME_CONSTANT)
        await sdk.helpers.txDidComplete(NODE, txid, true)
        const newBalance = await tmn.balanceOf(innocentBystander.address)
        assert.equal(newBalance, initialBalance + mintAmount)

        // revoke the user's ability to mint and verify permissions
        txid = await tmn.setUserPermission(newUser.address, 'mint', false, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        await sdk.helpers.txDidComplete(NODE, txid, true)
        user = await tmn.getUserJSON(newUser.address)
        assert.equal(user.permissions.set_permissions, 0)
        assert.equal(user.permissions.mint, 0)
        assert.equal(user.permissions.update, 0)

        // use the new user to mint tokens to the bystander
        try {
            txid = await tmn.mint(mintPayload, newUser)
            await sdk.helpers.sleep(TIME_CONSTANT)
            await sdk.helpers.txDidComplete(NODE, txid, true)
        } catch(e) {
            const newnewBalance = await tmn.balanceOf(innocentBystander.address)
            assert.equal(newBalance, newnewBalance)
            return
        }
        assert.fail()


    })

    it("should prevent an unauthorized user from toggling permissions", async() => {
        const cozWallet = network.wallets[0].wallet
        const newUser = new Neon.wallet.Account()

        await tmn.arbitraryTransfer(
            '0xd2a4cff31913016155e38e474a2c06d08be276cf',
            cozWallet,
            newUser.address,
            10000 * 10**8)
        await sdk.helpers.sleep(TIME_CONSTANT)

        try {
            const txid = await tmn.setUserPermission(newUser.address, 'mint', false, newUser)
            await sdk.helpers.sleep(TIME_CONSTANT)
            await sdk.helpers.txDidComplete(NODE, txid, true)
        } catch(e) {
            return
        }
        assert.fail()
    })

    it("should update the contract and return the revised symbol", async() => {
        const cozWallet = network.wallets[0].wallet

        const pathToNEF = '../contract/tmn/tmn.nef'
        const nef = Neon.sc.NEF.fromBuffer(
            fs.readFileSync(
                pathToNEF
            )
        )

        const serializedNEF = Neon.u.HexString.fromHex(nef.serialize(), true)
        const rawManifest = fs.readFileSync(pathToNEF.replace('.nef', '.manifest.json'))

        const manifest = Neon.sc.ContractManifest.fromJson(
            JSON.parse(rawManifest.toString())
        )
        const stringifiedManifest = JSON.stringify(manifest.toJson())

        const txid = await tmn.update(serializedNEF.toBase64(true), stringifiedManifest, cozWallet)
        await sdk.helpers.sleep(TIME_CONSTANT)
        const res = await sdk.helpers.txDidComplete(NODE, txid, true)
        console.log(res)

    })

})