import fs from "fs";
import path from "path";
import { exec } from "child_process";
import { task } from "hardhat/config";
import minimist from "minimist";
import { Blob, NFTStorage } from "nft.storage";

import { getLongVersion } from "./solc/version";

const NFT_STORAGE_API_KEY = process.env.NFT_STORAGE_API_KEY;
const argv = minimist(process.argv.slice(2));

// Uploads a compiled contract to IPFS and returns its hash
task("upload", "Uploads a compiled contract to IPFS and returns deploy link")
    .addPositionalParam("contract", "Contract to deploy")
    .addOptionalParam("args", "Deploy arguments")
    .addOptionalParam("ascii", "ASCII art file path (.txt)")
    .setAction(async (taskArgs, hre) => {
        try {
            if (!NFT_STORAGE_API_KEY) {
                console.error("Please put NFT_STORAGE_API_KEY in .env");
                process.exit(-1);
            }

            const client = new NFTStorage({ token: NFT_STORAGE_API_KEY });

            await hre.run("compile");

            // console.log('process.argv', process.argv)
            const { contract, args, ascii } = taskArgs;

            console.log("Using contract", contract);

            if (!contract) {
                console.log(
                    `Usage: npx hardhat upload [contract name] --args '"arg1","arg2"'`
                );
                return;
            }

            if (!fs.existsSync(contract)) {
                console.error(`Contract ${contract} not found`);
                return;
            }

            // const factory = await hre.ethers.getContractFactory(contractName);

            // extract filename from contract
            const filename = contract.split("/").pop().replace(".sol", "");

            if (!filename) {
                console.log(`File has no name`);
                return;
            }

            // read dir ./artifacts/${contract} and open the {filename}.json file
            const contractArtifact = JSON.parse(
                fs
                    .readFileSync(`./artifacts/${contract}/${filename}.json`)
                    .toString()
            );

            let solcInput, solcLongVersion;

            try {
                const artifactDbg = `./artifacts/${contract}/${filename}.dbg.json`
                const { buildInfo } = JSON.parse(fs.readFileSync(artifactDbg).toString());

                const buildInfoPath = path.join(`./artifacts/${contract}`, buildInfo);

                const info = fs.readFileSync(buildInfoPath).toString();

                const { solcLongVersion: version, input } = JSON.parse(info);

                solcInput = input;
                solcLongVersion = version;

                console.log(`Build info: ${buildInfoPath}`);
            } catch (err: any) {
                console.log(`Build info not found:\n`, err.message);
                return;
            }

            const { abi, bytecode, ...artifact } = contractArtifact;

            // if process.argv elements contain "help"
            if (argv._.find((elem) => elem.includes("help"))) {
                console.log(
                    `Usage: npx hardhat upload [contract name] --args '"arg1","arg2"'`
                );
                return;
            }

            let flattened;

            try {
                // try flattening contract
                const sourcePath = contract;

                // create dir ./tmp
                if (!fs.existsSync("./tmp")) {
                    fs.mkdirSync("./tmp");
                }

                // await run("flatten", [ sourcePath, "./tmp/Flattened.sol" ]);

                // const sh = `npx truffle-flattener ${sourcePath} | awk '/SPDX-License-Identifier/&&c++>0 {next} 1' | awk '/pragma experimental ABIEncoderV2;/&&c++>0 {next} 1' > ./tmp/Flattened.sol`;
                const sh = `npx hardhat flatten "${sourcePath}" > ./tmp/Flattened.sol`;

                // run the flattener
                console.log("\nRunning command:", sh);

                await new Promise((resolve, reject) => {
                    exec(sh, (err, stdout, stderr) => {
                        if (err || stderr) {
                            console.log("Error flattening contract:", err);
                            return reject(err);
                        }

                        // pipe stdout and stderr to console
                        // tslint:disable-next-line
                        stdout &&
                            console.log(
                                `\nOutput: ${stdout.split("\n").join("\n\t")}`
                            );
                        // tslint:disable-next-line
                        stderr &&
                            console.log(
                                `\nErrors: ${stderr.split("\n").join("\n\t")}`
                            );

                        resolve(true);
                    });
                });

                flattened = fs.readFileSync("./tmp/Flattened.sol", "utf8");

                if (!flattened) {
                    throw new Error("No flattened contract");
                }

                // from https://github.com/boringcrypto/dictator-dao/blob/a3de9f606d05852eb5cfa811a3f38870ab22800a/hardhat.config.js#L60

                // Remove every line started with "// SPDX-License-Identifier:"
                flattened = flattened.replace(
                    /SPDX-License-Identifier:/gm,
                    "License-Identifier:"
                );

                flattened = `// SPDX-License-Identifier: MIXED\n\n${flattened}`;

                // Remove every line started with "pragma experimental ABIEncoderV2;" except the first one
                flattened = flattened.replace(
                    /pragma experimental ABIEncoderV2;\n/gm,
                    ((i) => (m: string) => (!i++ ? m : ""))(0)
                );

                flattened = flattened.trim();

                if (ascii) {
                    // read file from ascii and paste in front of flattened
                    const art = fs.readFileSync(ascii, "utf8");

                    flattened = `${art}\n\n${flattened}`;
                }

                // write it back
                fs.writeFileSync("./tmp/Flattened.sol", flattened);
            } catch (err) {
                // process exit with error message
                console.error(`\nError:`, err);
                return;
            } finally {
                // rm flattened file
                // tslint:disable-next-line
                !process.env.KEEP && fs.unlinkSync("./tmp/Flattened.sol");
                // fs.rmdirSync("./tmp");
            }

            console.log(`\nDeploying ${contract}`);

            // const longVersion = await getLongVersion(
            //     hre.config.solidity.compilers[0].version
            // );

            // const longVersionString = longVersion.replace(/^v/, "");

            const contractInfo = {
                name: contractArtifact.contractName,
                filename,
                abi,
                bytecode,
                artifact,
                extra: {
                    metadata: JSON.stringify({
                        ...hre.config.solidity.compilers[0],
                        compiler: {
                            ...hre.config.solidity.compilers[0],
                            version: solcLongVersion,
                        },
                    }),
                },
                input: solcInput,
                flattened,
            };

            const blob = new Blob([JSON.stringify(contractInfo)], {
                type: "application/json",
            });
            const cid = await client.storeBlob(blob);

            console.log(
                `Metadata uploaded to https://cloudflare-ipfs.com/ipfs/${cid}\n`
            );

            console.log(`Deploy here:`);

            const argsString = args
                ? `?args=%5B${encodeURIComponent(args)}%5D`
                : "?args=%5B%5D";

            console.log(
                `https://gate-goerli.buildship.xyz/deploy/${cid}${argsString}`
            );
        } catch (err) {
            console.error(err);
        }
    });
