contract AuroxBridge is 
    Ownable,
    ReentrancyGuard,
    IAuroxBridge {
    using SafeERC20 for IERC20;

    address public immutable usdcAddress;
    IUniswapV2Router02 public immutable uniRouter;

    address immutable nodeAddress;
    mapping(address => uint96) userInfo;

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public assetsOf;

    modifier onlyNode() {
        require(msg.sender == nodeAddress, "Only node allowed!");
        _;
    }

    event Withdraw(address indexed token, address indexed recipient, uint256 amount);

    constructor(
        address usdcAddress_,
        address routerAddress_,
        address nodeAddress_        
    ) {
        usdcAddress = usdcAddress_;
        uniRouter = IUniswapV2Router02(routerAddress_);
        nodeAddress = nodeAddress_;
    }

    function withdrawETH(address recipient) external onlyOwner {
        uint256 balance = address(this).balance;
        if (balance > 0) {
            (bool success, ) = recipient.call{value: balance}(new bytes(0));
            require(success, 'ETH_TRANSFER_FAILED'); 
            emit Withdraw(address(0), recipient, balance);
        }
    }

    function withdraw(IERC20 token, address recipient) external onlyOwner {
        _withdraw(token, recipient);
    }

    function batchWithdraw(IERC20[] calldata tokens, address recipient) external onlyOwner {
        for (uint256 i = 0; i < tokens.length; i++) {
            _withdraw(tokens[i], recipient);
        }
    }

    /**
    * @notice Register the swap request from user
    * @dev thisTokenPath[0] = inputToken, thisTokenPath[last] = stableToken
    */
    function registerSwap(
        address[] calldata thisTokenPath,
        address[] calldata targetTokenPath,
        uint256 amountIn,
        uint256 minAmountOut) external override nonReentrant {       
        require(
            thisTokenPath.length > 1 &&
            targetTokenPath.length > 1 &&
            amountIn > 0 &&
            minAmountOut > 0,
            "bad input params"
        );
        require(
            thisTokenPath[thisTokenPath.length-1] == usdcAddress,
            "token path should end with usdc"
        );

        IERC20 token = IERC20(thisTokenPath[0]);

        require(
            token.allowance(msg.sender, address(this)) >= amountIn,
            "token allowance is not enough"
        );

        token.safeTransferFrom(msg.sender, address(this), amountIn);
        token.safeApprove(address(uniRouter), amountIn);

        IERC20 usdc = IERC20(usdcAddress);
        uint256 usdcBalanceBefore = usdc.balanceOf(address(this));

        uniRouter.swapExactTokensForTokens(
            amountIn,
            1,
            thisTokenPath,
            address(this),
            block.timestamp);
        
        
        uint256 amountUsd = usdc.balanceOf(address(this)) - usdcBalanceBefore;
        require(amountUsd > 0, "bad swap");
        
        balanceOf[msg.sender] += amountUsd;

        emit RegisterSwap(msg.sender,
            thisTokenPath[0],
            targetTokenPath[targetTokenPath.length - 1],
            amountIn,
            amountUsd
            );
    }

    /**
    * @notice Purchase asset on behalf of user
    * @dev thisTokenPath should be generated by user
    */
    function buyAssetOnBehalf(
        address[] calldata thisTokenPath,
        address userAddress,
        uint256 usdAmount,
        uint256 usdBalance) external override onlyNode {
        require(
            thisTokenPath.length > 1 &&
            userAddress != address(0),
            "bad input params"
        );
        require(
            usdAmount <= usdBalance - balanceOf[userAddress],
            "bad user balance"
        );
        require(
            thisTokenPath[0] == usdcAddress,
            "token path should start with usdc"
        );

        IERC20 usdc = IERC20(usdcAddress);
        require(
            usdc.balanceOf(address(this)) >= usdAmount,
            "too small contract usdc balance"
        );

        usdc.safeApprove(address(uniRouter), usdAmount);

        IERC20 token = IERC20(thisTokenPath[thisTokenPath.length - 1]);
        uint256 tokenBalanceBefore = token.balanceOf(address(this));

        uniRouter.swapExactTokensForTokens(
            usdAmount,
            1,
            thisTokenPath,
            address(this),
            block.timestamp);

        uint256 amountToken = token.balanceOf(address(this)) - tokenBalanceBefore;
        require(amountToken > 0, "bad swap");
        assetsOf[userAddress][address(token)] += amountToken;
        balanceOf[userAddress] += usdAmount;

        emit BuyAssetOnBehalf(
            userAddress,
            address(token),
            usdAmount,
            amountToken);
    }

    /**
    * @notice Issue nft that representing specific token amount purchased
    */
    function issueOwnershipNft(address userAddress, address token, uint256 amount) external override {}

    function _withdraw(IERC20 token, address recipient) internal {
        uint256 amount = token.balanceOf(address(this));
        if (amount > 0) {
            token.safeTransfer(recipient, amount);
            emit Withdraw(address(token), recipient, amount);
        }
    }
}
function registerSwap(
    address[] calldata thisTokenPath,
    address[] calldata targetTokenPath,
    uint256 amountIn,
    uint256 minAmountOut) external override nonReentrant {       
    require(
        thisTokenPath.length > 1 &&
        targetTokenPath.length > 1 &&
        amountIn > 0 &&
        minAmountOut > 0,
        "bad input params"
    );
    require(
        thisTokenPath[thisTokenPath.length-1] == usdcAddress,
        "token path should end with usdc"
    );

    IERC20 token = IERC20(thisTokenPath[0]);

    require(
        token.allowance(msg.sender, address(this)) >= amountIn,
        "token allowance is not enough"
    );

    token.safeTransferFrom(msg.sender, address(this), amountIn);
    token.safeApprove(address(uniRouter), amountIn);

    IERC20 usdc = IERC20(usdcAddress);
    uint256 usdcBalanceBefore = usdc.balanceOf(address(this));

    uniRouter.swapExactTokensForTokens(
        amountIn,
        1,
        thisTokenPath,
        address(this),
        block.timestamp);
        
        
    uint256 amountUsd = usdc.balanceOf(address(this)) - usdcBalanceBefore;
    require(amountUsd > 0, "bad swap");
        
    balanceOf[msg.sender] += amountUsd;

    emit RegisterSwap(msg.sender,
        thisTokenPath[0],
        targetTokenPath[targetTokenPath.length - 1],
        amountIn,
        amountUsd
        );
}

/**
 * @notice Register the swap request from user
 * @dev thisTokenPath[0] = inputToken, thisTokenPath[last] = stableToken
 */