
pragma solidity 0.8.26;

import "@forge-std/console2.sol";
import "../src/core/VaultFactory.sol";
import "../src/more/VaultAddress.sol";
import "@forge-std/Test.sol";
import "../src/interface/IVault.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {MockEnv} from "./MockEnv.sol";

contract TestCoin is ERC20 {
    constructor(uint256 initialSupply) ERC20("TestCoin", "TCOIN") {
        _mint(msg.sender, initialSupply);
    }

    function mint(address to, uint256 amount) public {
        _mint(to, amount);
    }
}

contract TestDeployVault is Test, MockEnv {
    function setUp() public {
        initNoFees();
    }

    function testDeployVault() public {
        factory.deployVault();
    }
}

contract TestVault is Test, MockEnv {

    IVault public vault;
    address payable owner = payable(address(this)); // this contract owns vault

    receive() external payable {} // this is owner and needs to be able to receive native

    uint256 constant runnerGib = 2**96-1; // Test runner gives Test{} some native to start;

    function setUp() public {
        initNoFees();
        console2.log("setUp()");
        console2.log("msg.sender, balance  ", msg.sender, payable(msg.sender).balance);
        console2.log("owner, balance       ", owner, owner.balance);
        assert (owner == address(this));
        assert (owner.balance == runnerGib);

        console2.log("factory, balance     ", address(factory), address(factory).balance);

        vault = factory.deployVault(owner);
        assert (vault.owner() == owner);
        console2.log("vault, balance       ", address(vault), address(vault).balance);
    }

    function testDeterministicAddress() public view {
        console2.log(address(vault));
        address d = VaultAddress.computeAddress(address(factory), owner);
        console2.log(d);
        assert(address(vault) == d);
    }

    function testWithdraw() public {

        console2.log("testWithdraw()");
        console2.log("msg.sender, balance  ", msg.sender, msg.sender.balance); // msg.sender == test runner

        // get vault address and give some eth
        address payable vaultAddr = payable(address(vault));
        assert(vaultAddr.balance == 0);
        uint256 vaultNative = 1 ether;
        vm.deal(vaultAddr, vaultNative); // Give vault some native
        console2.log("vault, balance       ", address(vault), address(vault).balance);
        assert (vaultAddr.balance == vaultNative);

        // get address for withdrawTo()
        uint256 seed = uint256(keccak256(abi.encodePacked("testSeed")));
        address payable toAddr = payable(vm.addr(seed));

        // Verify native withdrawTo()
        assert(vaultAddr.balance == vaultNative);
        assert(toAddr.balance == 0);
        vault.withdrawTo(toAddr, 100);
        assert(toAddr.balance == 100);
        assert(vaultAddr.balance == vaultNative - 100);

        // Verify native withdraw()
        vault.withdraw(100);
        assert(vaultAddr.balance == vaultNative - 200);
        assert(owner.balance == runnerGib + 100);
        console2.log("owner, balance       ", owner, owner.balance);

    }

    function testWithdrawERC20() public {

        TestCoin testCoin = new TestCoin(0); // Zero tokens to start

        console2.log("testWithdrawERC20()");

        // give vault some tokens
        address payable vaultAddr = payable(address(vault));
        assert(testCoin.balanceOf(vaultAddr) == 0);

        uint256 vaultTokens = 1000;
        testCoin.mint(vaultAddr, vaultTokens); // Give vault some tokens

        console2.log("vault, balance       ", address(vault), testCoin.balanceOf(vaultAddr)) ;
        assert (testCoin.balanceOf(vaultAddr) == vaultTokens);

        // get address for withdrawTo()
        uint256 seed = uint256(keccak256(abi.encodePacked("testSeed")));
        address payable toAddr = payable(vm.addr(seed));

        // Verify token withdrawTo()
        assert(testCoin.balanceOf(vaultAddr) == vaultTokens);
        assert(testCoin.balanceOf(toAddr) == 0);

        vault.withdrawTo(testCoin, toAddr, 100);
        assert(testCoin.balanceOf(toAddr) == 100);
        assert(testCoin.balanceOf(vaultAddr) == vaultTokens - 100);

        // Verify token withdraw()
        vault.withdraw(testCoin, 100);
        assert(testCoin.balanceOf(vaultAddr) == vaultTokens - 200);
        assert(testCoin.balanceOf(owner) == 100);
        console2.log("owner, balance       ", owner, testCoin.balanceOf(owner));

    }
}
