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

import "forge-std/Test.sol";

import "../../../contracts/bridge/atomic/AtomicBridgeCoordinator.sol";
import "../../../contracts/bridge/atomic/AtomicFeePolicy.sol";
import "../../../contracts/bridge/atomic/AtomicFulfillerRegistry.sol";
import "../../../contracts/bridge/atomic/AtomicLiquidityVault.sol";
import "../../../contracts/bridge/atomic/AtomicObligationEscrow.sol";
import "../../../contracts/bridge/atomic/AtomicQuoteEngine.sol";
import "../../../contracts/bridge/atomic/AtomicSettlementRouter.sol";
import "../../../contracts/bridge/atomic/AtomicSlashingManager.sol";
import "../../../contracts/bridge/atomic/AtomicTypes.sol";
import "../../../contracts/bridge/atomic/interfaces/IAtomicSettlementAdapter.sol";
import "../../dbis/MockMintableToken.sol";

contract MockAtomicSettlementAdapter is IAtomicSettlementAdapter {
    address public lastToken;
    uint256 public lastAmount;
    address public lastRecipient;
    bytes public lastData;

    function executeSettlement(
        bytes32 obligationId,
        address token,
        uint256 amount,
        address recipient,
        bytes calldata data
    ) external payable returns (bytes32 settlementId) {
        lastToken = token;
        lastAmount = amount;
        lastRecipient = recipient;
        lastData = data;
        settlementId = keccak256(abi.encode(obligationId, token, amount, recipient, data, block.timestamp));
    }
}

contract AtomicBridgeCoordinatorTest is Test {
    bytes32 internal constant MOCK_SETTLEMENT_MODE = keccak256("MOCK_SETTLEMENT_MODE");

    MockMintableToken internal sourceToken;
    MockMintableToken internal destinationToken;
    MockMintableToken internal bondToken;

    AtomicLiquidityVault internal vault;
    AtomicFulfillerRegistry internal registry;
    AtomicFeePolicy internal feePolicy;
    AtomicObligationEscrow internal escrow;
    AtomicSettlementRouter internal router;
    AtomicSlashingManager internal slashingManager;
    AtomicBridgeCoordinator internal coordinator;
    AtomicQuoteEngine internal quoteEngine;
    MockAtomicSettlementAdapter internal settlementAdapter;

    address internal user = address(0x1111);
    address internal fulfiller = address(0x2222);
    address internal protocolTreasury = address(0x3333);
    bytes32 internal corridorId;

    function setUp() public {
        sourceToken = new MockMintableToken("Chain 138 USD", "cUSDC", 6, address(this));
        destinationToken = new MockMintableToken("Mainnet Wrapped USD", "cWUSDC", 6, address(this));
        bondToken = new MockMintableToken("Bond USDC", "bUSDC", 6, address(this));

        vault = new AtomicLiquidityVault(address(this));
        registry = new AtomicFulfillerRegistry(address(bondToken), address(this));
        feePolicy = new AtomicFeePolicy(address(this));
        escrow = new AtomicObligationEscrow(address(this));
        router = new AtomicSettlementRouter(address(this));
        slashingManager = new AtomicSlashingManager(address(registry), address(this));
        coordinator = new AtomicBridgeCoordinator(
            address(vault),
            address(registry),
            address(escrow),
            address(router),
            address(feePolicy),
            address(slashingManager),
            protocolTreasury,
            address(this)
        );
        quoteEngine = new AtomicQuoteEngine(
            address(coordinator),
            address(vault),
            address(registry),
            address(feePolicy)
        );
        settlementAdapter = new MockAtomicSettlementAdapter();

        vault.grantRole(vault.COORDINATOR_ROLE(), address(coordinator));
        vault.grantRole(vault.RECONCILER_ROLE(), address(coordinator));
        registry.grantRole(registry.COORDINATOR_ROLE(), address(coordinator));
        registry.grantRole(registry.SLASHER_ROLE(), address(slashingManager));
        escrow.grantRole(escrow.COORDINATOR_ROLE(), address(coordinator));
        router.grantRole(router.COORDINATOR_ROLE(), address(coordinator));
        slashingManager.grantRole(slashingManager.COORDINATOR_ROLE(), address(coordinator));
        router.setAdapter(MOCK_SETTLEMENT_MODE, address(settlementAdapter));

        corridorId = coordinator.getCorridorId(138, 1, address(sourceToken), address(destinationToken));
        coordinator.configureCorridor(
            AtomicTypes.CorridorConfig({
                enabled: true,
                degraded: false,
                sourceChain: 138,
                destinationChain: 1,
                assetIn: address(sourceToken),
                assetOut: address(destinationToken),
                maxNotional: 500_000e6,
                maxReservedBps: 5_000,
                targetBuffer: 10_000e6,
                maxSettlementBacklog: 250_000e6,
                maxOracleDriftBps: 500,
                fulfilmentTimeout: 1 days,
                settlementTimeout: 2 days,
                defaultSettlementMode: MOCK_SETTLEMENT_MODE
            })
        );
        feePolicy.setCorridorPolicy(corridorId, 100, 50, 12_000, 1_000, 1 days, 2 days);
        vault.setTargetBuffer(corridorId, address(destinationToken), 10_000e6);

        registry.setFulfillerActive(fulfiller, true);
        registry.setCorridorAuthorization(fulfiller, corridorId, true);

        destinationToken.mint(address(this), 100_000e6);
        destinationToken.approve(address(vault), type(uint256).max);
        vault.fundCorridor(corridorId, address(destinationToken), 100_000e6);

        bondToken.mint(fulfiller, 100_000e6);
        vm.startPrank(fulfiller);
        bondToken.approve(address(registry), type(uint256).max);
        registry.depositBond(50_000e6);
        vm.stopPrank();

        sourceToken.mint(user, 50_000e6);
        vm.prank(user);
        sourceToken.approve(address(escrow), type(uint256).max);
    }

    function testCreateIntentReservesDestinationLiquidity() public {
        bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 1 hours);
        AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
        assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.IntentCreated));
        assertEq(obligation.sourceEscrow, 1_000e6);
        assertEq(obligation.destinationReserve, 900e6);

        AtomicTypes.CorridorLiquidityState memory state =
            vault.getCorridorLiquidityState(corridorId, address(destinationToken));
        assertEq(state.totalLiquidity, 100_000e6);
        assertEq(state.reservedLiquidity, 900e6);
        assertEq(state.freeLiquidity, 99_100e6);
    }

    function testQuoteReflectsExecutionReadyPath() public {
        AtomicTypes.AtomicQuote memory q = quoteEngine.quote(corridorId, 1_000e6, 900e6, fulfiller);
        assertEq(uint8(q.routeClass), uint8(AtomicTypes.RouteClass.ExecutionReady));
        assertTrue(q.fulfillerAuthorized);
        assertTrue(q.fulfillerBondSufficient);
        assertEq(q.protocolFee, 5e6);
        assertEq(q.fulfillerFee, 10e6);
        assertEq(q.requiredBond, 1_080e6);
    }

    function testCommitFailsWhenBondIsInsufficient() public {
        address weakFulfiller = address(0x4444);
        registry.setFulfillerActive(weakFulfiller, true);
        registry.setCorridorAuthorization(weakFulfiller, corridorId, true);
        bondToken.mint(weakFulfiller, 100e6);
        vm.startPrank(weakFulfiller);
        bondToken.approve(address(registry), type(uint256).max);
        registry.depositBond(100e6);
        vm.stopPrank();

        bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 1 hours);
        vm.prank(weakFulfiller);
        vm.expectRevert(AtomicFulfillerRegistry.InsufficientAvailableBond.selector);
        coordinator.submitCommitment(obligationId, bytes32(0));
    }

    function testSuccessfulSettlementReleasesBondAndReplenishesLiquidity() public {
        bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 1 hours);

        vm.prank(fulfiller);
        coordinator.submitCommitment(obligationId, bytes32(0));

        assertEq(destinationToken.balanceOf(user), 900e6);
        AtomicTypes.CorridorLiquidityState memory postFulfill =
            vault.getCorridorLiquidityState(corridorId, address(destinationToken));
        assertEq(postFulfill.totalLiquidity, 99_100e6);
        assertEq(postFulfill.settlementBacklog, 900e6);

        bytes memory settlementData = abi.encodePacked(bytes32(uint256(123)));
        bytes32 settlementId = coordinator.initiateSettlement(obligationId, settlementData);
        assertTrue(settlementId != bytes32(0));
        assertEq(settlementAdapter.lastToken(), address(sourceToken));
        assertEq(settlementAdapter.lastAmount(), 985e6);
        assertEq(settlementAdapter.lastRecipient(), user);
        assertEq(sourceToken.balanceOf(protocolTreasury), 5e6);
        assertEq(sourceToken.balanceOf(fulfiller), 10e6);

        destinationToken.mint(address(this), 900e6);
        destinationToken.approve(address(vault), 900e6);
        coordinator.confirmSettlement(obligationId, 900e6);

        AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
        assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.Settled));
        assertEq(registry.lockedBond(fulfiller), 0);

        AtomicTypes.CorridorLiquidityState memory postSettlement =
            vault.getCorridorLiquidityState(corridorId, address(destinationToken));
        assertEq(postSettlement.totalLiquidity, 100_000e6);
        assertEq(postSettlement.settlementBacklog, 0);
    }

    function testExpiredIntentRefundsAndReleasesReservation() public {
        uint256 payerBalanceBefore = sourceToken.balanceOf(user);
        bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 30 minutes);

        vm.warp(block.timestamp + 31 minutes);
        coordinator.refundExpiredIntent(obligationId);

        AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
        assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.Refunded));
        assertEq(sourceToken.balanceOf(user), payerBalanceBefore);

        AtomicTypes.CorridorLiquidityState memory state =
            vault.getCorridorLiquidityState(corridorId, address(destinationToken));
        assertEq(state.reservedLiquidity, 0);
    }

    function testSettlementTimeoutSlashesBondAndDegradesCorridor() public {
        bytes32 obligationId = _createIntent(1_000e6, 900e6, block.timestamp + 1 hours);
        vm.prank(fulfiller);
        coordinator.submitCommitment(obligationId, bytes32(0));
        coordinator.initiateSettlement(obligationId, abi.encodePacked(bytes32(uint256(456))));

        uint256 treasuryBondBefore = bondToken.balanceOf(protocolTreasury);
        vm.warp(block.timestamp + 3 days);
        coordinator.handleSettlementTimeout(obligationId);

        AtomicTypes.AtomicObligation memory obligation = coordinator.getObligation(obligationId);
        assertEq(uint8(obligation.status), uint8(AtomicTypes.ObligationStatus.Slashed));
        AtomicTypes.CorridorConfig memory cfg = coordinator.getCorridorConfig(corridorId);
        assertTrue(cfg.degraded);
        assertEq(bondToken.balanceOf(protocolTreasury), treasuryBondBefore + 1_080e6);
    }

    function testConcurrentReservationsDoNotOverReserveLiquidity() public {
        _createIntent(10_000e6, 40_000e6, block.timestamp + 1 hours);
        _createIntent(10_000e6, 10_000e6, block.timestamp + 1 hours);

        vm.expectRevert(AtomicBridgeCoordinator.ReservedLiquidityLimitExceeded.selector);
        _createIntent(10_000e6, 5_000e6, block.timestamp + 1 hours);
    }

    function _createIntent(uint256 amountIn, uint256 minAmountOut, uint256 deadline) internal returns (bytes32) {
        vm.prank(user);
        return coordinator.createIntent(
            AtomicBridgeCoordinator.CreateIntentParams({
                sourceChain: 138,
                destinationChain: 1,
                assetIn: address(sourceToken),
                assetOut: address(destinationToken),
                amountIn: amountIn,
                minAmountOut: minAmountOut,
                recipient: user,
                deadline: deadline,
                routeId: corridorId
            })
        );
    }
}
