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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/IAlltraTransport.sol";

/**
 * @title AlltraCustomBridge
 * @notice Custom transport for 138 <-> ALL Mainnet (651940). Locks tokens and emits event; no CCIP.
 * @dev Deploy at same address on 138 and 651940 via CREATE2. On 138: lock + emit LockForAlltra.
 *      Off-chain relayer or contract on 651940 completes mint/unlock. On 651940: implement
 *      unlockOrMint (called by relayer or same contract) to complete the flow.
 */
contract AlltraCustomBridge is IAlltraTransport, AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
    uint256 public constant ALL_MAINNET_CHAIN_ID = 651940;

    mapping(bytes32 => LockRecord) public locks;
    mapping(bytes32 => bool) public releasedOnAlltra; // on 651940: prevent double release
    mapping(address => uint256) public nonces;
    bool private _hasRelayer;

    struct LockRecord {
        address sender;
        address token;
        uint256 amount;
        address recipient;
        uint256 createdAt;
        bool released;
    }

    event LockForAlltra(
        bytes32 indexed requestId,
        address indexed sender,
        address indexed token,
        uint256 amount,
        address recipient,
        uint256 sourceChainId
    );

    event UnlockOnAlltra(
        bytes32 indexed requestId,
        address indexed recipient,
        address indexed token,
        uint256 amount
    );

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(RELAYER_ROLE, admin);
        _hasRelayer = true;
    }

    /**
     * @notice Lock tokens and emit event for relay to ALL Mainnet. Does not use CCIP.
     */
    function lockAndRelay(
        address token,
        uint256 amount,
        address recipient
    ) external payable override nonReentrant returns (bytes32 requestId) {
        require(recipient != address(0), "zero recipient");
        require(amount > 0, "zero amount");

        requestId = keccak256(abi.encodePacked(
            msg.sender,
            token,
            amount,
            recipient,
            nonces[msg.sender]++,
            block.chainid,
            block.timestamp
        ));

        if (token == address(0)) {
            require(msg.value >= amount, "insufficient value");
        } else {
            IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        }

        locks[requestId] = LockRecord({
            sender: msg.sender,
            token: token,
            amount: amount,
            recipient: recipient,
            createdAt: block.timestamp,
            released: false
        });

        emit LockForAlltra(requestId, msg.sender, token, amount, recipient, block.chainid);
        return requestId;
    }

    function isConfigured() external view override returns (bool) {
        return _hasRelayer;
    }

    /**
     * @notice On ALL Mainnet (651940): release tokens to recipient after relay proof.
     * @dev Only RELAYER_ROLE; in production, verify merkle proof or signature from source chain.
     *      Uses releasedOnAlltra[requestId] to prevent double release on this chain.
     */
    function releaseOnAlltra(
        bytes32 requestId,
        address token,
        uint256 amount,
        address recipient
    ) external onlyRole(RELAYER_ROLE) nonReentrant {
        require(!releasedOnAlltra[requestId], "already released");
        releasedOnAlltra[requestId] = true;

        if (token == address(0)) {
            (bool sent,) = payable(recipient).call{value: amount}("");
            require(sent, "transfer failed");
        } else {
            IERC20(token).safeTransfer(recipient, amount);
        }

        emit UnlockOnAlltra(requestId, recipient, token, amount);
    }

    receive() external payable {}
}
