
pragma solidity 0.8.26;

import "@forge-std/console2.sol";
import "../src/more/MockERC20.sol";
import "../src/core/Util.sol";
import "./MockUtil.sol";
import "../lib_uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "../lib_uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
import "../lib_uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
import "../src/more/FeeManagerLib.sol";
import "../src/core/VaultImpl.sol";
import "../src/core/VaultFactory.sol";
import {ArbitrumRouter} from "../src/core/Router.sol";


contract MockEnv {

    IVaultFactory public factory;

    INonfungiblePositionManager private nfpm =
        INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); // Arbitrum

    IUniswapV3Pool public pool;
    uint24 public fee;
    MockERC20 public COIN;
    MockERC20 public USD;
    address public token0; // either COIN or USD depending on the order in the pool
    address public token1;
    bool public inverted;

    // sets up two mock coins COIN and USD, plus a uniswap v3 pool.
    function init() public {
        initNoFees();
    }

    function initDebugFees() public {
        return init(new ArbitrumRouter(), FeeManagerLib.debugFeeManager());
    }

    function initNoFees() public {
        return init(new ArbitrumRouter(), FeeManagerLib.freeFeeManager());
    }

    function init(IRouter router, FeeManager feeManager) public {
//        console2.log('init MockEnv...');
        VaultImpl impl = new VaultImpl(router, feeManager, address(0));
        factory = new VaultFactory(msg.sender, address(impl), 2*60); // 2 minutes upgrade notice

        console2.log('MockEnv: msg.sender:', msg.sender);
//        console2.log('MockEnv: tx.origin:' , tx.origin);
        COIN = new MockERC20('Mock Ethereum Hardfork', 'MEH', 18);
        console2.log('MEH');
        console2.log(address(COIN));
        USD = new MockERC20('Joke Currency XD', 'USXD', 6);
        console2.log('USXD');
        console2.log(address(USD));
        fee = 500;
        inverted = address(COIN) > address(USD);
        token0 = inverted ? address(USD) : address(COIN);
        token1 = inverted ? address(COIN) : address(USD);
        console2.log('if this is the last line before a revert then make sure to run forge with --rpc-url');
        // if this reverts here make sure Anvil is started and you are running forge with --rpc-url
        pool = IUniswapV3Pool(nfpm.createAndInitializePoolIfNecessary(token0, token1, fee, oneSqrtX96()));
        console2.log('v3 pool');
        console2.log(address(pool));
        // stake a super wide range so we have liquidity everywhere.
        uint256 amount = 10_000*1774545 * 10**12; // 1774545 is the number of ticks so this is $10k liquidity per 0.1%
        stake(amount, amount, TickMath.MIN_TICK, TickMath.MAX_TICK);
    }


    function oneSqrtX96() public view returns (uint160) {
        return inverted ? uint160(79228162514264337593543950336000000) : uint160(79228162514264337593543); // $1.00 * 2^96 * 10^±12
    }


    function swapTo1() public {
        swapToPrice(oneSqrtX96());
    }


    function stake(uint256 amount, int24 width) public {
        require(width>0);
        (, int24 tick, , , , ,) = pool.slot0();
        stake(amount, tick-width, tick+width);
    }


    function stake(uint256 amount, int24 lower, int24 upper) public {
        uint256 coinAmount = amount * 10**18 / 2;
        uint256 usdAmount = amount * 10**6 / 2;
        stake(coinAmount, usdAmount, lower, upper);
    }


    function stake(uint256 coinAmount, uint256 usdAmount, int24 lower, int24 upper) public
    returns (
        uint256 tokenId,
        uint128 liquidity,
        uint256 amount0,
        uint256 amount1
    )
    {
        return _stake(coinAmount, usdAmount, lower, upper);
    }

    function _stake(uint256 coinAmount, uint256 usdAmount, int24 lower, int24 upper) private
    returns (
        uint256 tokenId,
        uint128 liquidity,
        uint256 amount0,
        uint256 amount1
    )
    {

        uint256 a0 = inverted ? usdAmount : coinAmount;
        uint256 a1 = inverted ? coinAmount : usdAmount;
        if (inverted) {
            lower = -upper;
            upper = -lower;
        }

        (tokenId, liquidity, amount0, amount1) = MockUtil.stake(pool, a0, a1, lower, upper);
    }

    function swap(MockERC20 inToken, MockERC20 outToken, uint256 amountIn) public returns (uint256 amountOut) {
        uint160 limit = address(inToken) == pool.token0() ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1;
        return swap(inToken, outToken, amountIn, limit);
    }

    function swap(MockERC20 inToken, MockERC20 outToken, uint256 amountIn, uint160 sqrtPriceLimitX96) public returns (uint256 amountOut) {
        return MockUtil.swap(pool, inToken, outToken, amountIn, sqrtPriceLimitX96);
    }

    function price() public view returns (uint160 sqrtPriceX96) {
        (sqrtPriceX96,,,,,,) = pool.slot0();
    }

    function swapToPrice(uint160 sqrtPriceLimitX96) public {
        MockUtil.swapToPrice(pool, sqrtPriceLimitX96);
    }
}
