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

import {Test} from "forge-std/Test.sol";
import "../../../contracts/bridge/UniversalCCIPBridge.sol";
import "../../../contracts/bridge/adapters/evm/EVMAdapter.sol";
import "../../../contracts/bridge/adapters/evm/XDCAdapter.sol";
import "../../../contracts/bridge/adapters/non-evm/XRPLAdapter.sol";
import "../../../contracts/registry/ChainRegistry.sol";
import "../../../contracts/registry/UniversalAssetRegistry.sol";
import "../../../contracts/ccip/IRouterClient.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/** Mock CCIP router for tests; production uses existing Chainlink CCIP routers via CCIP_ROUTER in .env */
contract MockCCIPRouter is IRouterClient {
    function getFee(uint64, EVM2AnyMessage memory) external pure override returns (uint256) {
        return 0;
    }
    function ccipSend(uint64, EVM2AnyMessage memory) external payable override returns (bytes32 messageId, uint256) {
        messageId = keccak256(abi.encodePacked(block.timestamp, msg.sender));
        return (messageId, 0);
    }
    function getSupportedTokens(uint64) external pure override returns (address[] memory) {
        return new address[](0);
    }
}

contract MockTestToken is ERC20 {
    constructor() ERC20("Test Token", "TEST") {
        _mint(msg.sender, 1000000 ether);
    }
}

/**
 * @title MultiChainBridgeIntegrationTest
 * @notice Integration tests for multi-chain bridge functionality
 * @dev Tests cross-chain transfers, adapter functionality, and multi-chain scenarios.
 * Asset registration uses UniversalAssetRegistry proposeAsset → voteOnProposal → executeProposal flow.
 */
contract MultiChainBridgeIntegrationTest is Test {
    UniversalCCIPBridge public bridge;
    ChainRegistry public chainRegistry;
    UniversalAssetRegistry public assetRegistry;

    // Adapters
    EVMAdapter public polygonAdapter;
    EVMAdapter public arbitrumAdapter;
    XDCAdapter public xdcAdapter;
    XRPLAdapter public xrplAdapter;

    // Test tokens
    MockTestToken public testToken;

    address public admin = address(0x1);
    address public user = address(0x2);
    address public recipient = address(0x3);

    // Chain IDs
    uint256 public constant POLYGON_CHAIN_ID = 137;
    uint256 public constant ARBITRUM_CHAIN_ID = 42161;
    uint256 public constant XDC_CHAIN_ID = 50;

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

        // Deploy Universal Asset Registry (upgradeable: use proxy)
        UniversalAssetRegistry regImpl = new UniversalAssetRegistry();
        bytes memory regInitData = abi.encodeWithSelector(UniversalAssetRegistry.initialize.selector, admin);
        ERC1967Proxy regProxy = new ERC1967Proxy(address(regImpl), regInitData);
        assetRegistry = UniversalAssetRegistry(address(regProxy));

        // Add admin as validator so quorum can be met
        assetRegistry.addValidator(admin);

        // Deploy Chain Registry (upgradeable: use proxy)
        ChainRegistry chainImpl = new ChainRegistry();
        bytes memory chainInitData = abi.encodeWithSelector(ChainRegistry.initialize.selector, admin);
        ERC1967Proxy chainProxy = new ERC1967Proxy(address(chainImpl), chainInitData);
        chainRegistry = ChainRegistry(address(chainProxy));

        // Deploy Universal CCIP Bridge (upgradeable: use proxy)
        // Use mock CCIP router in tests; production uses existing Chainlink routers via CCIP_ROUTER in .env
        MockCCIPRouter mockRouter = new MockCCIPRouter();
        UniversalCCIPBridge bridgeImpl = new UniversalCCIPBridge();
        bytes memory bridgeInitData = abi.encodeWithSelector(
            UniversalCCIPBridge.initialize.selector,
            address(assetRegistry),
            address(mockRouter),
            admin
        );
        ERC1967Proxy bridgeProxy = new ERC1967Proxy(address(bridgeImpl), bridgeInitData);
        bridge = UniversalCCIPBridge(payable(address(bridgeProxy)));

        // Deploy test token
        testToken = new MockTestToken();
        require(testToken.transfer(user, 1000000e18), "transfer failed");

        // Register test token via propose → vote → timelock → execute
        bytes32 proposalId = assetRegistry.proposeAsset(
            address(testToken),
            UniversalAssetRegistry.AssetType.ERC20Standard,
            UniversalAssetRegistry.ComplianceLevel.Public,
            "Test Token",
            "TEST",
            18,
            "",
            50,  // volatilityScore
            0,   // minBridge
            1e24 // maxBridge
        );
        assetRegistry.voteOnProposal(proposalId, true);
        UniversalAssetRegistry.PendingAssetProposal memory p = assetRegistry.getProposal(proposalId);
        vm.warp(p.executeAfter + 1);
        assetRegistry.executeProposal(proposalId);

        // Enable bridge destinations for test token (mock receiver for tests)
        address mockReceiver = address(0xbeef);
        // forge-lint: disable-next-line(unsafe-typecast) -- chain IDs fit in uint64
        bridge.addDestination(address(testToken), uint64(POLYGON_CHAIN_ID), mockReceiver);
        // forge-lint: disable-next-line(unsafe-typecast) -- chain IDs fit in uint64
        bridge.addDestination(address(testToken), uint64(ARBITRUM_CHAIN_ID), mockReceiver);

        // Deploy adapters
        polygonAdapter = new EVMAdapter(admin, payable(address(bridge)), POLYGON_CHAIN_ID, "Polygon");
        arbitrumAdapter = new EVMAdapter(admin, payable(address(bridge)), ARBITRUM_CHAIN_ID, "Arbitrum");
        xdcAdapter = new XDCAdapter(admin, payable(address(bridge)));
        xrplAdapter = new XRPLAdapter(admin);

        // Register chains
        chainRegistry.registerEVMChain(
            POLYGON_CHAIN_ID,
            address(polygonAdapter),
            "https://polygonscan.com",
            12,
            2,
            ""
        );

        chainRegistry.registerEVMChain(
            ARBITRUM_CHAIN_ID,
            address(arbitrumAdapter),
            "https://arbiscan.io",
            12,
            2,
            ""
        );

        chainRegistry.registerEVMChain(
            XDC_CHAIN_ID,
            address(xdcAdapter),
            "https://explorer.xdc.network",
            12,
            2,
            ""
        );

        chainRegistry.registerNonEVMChain(
            "XRPL-Mainnet",
            ChainRegistry.ChainType.XRPL,
            address(xrplAdapter),
            "https://xrpscan.com",
            1,
            4,
            true,
            ""
        );

        vm.stopPrank();
    }

    function testEVMToEVMBridge() public {
        vm.startPrank(user);

        uint256 amount = 1000e18;
        testToken.approve(address(polygonAdapter), amount);

        // Bridge to Polygon
        bytes memory destination = abi.encodePacked(recipient);
        bytes memory recipientData = abi.encode(recipient);

        bytes32 requestId = polygonAdapter.bridge(
            address(testToken),
            amount,
            destination,
            recipientData
        );

        assertNotEq(requestId, bytes32(0), "Request ID should not be zero");

        // Check bridge status
        IChainAdapter.BridgeRequest memory request = polygonAdapter.getBridgeStatus(requestId);
        assertTrue(request.status == IChainAdapter.BridgeStatus.Locked, "Status should be Locked");
        assertEq(request.amount, amount, "Amount should match");
        assertEq(request.sender, user, "Sender should match");

        vm.stopPrank();
    }

    function testXDCAddressConversion() public view {
        // Round-trip using adapter output so format/length (43 chars: "xdc" + 40 hex) is correct
        address ethAddr = address(0x742d35Cc6634C0532925a3b844Bc9e7595f0bE);
        string memory xdcAddr = xdcAdapter.convertEthToXdc(ethAddr);
        assertEq(bytes(xdcAddr).length, 43, "XDC string should be 43 chars");
        address backToEth = xdcAdapter.convertXdcToEth(xdcAddr);
        assertEq(backToEth, ethAddr, "Round-trip XDC conversion should match");
    }

    function testXRPLBridge() public {
        vm.startPrank(user);

        uint256 amount = 1000e18;
        testToken.approve(address(xrplAdapter), amount);

        // Bridge to XRPL (adapter expects raw string bytes for destination; pass tag as ABI-encoded or skip so length < 4)
        string memory xrplDestination = "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH";
        bytes memory destination = bytes(xrplDestination);
        bytes memory recipientData = ""; // no destination tag; avoid abi.decode of raw string in adapter

        bytes32 requestId = xrplAdapter.bridge(
            address(testToken),
            amount,
            destination,
            recipientData
        );

        assertNotEq(requestId, bytes32(0), "Request ID should not be zero");

        // Simulate oracle confirmation
        vm.stopPrank();
        vm.prank(admin);
        xrplAdapter.confirmXRPLTransaction(
            requestId,
            keccak256("xrpl-tx-hash"),
            12345
        );

        IChainAdapter.BridgeRequest memory request = xrplAdapter.getBridgeStatus(requestId);
        assertTrue(request.status == IChainAdapter.BridgeStatus.Confirmed, "Status should be Confirmed");

        vm.stopPrank();
    }

    function testMultiChainRouting() public {
        // Test that chain registry correctly routes to appropriate adapter
        ChainRegistry.ChainMetadata memory polygonMeta = chainRegistry.getEVMChain(POLYGON_CHAIN_ID);
        assertEq(polygonMeta.adapter, address(polygonAdapter), "Should return Polygon adapter");
        assertTrue(polygonMeta.isActive, "Chain should be active");

        ChainRegistry.ChainMetadata memory arbitrumMeta = chainRegistry.getEVMChain(ARBITRUM_CHAIN_ID);
        assertEq(arbitrumMeta.adapter, address(arbitrumAdapter), "Should return Arbitrum adapter");
        assertTrue(arbitrumMeta.isActive, "Chain should be active");
    }

    function testAdapterValidation() public {
        // Test destination validation
        bytes memory validEvm = abi.encodePacked(recipient);
        assertTrue(polygonAdapter.validateDestination(validEvm), "Valid EVM address should pass");

        bytes memory invalidEvm = abi.encodePacked(address(0));
        assertFalse(polygonAdapter.validateDestination(invalidEvm), "Invalid address should fail");

        // Test XRPL validation (adapter expects raw string bytes, not ABI-encoded)
        string memory validXrpl = "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH";
        assertTrue(xrplAdapter.validateDestination(bytes(validXrpl)), "Valid XRPL address should pass");
    }

    function testFeeEstimation() public {
        bytes memory destination = abi.encodePacked(recipient);

        uint256 polygonFee = polygonAdapter.estimateFee(address(testToken), 1000e18, destination);
        assertGt(polygonFee, 0, "Polygon fee should be greater than zero");

        uint256 xrplFee = xrplAdapter.estimateFee(address(testToken), 1000e18, destination);
        assertGt(xrplFee, 0, "XRPL fee should be greater than zero");
    }

    function testCancelBridge() public {
        vm.startPrank(user);

        uint256 amount = 1000e18;
        testToken.approve(address(polygonAdapter), amount);

        bytes memory destination = abi.encodePacked(recipient);
        bytes memory recipientData = abi.encode(recipient);

        bytes32 requestId = polygonAdapter.bridge(
            address(testToken),
            amount,
            destination,
            recipientData
        );

        // Tokens were pulled to the universal bridge; adapter.cancelBridge() refunds from adapter balance.
        // Simulate bridge not having consumed tokens yet by sending them back to the adapter.
        vm.stopPrank();
        vm.prank(address(bridge));
        require(testToken.transfer(address(polygonAdapter), amount), "transfer failed");
        vm.startPrank(user);

        // Cancel bridge (adapter now has tokens and can refund user)
        bool cancelled = polygonAdapter.cancelBridge(requestId);
        assertTrue(cancelled, "Bridge should be cancelled");

        IChainAdapter.BridgeRequest memory request = polygonAdapter.getBridgeStatus(requestId);
        assertTrue(request.status == IChainAdapter.BridgeStatus.Cancelled, "Status should be Cancelled");

        vm.stopPrank();
    }
}
