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

import {Test} from "forge-std/Test.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {
    AaveQuotePushFlashReceiver,
    IAaveExternalUnwinder
} from "../../contracts/flash/AaveQuotePushFlashReceiver.sol";

contract AaveForkMockExternalUnwinder is IAaveExternalUnwinder {
    IERC20 public immutable base;
    IERC20 public immutable quote;
    uint256 public immutable numerator;
    uint256 public immutable denominator;

    constructor(IERC20 base_, IERC20 quote_, uint256 numerator_, uint256 denominator_) {
        base = base_;
        quote = quote_;
        numerator = numerator_;
        denominator = denominator_;
    }

    function unwind(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes calldata)
        external
        override
        returns (uint256 amountOut)
    {
        require(tokenIn == address(base), "base only");
        require(tokenOut == address(quote), "quote only");
        IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
        amountOut = amountIn * numerator / denominator;
        require(amountOut >= minAmountOut, "min unwind");
        IERC20(address(quote)).transfer(msg.sender, amountOut);
    }
}

contract AaveQuotePushFlashReceiverMainnetForkTest is Test {
    address constant AAVE_POOL_MAINNET = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
    address constant DODO_PMM_INTEGRATION_MAINNET = 0xa9F284eD010f4F7d7F8F201742b49b9f58e29b84;
    address constant POOL_CWUSDC_USDC = 0x69776fc607e9edA8042e320e7e43f54d06c68f0E;
    address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    address constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;

    bool public forkAvailable;
    AaveQuotePushFlashReceiver internal receiver;
    AaveForkMockExternalUnwinder internal unwinder;

    modifier skipIfNoFork() {
        if (!forkAvailable) {
            return;
        }
        _;
    }

    function setUp() public {
        string memory rpcUrl = vm.envOr("ETHEREUM_MAINNET_RPC", string(""));
        if (bytes(rpcUrl).length == 0) {
            forkAvailable = false;
            return;
        }

        try vm.createSelectFork(rpcUrl) {
            forkAvailable = true;
        } catch {
            forkAvailable = false;
            return;
        }

        receiver = new AaveQuotePushFlashReceiver(AAVE_POOL_MAINNET, address(this));
        unwinder = new AaveForkMockExternalUnwinder(IERC20(CWUSDC), IERC20(USDC), 112, 100);
        // PMM + unwind sizing can require materially more than 100 USDC on a live reserve snapshot.
        deal(USDC, address(unwinder), 50_000_000_000); // 50k USDC (6 decimals) for mock unwind payouts
    }

    function testFork_aaveQuotePush_usesRealAaveAndRealMainnetPmm() public skipIfNoFork {
        uint256 amount = 2_964_298; // current safe tranche at 120/120
        uint256 receiverQuoteBefore = IERC20(USDC).balanceOf(address(receiver));
        uint256 poolBaseBefore = IERC20(CWUSDC).balanceOf(POOL_CWUSDC_USDC);
        uint256 poolQuoteBefore = IERC20(USDC).balanceOf(POOL_CWUSDC_USDC);

        AaveQuotePushFlashReceiver.QuotePushParams memory p = AaveQuotePushFlashReceiver.QuotePushParams({
            integration: DODO_PMM_INTEGRATION_MAINNET,
            pmmPool: POOL_CWUSDC_USDC,
            baseToken: CWUSDC,
            externalUnwinder: address(unwinder),
            minOutPmm: 2_800_000,
            minOutUnwind: amount + 1_483, // 5 bps Aave premium
            unwindData: bytes(""),
            atomicBridge: AaveQuotePushFlashReceiver.AtomicBridgeParams({
                coordinator: address(0),
                sourceChain: 0,
                destinationChain: 0,
                destinationAsset: address(0),
                bridgeAmount: 0,
                minDestinationAmount: 0,
                destinationRecipient: address(0),
                destinationDeadline: 0,
                routeId: bytes32(0),
                settlementMode: bytes32(0),
                submitCommitment: false
            })
        });

        receiver.flashQuotePush(USDC, amount, p);

        uint256 receiverQuoteAfter = IERC20(USDC).balanceOf(address(receiver));
        uint256 poolBaseAfter = IERC20(CWUSDC).balanceOf(POOL_CWUSDC_USDC);
        uint256 poolQuoteAfter = IERC20(USDC).balanceOf(POOL_CWUSDC_USDC);

        assertGt(receiverQuoteAfter, receiverQuoteBefore, "receiver retains surplus");
        assertEq(IERC20(CWUSDC).balanceOf(address(receiver)), 0, "base fully unwound");
        assertLt(poolBaseAfter, poolBaseBefore, "pool base decreased via quote push");
        assertGt(poolQuoteAfter, poolQuoteBefore, "pool quote increased via quote push");
    }

    function testFork_ownerCanSweepRetainedQuoteSurplus() public skipIfNoFork {
        uint256 amount = 2_964_298;
        address recipient = address(0xBEEF);

        AaveQuotePushFlashReceiver.QuotePushParams memory p = AaveQuotePushFlashReceiver.QuotePushParams({
            integration: DODO_PMM_INTEGRATION_MAINNET,
            pmmPool: POOL_CWUSDC_USDC,
            baseToken: CWUSDC,
            externalUnwinder: address(unwinder),
            minOutPmm: 2_800_000,
            minOutUnwind: amount + 1_483,
            unwindData: bytes(""),
            atomicBridge: AaveQuotePushFlashReceiver.AtomicBridgeParams({
                coordinator: address(0),
                sourceChain: 0,
                destinationChain: 0,
                destinationAsset: address(0),
                bridgeAmount: 0,
                minDestinationAmount: 0,
                destinationRecipient: address(0),
                destinationDeadline: 0,
                routeId: bytes32(0),
                settlementMode: bytes32(0),
                submitCommitment: false
            })
        });

        receiver.flashQuotePush(USDC, amount, p);

        uint256 retainedBeforeSweep = IERC20(USDC).balanceOf(address(receiver));
        uint256 reserveRetained = 10_000;
        uint256 expectedSweep = retainedBeforeSweep - reserveRetained;

        uint256 sweepable = receiver.quoteSurplusBalance(USDC, reserveRetained);
        assertEq(sweepable, expectedSweep, "sweepable balance excludes retained reserve");

        uint256 recipientBefore = IERC20(USDC).balanceOf(recipient);
        receiver.sweepQuoteSurplus(USDC, recipient, reserveRetained);

        assertEq(IERC20(USDC).balanceOf(address(receiver)), reserveRetained, "receiver keeps configured reserve");
        assertEq(IERC20(USDC).balanceOf(recipient), recipientBefore + expectedSweep, "recipient receives swept surplus");
    }
}
