import * as fs from 'fs';
import { exec } from "child_process";
import { getEmitterAddressEth, getEmitterAddressSolana, parseSequenceFromLogEth } from '@certusone/wormhole-sdk';
import * as ethers from 'ethers';
import fetch from 'node-fetch';
import {getEmitterAddress as getEmitterAddressAptos} from './aptos';

const config = JSON.parse(fs.readFileSync('./xdapp.config.json').toString());

export async function deploy(chain: string){
    const rpc = config.networks[chain]['rpc'];
    const privateKey = config.networks[chain]['privateKey'];
    const bridgeAddress = config.networks[chain]['bridgeAddress'];

    exec(
        `cd chains/evm && forge build && forge create --legacy --rpc-url ${rpc} --private-key ${privateKey} src/Messenger.sol:Messenger --constructor-args ${bridgeAddress} && exit`,
        (err, out, errStr) => {
            if (err) {
                throw new Error(err.message);
            }

            if (out) {
                console.log(out);
                const deploymentAddress = out
                    .split("Deployed to: ")[1]
                    .split("\n")[0]
                    .trim();
                const emittedVAAs = []; //Resets the emittedVAAs
                fs.writeFileSync(
                    `./deployinfo/${chain}.deploy.json`,
                    JSON.stringify({
                        address: deploymentAddress,
                        vaas: emittedVAAs
                    }, null, 4)
                );
            }
        }
    );
}

export async function registerApp(src:string, target:string){
    const srcNetwork = config.networks[src];
    const targetNetwork = config.networks[target];
    let srcDeploymentInfo;
    let targetDeploymentInfo;
    let targetEmitter;


    try{
        srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    } catch (e){
        throw new Error(`${src} is not deployed yet`);
    }

    try{
        targetDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
    } catch (e){
        throw new Error(`${target} is not deployed yet`);
    }

    switch (targetNetwork['type']){
        case 'evm': 
            targetEmitter = getEmitterAddressEth(targetDeploymentInfo['address']);
            break;
        case 'solana':
            targetEmitter = await getEmitterAddressSolana(targetDeploymentInfo['address']);
            break;
        case 'aptos':
            targetEmitter = await getEmitterAddressAptos(targetNetwork.rpc, targetDeploymentInfo['address']);
            break;
    }

    const emitterBuffer = Buffer.from(targetEmitter, 'hex');
    const signer = new ethers.Wallet(srcNetwork.privateKey).connect(
        new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
    );
    const messenger = new ethers.Contract(
        srcDeploymentInfo.address,
        JSON.parse(
            fs
                .readFileSync(
                    "./chains/evm/out/Messenger.sol/Messenger.json"
                )
                .toString()
        ).abi,
        signer
    );
    const tx = await messenger.registerApplicationContracts(
        targetNetwork.wormholeChainId,
        emitterBuffer
    );
    return tx;
}

export async function sendMsg(src:string, msg:string){
    const srcNetwork = config.networks[src];
    let srcDeploymentInfo;
    try{
        srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    } catch (e){
        throw new Error(`${src} is not deployed yet`);
    }
    const signer = new ethers.Wallet(srcNetwork.privateKey).connect(
        new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
    );
    const messenger = new ethers.Contract(
        srcDeploymentInfo.address,
        JSON.parse(
            fs
                .readFileSync(
                    "./chains/evm/out/Messenger.sol/Messenger.json"
                )
                .toString()
        ).abi,
        signer
    );
    
    const tx = await (await messenger.sendMsg(Buffer.from(msg), {gasLimit: 150000})).wait();
    const seq = parseSequenceFromLogEth(tx, srcNetwork['bridgeAddress']);
    const emitterAddr = getEmitterAddressEth(srcDeploymentInfo['address']);
    

    await new Promise((r) => setTimeout(r, 5000)); //wait for Guardian to pick up message
    console.log(
        "Searching for: ",
        `${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
    );
    const vaaBytes = await (
        await fetch(
            `${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
        )
    ).json();

    if(!vaaBytes['vaaBytes']){
        throw new Error("VAA not found!");
    }

    if(!srcDeploymentInfo['vaas']){
        srcDeploymentInfo['vaas'] = [vaaBytes['vaaBytes']]
    } else {
        srcDeploymentInfo['vaas'].push(vaaBytes['vaaBytes'])
    }
    fs.writeFileSync(
        `./deployinfo/${src}.deploy.json`,
        JSON.stringify(srcDeploymentInfo, null, 4)
    );
    return vaaBytes['vaaBytes'];
}

export async function submitVaa(src:string, target:string, idx:string){
    const srcNetwork = config.networks[src];
    let srcDeploymentInfo;
    let targetDeploymentInfo;

    try{
        srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    } catch (e){
        throw new Error(`${src} is not deployed yet`);
    }

    try{
        targetDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
    } catch (e){
        throw new Error(`${target} is not deployed yet`);
    }

    const vaa = isNaN(parseInt(idx))
    ? targetDeploymentInfo.vaas.pop()
    : targetDeploymentInfo.vaas[parseInt(idx)];

    const signer = new ethers.Wallet(srcNetwork.privateKey).connect(
        new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
    );
    const messenger = new ethers.Contract(
        srcDeploymentInfo.address,
        JSON.parse(
            fs
                .readFileSync(
                    "./chains/evm/out/Messenger.sol/Messenger.json"
                )
                .toString()
        ).abi,
        signer
    );
    const tx = await messenger.receiveEncodedMsg(Buffer.from(vaa, "base64"));

    return tx;
}

export async function getCurrentMsg(src:string){
    const srcNetwork = config.networks[src];
    let srcDeploymentInfo;

    try{
        srcDeploymentInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    } catch (e){
        throw new Error(`${src} is not deployed yet`);
    }
    const signer = new ethers.Wallet(srcNetwork.privateKey).connect(
        new ethers.providers.JsonRpcProvider(srcNetwork.rpc)
    );

    const messenger = new ethers.Contract(
        srcDeploymentInfo.address,
        JSON.parse(
            fs
                .readFileSync(
                    "./chains/evm/out/Messenger.sol/Messenger.json"
                )
                .toString()
        ).abi,
        signer
    );
    
    return await messenger.getCurrentMsg();
}  