"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = require("@oclif/core");
const axios_1 = (0, tslib_1.__importDefault)(require("axios"));
const exec_sh_1 = (0, tslib_1.__importDefault)(require("exec-sh"));
const inquirer_1 = (0, tslib_1.__importDefault)(require("inquirer"));
const uniswapv3_1 = require("../utils/uniswapv3");
const ethers_1 = require("ethers");
const slippage_1 = require("../utils/slippage");
const Strategy_1 = require("./../utils/Strategy");
const dayjs = (0, tslib_1.__importStar)(require("dayjs"));
const utils_1 = require("ethers/lib/utils");
const chains_1 = require("../utils/chains");
const kill = require('kill-port');
class SimulateSlippage extends core_1.Command {
    constructor() {
        super(...arguments);
        // LOCALS
        this.UniV3Adapter = new uniswapv3_1.UniswapV3(chains_1.Chain.Mainnet);
        this.pool = undefined;
        // private file: string | undefined = undefined;
        this.strategy = undefined;
        this.strategyParams = undefined;
        // private dataset: string | undefined = undefined;
        this.candleSize = undefined;
        this.candles = [];
        // private lookback: string | undefined = undefined;
        this.recentPoolSwaps = [];
        this.token0StartAmount = 0;
        this.token1StartAmount = 0;
    }
    async run() {
        const { args, flags } = await this.parse(SimulateSlippage);
        // const name = flags.name ?? 'world'
        this.log('\n   ░██████╗████████╗███████╗███████╗██████╗░\n   ██╔════╝╚══██╔══╝██╔════╝██╔════╝██╔══██╗\n   ╚█████╗░░░░██║░░░█████╗░░█████╗░░██████╔╝\n   ░╚═══██╗░░░██║░░░██╔══╝░░██╔══╝░░██╔══██╗\n   ██████╔╝░░░██║░░░███████╗███████╗██║░░██║\n   ╚═════╝░░░░╚═╝░░░╚══════╝╚══════╝╚═╝░░╚═╝\n');
        // this.log('Please use --help to see the available commands.')
        // this.log('To get started creating your own strategy visit our documentation: https://docs.steer.finance/Strategies/writing-a-strategy')
        // if (args.file && flags.force) {
        //   this.log(`you input --force and --file: ${args.file}`)
        // }
        this.pool = flags.pool;
        // const file = args.file
        if (!this.pool) {
            const tokenAnswers = await this.getPoolId(this.UniV3Adapter);
            // We need to parse the pool address so we can look up it's information
            // Then we parse the tokens associated with the pool
            const { data: poolTokens } = await this.UniV3Adapter.findTokensByAddress(this.parsePoolId(tokenAnswers.poolId));
            // We then parse out the first token
            // this provides the data we need to query linked pools
            const tokenData = poolTokens === null || poolTokens === void 0 ? void 0 : poolTokens.tokens[0];
            // We now pull pools with this above token on both side of the market
            // (aka token0 and token1)
            const poolQuestions = [
                {
                    type: 'autocomplete',
                    name: 'poolId',
                    message: 'Please pick a pool to simulate or filter further with providing a token symbol',
                    source: async (answersSoFar, input) => {
                        var _a, _b;
                        const { data: { tokens: tokensBySymbol }, } = await this.UniV3Adapter.findTokensBySymbol(input || 'Steer', true);
                        const tokens = tokensBySymbol === null || tokensBySymbol === void 0 ? void 0 : tokensBySymbol.map((token) => {
                            return token.id;
                        });
                        const poolsByTokenAddress = tokens.length > 0 ?
                            (_b = (_a = (await this.UniV3Adapter.findPoolsByToken0(tokenData.id ||
                                '0x0000000000000000000000000000000000000000', tokens))) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.pools :
                            await this.UniV3Adapter.findPoolsByToken(tokenData.id || '0x0000000000000000000000000000000000000000');
                        return (poolsByTokenAddress || []).map(pool => `${pool.token0.name}/${pool.token1.name} - ${Number(pool.feeTier) / 10000}% - ${pool.id}`);
                    },
                },
            ];
            // The final pool chosen by the user
            const poolAnswers = await inquirer_1.default.prompt(poolQuestions);
            // parsed the pool id
            this.pool = this.parsePoolId(poolAnswers.poolId);
        }
        this.log('⏳ Prepping environment for testing...');
        // To prepare the environment for testing, we need to
        // make sure nothing is running on the default hardhat port
        await kill(8545);
        // If there is nothing on the hardhat port, we will start up a hardhat node headless
        // and fork at the given block numbers based on the datasets first block number
        // eslint-disable-next-line no-new
        new Promise((resolve, reject) => {
            (0, exec_sh_1.default)(`npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/Q115S9wBEp4Z2CZPmW8RTfwXCIjevA19`, {
                cwd: this.config.dataDir,
                stdio: process.env.DEBUG,
                env: {
                    DEBUG: '*'
                }
            }, function (err, _stdout, _stderr) {
                if (err) {
                    // console.log('Error while attempting to fork mainnet:', err)
                    reject();
                }
                resolve(null);
            });
        });
        // Hardhat takes time to start, so we must wait
        // eslint-disable-next-line no-async-promise-executor
        await new Promise(async (resolve) => {
            let isOnline = false;
            while (!isOnline) {
                try {
                    await axios_1.default.get('http://localhost:8545');
                    this.log('Local node ready. Starting simulation...');
                    isOnline = true;
                    resolve(true);
                }
                catch {
                    // console.log('Error while attempting to connect to node:', e);
                }
                await new Promise(resolve => {
                    setTimeout(resolve, 5000);
                });
            }
        });
        try {
            const slippageTester = new slippage_1.SlippageTest();
            await slippageTester.run(this.pool);
        }
        catch (error) {
        }
    }
    // HELPERS
    getDurationInSeconds(epochLength) {
        if (!epochLength) {
            return 0;
        }
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        // prettier-ignore
        return dayjs.default.duration(...(epochLength.match(/(\d+)([dhmw])/).slice(1, 3))).asSeconds();
    }
    async setDatasetCandles(swapData) {
        if (this.candleSize) {
            this.candles = this.UniV3Adapter.getCandles(swapData, this.candleSize);
            // this.stats.candles = this.candles
            return this.candles;
        }
        return this.candles;
    }
    async loadStrategy(file) {
        this.strategy = new Strategy_1.Strategy(file);
        // We need to initialize the wasm module by loading it from the disk
        await this.strategy.load();
        return this.strategy;
    }
    async getPoolId(Uni3Adapter) {
        const tokenQuestions = [
            {
                type: 'autocomplete',
                name: 'poolId',
                message: 'Provide a token name or symbol which you would like to backtest against',
                source: async (answersSoFar, input) => {
                    const tokensBySymbol = await Uni3Adapter.findTokensBySymbol(input || 'Steer');
                    const tokensByName = await Uni3Adapter.findTokens(input || 'Steer');
                    return [
                        ...tokensBySymbol.data.tokens,
                        ...tokensByName.data.tokens,
                    ].map((token) => `${token.name} - ${token.symbol} - ${token.id}`);
                },
            },
        ];
        const tokenAnswers = await inquirer_1.default.prompt(tokenQuestions);
        return tokenAnswers;
    }
    parsePoolId(poolId) {
        const id = poolId.split(' - ')[2];
        return id;
    }
    sortDataset(data) {
        // Now that we have data, we need to sort it by timestamp
        return data.sort(function (a, b) {
            return Number(a.timestamp) - Number(b.timestamp);
        });
    }
    getPriceAction() {
        const t0Decimals = Number(this.recentPoolSwaps[0].token0.decimals);
        const t1Decimals = Number(this.recentPoolSwaps[0].token1.decimals);
        // Convert all the swaps to to a timestamp and price for token0 to token1
        const Q192 = ethers_1.BigNumber.from(2).pow(192);
        const Precision = (0, utils_1.parseEther)('1'); // 1e18
        const swapData = this.recentPoolSwaps.map((d) => {
            return {
                index: dayjs.unix(d.timestamp).toDate().getTime(),
                value: ((Number(((ethers_1.BigNumber.from(d.sqrtPriceX96).pow(2)).mul(Precision)).div(Q192)
                    .toString()) / 1e18) * (10 ** (t1Decimals - t0Decimals))).toString()
            };
        });
        return swapData;
    }
}
exports.default = SimulateSlippage;
SimulateSlippage.description = 'Estimates slippage impacts before and after strategy';
SimulateSlippage.examples = [
    '<%= config.bin %> <%= command.id %>',
];
SimulateSlippage.flags = {
    // flag with a value (-n, --name=VALUE)
    pool: core_1.Flags.string({
        char: 'p',
        description: 'Pool which the strategy will be tested against.',
    }),
    // flag with no value (-f, --force)
    force: core_1.Flags.boolean({ char: 'f' }),
};
