// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.30;

import "forge-std/console2.sol";
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
import {CommonBase} from "../lib/forge-std/src/Base.sol";
import {Script} from "../lib/forge-std/src/Script.sol";
import {StdChains} from "../lib/forge-std/src/StdChains.sol";
import {StdCheatsSafe} from "../lib/forge-std/src/StdCheats.sol";
import {StdUtils} from "../lib/forge-std/src/StdUtils.sol";
import {IERC3156FlashBorrower} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IPartyInfo} from "../src/IPartyInfo.sol";
import {IPartyPool} from "../src/IPartyPool.sol";
import {LMSRStabilized} from "../src/LMSRStabilized.sol";
import {NativeWrapper} from "../src/NativeWrapper.sol";
import {PartyInfo} from "../src/PartyInfo.sol";
import {PartyPlanner} from "../src/PartyPlanner.sol";
import {PartyPoolInitCode, PartyPoolBalancedPairInitCode} from "../src/PartyPoolDeployer.sol";
import {PartyPoolMintImpl} from "../src/PartyPoolMintImpl.sol";
import {PartyPoolSwapImpl} from "../src/PartyPoolSwapImpl.sol";
import {MockERC20} from "../test/MockERC20.sol";
import {MockFlashBorrower} from "../test/MockFlashBorrower.sol";

contract DeploySepolia is Script {
    // for some reason the mintAll() causes Sepolia to complain sometimes with:
    // server returned an error response: error code -32003: gas limit too high
    bool constant private ALLOW_MINT = true;

    address constant public PROTOCOL_FEE_ADDRESS = 0x0E280F5eDA58872d7cDaA8AC0A57A55fD6133AEd;
    uint256 constant public PROTOCOL_FEE_PPM = 10_0000;  // 10% of LP fees
    NativeWrapper constant public WETH = NativeWrapper(0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14);

    function run() public {
        require(block.chainid == 11155111, 'Not Sepolia');

        vm.startBroadcast();

        console2.log('deploying mock tokens');
        // create mock _tokens
        // usxd = new MockERC20('Joke Currency', 'USXD', 6);
        // fusd = new MockERC20('Fake USD', 'FUSD', 6);
        // dive = new MockERC20('DAI Virtually Equal', 'DIVE', 18);
        // butc = new MockERC20('Buttcoin', 'BUTC', 8);
        // wteth = new MockERC20('Wrapped TETH', 'WTETH', 18);
        usxd = MockERC20(0x8E4D16886b8946dfE463fA172129eaBf4825fb09);
        fusd = MockERC20(0xdc225280216822CA956738390f589c794129bd53);
        dive = MockERC20(0x7ba123e4e7395A361284d069bD0D545F3f820641);
        butc = MockERC20(0x88125947BBF1A6dd0FeD4B257BB3f9E1FBdCb3Cc);
        wteth = MockERC20(0xC8dB65C0B9f4cf59097d4C5Bcb9e8E92B9e4e15F);
        vm.label(address(usxd), 'USXD');
        vm.label(address(fusd), 'FUSD');
        vm.label(address(dive), 'DIVE');
        vm.label(address(butc), 'BUTC');
        vm.label(address(wteth), 'WTETH');

        // give tokens to msg.sender for later use
        mintAll(msg.sender, 1_000_000);

        console2.log('creating swap impl');
        PartyPoolSwapImpl swapImpl = new PartyPoolSwapImpl(WETH);
        console2.log('creating mint impl');
        PartyPoolMintImpl mintImpl = new PartyPoolMintImpl(WETH);
        console2.log('creating pool init');
        PartyPoolInitCode poolInit = new PartyPoolInitCode();
        console2.log('creating bppool init');
        PartyPoolBalancedPairInitCode bpInit = new PartyPoolBalancedPairInitCode();

        // deploy a PartyPlanner factory and create the pool via factory
        console2.log('creating planner');
        PartyPlanner planner = new PartyPlanner(
            msg.sender, // admin address is the same as the deployer
            WETH,
            swapImpl,
            mintImpl,
            poolInit,
            bpInit,
            PROTOCOL_FEE_PPM,
            PROTOCOL_FEE_ADDRESS
        );

        approveAll(address(planner) );

        //
        // Deploy 3-asset pool
        //

        uint256 _feePpm = 25_00; // 25 bps
        IERC20[] memory tokens = new IERC20[](3);
        tokens[0] = IERC20(usxd);
        tokens[1] = IERC20(butc);
        tokens[2] = IERC20(wteth);
        uint256[] memory _bases = new uint256[](3);
        _bases[0] = 10**6;
        _bases[1] = 10**8;
        _bases[2] = 10**18;
        uint256[] memory _feesPpm = new uint256[](3);
        _feesPpm[0] = 50;
        _feesPpm[1] = 250;
        _feesPpm[2] = 350;
        uint256[] memory _prices = new uint256[](3);
        _prices[0] = 1;
        _prices[1] = 100000;
        _prices[2] = 4000;

        // prepare initial deposits (10_000 units of each token, scaled by _bases)
        uint256[] memory initialDeposits = new uint256[](3);
        initialDeposits[0] = 10_000 * _bases[0] / _prices[0];
        initialDeposits[1] = 10_000 * _bases[1] / _prices[1];
        initialDeposits[2] = 10_000 * _bases[2] / _prices[2];
        int128 kappa = LMSRStabilized.computeKappaFromSlippage(3, ABDKMath64x64.divu(1, 10), ABDKMath64x64.divu(50,10000));

        // call full newPool signature on factory which will take the deposits and mint initial LP
        console2.log('deploying exercise pool');
        (IPartyPool exercisePool,) = planner.newPool(
            'Token Pool',
            'TP',
            tokens,
            kappa,
            _feesPpm,
            _feePpm,
            false,
            msg.sender, // payer: this script
            msg.sender,   // receiver of initial LP
            initialDeposits,
            10_000 * 10**18,
            0
        );


        //
        // Deploy 3-asset stablecoin pool
        //

        _feePpm = 1_00; // 1 bp
        tokens = new IERC20[](3);
        tokens[0] = IERC20(usxd);
        tokens[1] = IERC20(fusd);
        tokens[2] = IERC20(dive);
        _bases = new uint256[](3);
        _bases[0] = 10**6;
        _bases[1] = 10**6;
        _bases[2] = 10**18;

        // prepare initial deposits (10_000 units of each token, scaled by _bases)
        initialDeposits = new uint256[](3);
        initialDeposits[0] = _bases[0] * 10_000;
        initialDeposits[1] = _bases[1] * 10_000;
        initialDeposits[2] = _bases[2] * 10_000;

        // call full newPool signature on factory which will take the deposits and mint initial LP
        console2.log('deploying stablecoin pool');
        planner.newPool(
            'Stablecoin Pool',
            'STAP',
            tokens,
            ABDKMath64x64.divu(1, 10),
            ABDKMath64x64.divu(1,10000),
            _feePpm,
            _feePpm,
            false,
            msg.sender, // payer: this script
            msg.sender,   // receiver of initial LP
            initialDeposits,
            10_000 * 10**18,
            0
        );


        //
        // Deploy 2-asset balanced pair pool
        //

        _feePpm = 7; // 0.07 bp
        tokens = new IERC20[](2);
        tokens[0] = IERC20(usxd);
        tokens[1] = IERC20(dive);
        _bases = new uint256[](2);
        _bases[0] = 10**6;
        _bases[1] = 10**18;

        // prepare initial deposits (10_000 units of each token, scaled by _bases)
        initialDeposits = new uint256[](2);
        initialDeposits[0] = _bases[0] * 10_000;
        initialDeposits[1] = _bases[1] * 10_000;

        // call full newPool signature on factory which will take the deposits and mint initial LP
        console2.log('deploying balanced pair pool');
        planner.newPool(
            'Stable Pair',
            'SPAIR',
            tokens,
            ABDKMath64x64.divu(8,10), // kappa = 0.8
            _feePpm,
            _feePpm,
            true, // STABLE
            msg.sender, // payer: this script
            msg.sender,   // receiver of initial LP
            initialDeposits,
            10_000 * 10**18,
            0
        );

        console2.log('deploying info');
        PartyInfo info = new PartyInfo(swapImpl, mintImpl);

        console2.log('running exercise');
        exercise(exercisePool, info);

        console2.log('broadcast completed');
        vm.stopBroadcast();

        // Set ENV vars
        string memory plannerStr = vm.toString(address(planner));
        string memory infoStr = vm.toString(address(info));
        vm.setEnv('PLANNER', plannerStr);
        vm.setEnv('INFO', infoStr);
        vm.setEnv('USXD', vm.toString(address(usxd)));
        vm.setEnv('FUSD', vm.toString(address(fusd)));
        vm.setEnv('DIVE', vm.toString(address(dive)));
        vm.setEnv('BUTC', vm.toString(address(butc)));
        vm.setEnv('WTETH', vm.toString(address(wteth)));

        console2.log();
        console2.log('  PartyPlanner', address(planner));
        console2.log('     PartyInfo', address(info));
        console2.log('      SwapImpl', address(swapImpl));
        console2.log('      MintImpl', address(mintImpl));
        console2.log('      PoolCode', address(poolInit));
        console2.log('    BPPoolCode', address(bpInit));
        console2.log();
        console2.log('          USXD', address(usxd));
        console2.log('          FUSD', address(fusd));
        console2.log('          DIVE', address(dive));
        console2.log('          BUTC', address(butc));
        console2.log('         WTETH', address(wteth));
    }

    MockERC20 private usxd;
    MockERC20 private fusd;
    MockERC20 private dive;
    MockERC20 private butc;
    MockERC20 private wteth;

    function mintAll(address who, uint256 amount) internal {
        if(ALLOW_MINT) {
            console2.log('minting mock tokens');
            usxd.mint(who, amount * 1e6);
            fusd.mint(who, amount * 1e6);
            dive.mint(who, amount * 1e18);
            butc.mint(who, amount * 1e8);
            wteth.mint(who, amount * 1e18);
        }
    }

    function approveAll(address spender) internal {
        usxd.approve(spender, type(uint256).max);
        fusd.approve(spender, type(uint256).max);
        dive.approve(spender, type(uint256).max);
        butc.approve(spender, type(uint256).max);
        wteth.approve(spender, type(uint256).max);
    }

    function exercise( IPartyPool pool, IPartyInfo info) internal {
        // gather tokens and denominators
        IERC20[] memory tokens = pool.allTokens();
        uint256 n = tokens.length;

        approveAll(address(pool));

        console2.log('post-creation supply', pool.totalSupply());

        // 1) Proportional mint (request some LP)
        uint256 lpToMint = 1_234; // arbitrary non-even amount
        // payer = this contract, receiver = this contract
        uint256 minted = pool.mint(msg.sender, msg.sender, lpToMint, 0);

        console2.log('minted', minted);
        console2.log('post-mint supply', pool.totalSupply());

        // 2) Proportional burn (withdraw a small, non-even amount of LP)
        uint256 lpToBurn = 7;
        pool.burn(msg.sender, msg.sender, lpToBurn, 0, false);

        // 3) Flash loan: borrow token 0 and immediately repay in callback
        // deploy a temporary borrower that repays amount + fee back to the pool
        MockFlashBorrower borrower = new MockFlashBorrower();
        uint256 flashAmt = 53 * 10**6; // arbitrary non-even amount
        uint256 flashFee = info.flashFee(pool, address(tokens[0]), flashAmt);
        // Mint enough to cover the flash fee
        MockERC20(address(tokens[0])).mint(address(borrower), flashFee);
        // pass the pool address in data so borrower can repay back to this pool
        bytes memory data = abi.encode(address(pool));
        // call flashLoan (ignore success boolean/revert)
        pool.flashLoan(IERC3156FlashBorrower(address(borrower)), address(tokens[0]), flashAmt, data);

        // 4) swapMint (single-token mint -> LP)
        uint256 swapMintAmt = 321 * 10**6; // not even
        pool.swapMint(msg.sender, msg.sender, 0, swapMintAmt, 0);

        // 5) regular swap (token 0 -> last token)
        uint256 inputIndex = 0;
        uint256 outputIndex = n > 1 ? n - 1 : 0;
        uint256 maxIn = 89 * 10**6; // varied
        pool.swap(msg.sender, bytes4(0), msg.sender, inputIndex, outputIndex, maxIn, int128(0), 0, false, '');

        // 6) Collect protocol fees now (after some swaps) so some will have been moved out
        pool.collectProtocolFees();

        // 7) Final swap-style operation: burnSwap (burn LP then swap to single asset)
        // ensure we have some LP allowance
        uint256 lpForBurnSwap = 3 * 10**18; // non-even small amount
        uint256 burnToIndex = (n > 1) ? 1 : 0;
        pool.burnSwap(msg.sender, msg.sender, lpForBurnSwap, burnToIndex, 0, false);
    }
}
