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

import {Test} from "forge-std/Test.sol";
import "../../../contracts/bridge/trustless/DualRouterBridgeSwapCoordinator.sol";
import "../../../contracts/bridge/trustless/ChallengeManager.sol";
import "../../../contracts/bridge/trustless/BondManager.sol";
import "../../../contracts/bridge/trustless/LiquidityPoolETH.sol";
import "../../../contracts/bridge/trustless/InboxETH.sol";
import "../../../contracts/bridge/trustless/EnhancedSwapRouter.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        _mint(msg.sender, 1000000 ether);
    }
    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
}

/**
 * Mock SwapRouter: implements swapToStablecoin(..., bytes) returns (uint256).
 * Transfers stablecoinToken to msg.sender (caller) for amountIn (1:1 mock).
 */
contract MockBasicSwapRouter {
    function swapToStablecoin(
        LiquidityPoolETH.AssetType,
        address stablecoinToken,
        uint256 amountIn,
        uint256,
        bytes calldata
    ) external payable returns (uint256) {
        if (msg.value > 0) {
            // Caller sent ETH; in real router it would wrap and swap. We just transfer stablecoin.
        }
        uint256 out = amountIn; // 1:1 mock
        require(IERC20(stablecoinToken).transfer(msg.sender, out), "transfer failed");
        return out;
    }
}

/**
 * Mock EnhancedSwapRouter: implements swapToStablecoin(..., SwapProvider) returns (uint256, SwapProvider).
 */
contract MockEnhancedSwapRouter {
    function swapToStablecoin(
        LiquidityPoolETH.AssetType,
        address stablecoinToken,
        uint256 amountIn,
        uint256,
        EnhancedSwapRouter.SwapProvider
    ) external payable returns (uint256 amountOut, EnhancedSwapRouter.SwapProvider providerUsed) {
        uint256 out = amountIn;
        require(IERC20(stablecoinToken).transfer(msg.sender, out), "transfer failed");
        return (out, EnhancedSwapRouter.SwapProvider.UniswapV3);
    }
}

contract DualRouterBridgeSwapCoordinatorTest is Test {
    DualRouterBridgeSwapCoordinator public coordinator;
    BondManager public bondManager;
    ChallengeManager public challengeManager;
    LiquidityPoolETH public liquidityPool;
    InboxETH public inbox;
    MockBasicSwapRouter public basicRouter;
    MockEnhancedSwapRouter public enhancedRouter;

    MockERC20 public weth;
    MockERC20 public usdt;

    address public deployer = address(0xDE01);
    address public relayer = address(0x2222);
    address public lpProvider = address(0x3333);
    address public recipient = address(0x4444);

    uint256 constant BOND_MULTIPLIER = 11000;
    uint256 constant MIN_BOND = 1 ether;
    uint256 constant CHALLENGE_WINDOW = 30 minutes;
    uint256 constant LP_FEE_BPS = 5;
    uint256 constant MIN_LIQUIDITY_RATIO_BPS = 11000;

    function setUp() public {
        vm.startPrank(deployer);

        weth = new MockERC20("WETH", "WETH");
        usdt = new MockERC20("USDT", "USDT");

        bondManager = new BondManager(BOND_MULTIPLIER, MIN_BOND);
        challengeManager = new ChallengeManager(address(bondManager), CHALLENGE_WINDOW);
        liquidityPool = new LiquidityPoolETH(address(weth), LP_FEE_BPS, MIN_LIQUIDITY_RATIO_BPS);
        inbox = new InboxETH(address(bondManager), address(challengeManager), address(liquidityPool));

        basicRouter = new MockBasicSwapRouter();
        enhancedRouter = new MockEnhancedSwapRouter();

        coordinator = new DualRouterBridgeSwapCoordinator(
            address(inbox),
            address(liquidityPool),
            address(basicRouter),
            address(enhancedRouter),
            address(challengeManager)
        );

        liquidityPool.authorizeRelease(address(coordinator));

        usdt.mint(address(basicRouter), 1000 ether);
        usdt.mint(address(enhancedRouter), 1000 ether);

        vm.deal(lpProvider, 1000 ether);
        vm.deal(relayer, 100 ether);

        vm.stopPrank();
    }

    function _setupFinalizedClaim(uint256 depositAmount) internal returns (uint256 depositId) {
        depositId = uint256(keccak256(abi.encodePacked(block.timestamp, recipient, depositAmount)));
        vm.prank(address(inbox));
        liquidityPool.addPendingClaim(depositAmount, LiquidityPoolETH.AssetType.ETH);

        // Simulate ChallengeManager having a finalized claim (we don't have Inbox.submitClaim in this test,
        // so we need to push claim state into ChallengeManager - but ChallengeManager is deployed and
        // we can't easily inject a claim without going through Inbox. So we use a different approach:
        // use the real flow: deploy Lockbox138, user deposits, relayer submits claim, time passes, finalize.
        // For unit test we can use vm.mockCall to make getClaim return a finalized claim.
        vm.mockCall(
            address(challengeManager),
            abi.encodeWithSelector(ChallengeManager.getClaim.selector, depositId),
            abi.encode(
                ChallengeManager.Claim({
                    depositId: depositId,
                    asset: address(0),
                    recipient: recipient,
                    amount: depositAmount,
                    challengeWindowEnd: block.timestamp,
                    finalized: true,
                    challenged: false
                })
            )
        );
        return depositId;
    }

    function testDeployment() public view {
        assertEq(address(coordinator.inbox()), address(inbox));
        assertEq(address(coordinator.liquidityPool()), address(liquidityPool));
        assertEq(address(coordinator.swapRouter()), address(basicRouter));
        assertEq(address(coordinator.enhancedSwapRouter()), address(enhancedRouter));
        assertEq(address(coordinator.challengeManager()), address(challengeManager));
    }

    function testCanSwapWhenFinalized() public {
        uint256 depositId = 1;
        vm.mockCall(
            address(challengeManager),
            abi.encodeWithSelector(ChallengeManager.getClaim.selector, depositId),
            abi.encode(
                ChallengeManager.Claim({
                    depositId: depositId,
                    asset: address(0),
                    recipient: recipient,
                    amount: 1 ether,
                    challengeWindowEnd: block.timestamp,
                    finalized: true,
                    challenged: false
                })
            )
        );
        (bool canSwap, string memory reason) = coordinator.canSwap(depositId);
        assertTrue(canSwap);
        assertEq(reason, "");
    }

    function testCanSwapWhenNotFinalized() public {
        uint256 depositId = 2;
        vm.mockCall(
            address(challengeManager),
            abi.encodeWithSelector(ChallengeManager.getClaim.selector, depositId),
            abi.encode(
                ChallengeManager.Claim({
                    depositId: depositId,
                    asset: address(0),
                    recipient: recipient,
                    amount: 1 ether,
                    challengeWindowEnd: block.timestamp,
                    finalized: false,
                    challenged: false
                })
            )
        );
        (bool canSwap, string memory reason) = coordinator.canSwap(depositId);
        assertFalse(canSwap);
        assertEq(reason, "Claim not finalized");
    }
}
