// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { Test } from "forge-std/Test.sol";
import { MutualAidPoolV3 } from "../../contracts/mutualaid/MutualAidPoolV3.sol";
import { FeeOnTransferMockERC20 } from "../mocks/FeeOnTransferMockERC20.sol";
import { MockERC20 } from "../mocks/MockERC20.sol";
import { ForceNativeDonation, NoDeliveryMockERC20, OutboundFeeMockERC20 } from "../mocks/MaliciousERC20s.sol";

contract MutualAidPoolV3AssetAccountingTest is Test {
    address internal constant OWNER = address(0xBEEF);
    address internal constant DONOR = address(0xD0A0);
    address internal constant RECIPIENT = address(0xA11CE);

    function testFuzz_exactERC20ContributionPayoutAndSolvency(uint96 rawAmount) external {
        uint256 amount = bound(uint256(rawAmount), 2, 100 ether);
        MockERC20 token = new MockERC20();
        MutualAidPoolV3 pool = new MutualAidPoolV3(OWNER, address(token), amount, amount);
        token.mint(DONOR, amount);
        vm.startPrank(DONOR);
        token.approve(address(pool), amount);
        pool.contributeERC20(amount);
        vm.stopPrank();

        assertEq(pool.assets(), amount);
        assertEq(pool.liabilities(), amount);
        assertTrue(pool.isSolvent());
        vm.prank(OWNER);
        pool.pay(RECIPIENT, amount, keccak256("aid"));
        assertEq(token.balanceOf(RECIPIENT), amount);
        assertEq(pool.assets(), 0);
        assertEq(pool.liabilities(), 0);
    }

    function test_feeAndNoDeliveryContributionsAreRejected() external {
        FeeOnTransferMockERC20 fee = new FeeOnTransferMockERC20();
        MutualAidPoolV3 feePool = new MutualAidPoolV3(OWNER, address(fee), 10 ether, 10 ether);
        fee.mint(DONOR, 10 ether);
        vm.startPrank(DONOR);
        fee.approve(address(feePool), 10 ether);
        vm.expectRevert(MutualAidPoolV3.UnsupportedAssetTransfer.selector);
        feePool.contributeERC20(10 ether);
        vm.stopPrank();

        NoDeliveryMockERC20 noDelivery = new NoDeliveryMockERC20();
        MutualAidPoolV3 noDeliveryPool = new MutualAidPoolV3(OWNER, address(noDelivery), 10 ether, 10 ether);
        noDelivery.mint(DONOR, 10 ether);
        vm.startPrank(DONOR);
        noDelivery.approve(address(noDeliveryPool), 10 ether);
        vm.expectRevert(MutualAidPoolV3.UnsupportedAssetTransfer.selector);
        noDeliveryPool.contributeERC20(10 ether);
        vm.stopPrank();
    }

    function test_outboundFeePayoutRevertsWithoutReducingLiability() external {
        OutboundFeeMockERC20 fee = new OutboundFeeMockERC20();
        MutualAidPoolV3 pool = new MutualAidPoolV3(OWNER, address(fee), 10 ether, 10 ether);
        fee.mint(DONOR, 10 ether);
        vm.startPrank(DONOR);
        fee.approve(address(pool), 10 ether);
        pool.contributeERC20(10 ether);
        vm.stopPrank();

        vm.prank(OWNER);
        vm.expectRevert(MutualAidPoolV3.UnsupportedAssetTransfer.selector);
        pool.pay(RECIPIENT, 10 ether, keccak256("aid"));
        assertEq(pool.totalPaid(), 0);
        assertEq(pool.liabilities(), 10 ether);
        assertTrue(pool.isSolvent());
    }

    function test_forcedNativeTransferIsSurplusAndCannotDrainContributions() external {
        MutualAidPoolV3 pool = new MutualAidPoolV3(OWNER, address(0), 1 ether, 10 ether);
        vm.deal(DONOR, 1 ether);
        vm.prank(DONOR);
        (bool ok,) = address(pool).call{ value: 1 ether }("");
        assertTrue(ok);

        vm.prank(OWNER);
        vm.expectRevert(MutualAidPoolV3.InsufficientSurplus.selector);
        pool.skimSurplus(OWNER, 1);

        ForceNativeDonation donor = new ForceNativeDonation{ value: 2 ether }();
        donor.force(payable(address(pool)));
        assertEq(pool.assets(), 3 ether);
        assertEq(pool.liabilities(), 1 ether);
        assertEq(pool.surplus(), 2 ether);

        vm.prank(OWNER);
        pool.skimSurplus(OWNER, 2 ether);
        assertEq(pool.assets(), 1 ether);
        assertEq(pool.liabilities(), 1 ether);
        assertTrue(pool.isSolvent());
    }
}
