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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

/**
 * @title BridgeEscrowVault
 * @notice Enhanced escrow vault for multi-rail bridging (EVM, XRPL, Fabric)
 * @dev Supports HSM-backed admin functions via EIP-712 signatures
 */
contract BridgeEscrowVault is ReentrancyGuard, Pausable, AccessControl, EIP712 {
    using SafeERC20 for IERC20;

    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    bytes32 public constant REFUND_ROLE = keccak256("REFUND_ROLE");

    enum DestinationType {
        EVM,
        XRPL,
        FABRIC
    }

    enum TransferStatus {
        INITIATED,
        DEPOSIT_CONFIRMED,
        ROUTE_SELECTED,
        EXECUTING,
        DESTINATION_SENT,
        FINALITY_CONFIRMED,
        COMPLETED,
        FAILED,
        REFUND_PENDING,
        REFUNDED
    }

    struct Transfer {
        bytes32 transferId;
        address depositor;
        address asset; // address(0) for native ETH
        uint256 amount;
        DestinationType destinationType;
        bytes destinationData; // Encoded destination address/identifier
        uint256 timestamp;
        uint256 timeout;
        TransferStatus status;
        bool refunded;
    }

    struct RefundRequest {
        bytes32 transferId;
        uint256 deadline;
        bytes hsmSignature;
    }

    // EIP-712 type hashes
    bytes32 private constant REFUND_TYPEHASH =
        keccak256("RefundRequest(bytes32 transferId,uint256 deadline)");

    mapping(bytes32 => Transfer) public transfers;
    mapping(bytes32 => bool) public processedTransferIds;
    mapping(address => uint256) public nonces;

    event Deposit(
        bytes32 indexed transferId,
        address indexed depositor,
        address indexed asset,
        uint256 amount,
        DestinationType destinationType,
        bytes destinationData,
        uint256 timestamp
    );

    event TransferStatusUpdated(
        bytes32 indexed transferId,
        TransferStatus oldStatus,
        TransferStatus newStatus
    );

    event RefundInitiated(
        bytes32 indexed transferId,
        address indexed depositor,
        uint256 amount
    );

    event RefundExecuted(
        bytes32 indexed transferId,
        address indexed depositor,
        uint256 amount
    );

    error ZeroAmount();
    error ZeroRecipient();
    error ZeroAsset();
    error TransferAlreadyProcessed();
    error TransferNotFound();
    error TransferNotRefundable();
    error InvalidTimeout();
    error InvalidSignature();
    error TransferNotTimedOut();
    error InvalidStatus();

    constructor(address admin) EIP712("BridgeEscrowVault", "1") {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(PAUSER_ROLE, admin);
        _grantRole(OPERATOR_ROLE, admin);
        _grantRole(REFUND_ROLE, admin);
    }

    /**
     * @notice Deposit native ETH for cross-chain transfer
     * @param destinationType Type of destination (EVM, XRPL, Fabric)
     * @param destinationData Encoded destination address/identifier
     * @param timeout Timeout in seconds for refund eligibility
     * @param nonce User-provided nonce for replay protection
     * @return transferId Unique transfer identifier
     */
    function depositNative(
        DestinationType destinationType,
        bytes calldata destinationData,
        uint256 timeout,
        bytes32 nonce
    ) external payable nonReentrant whenNotPaused returns (bytes32 transferId) {
        if (msg.value == 0) revert ZeroAmount();
        if (destinationData.length == 0) revert ZeroRecipient();
        if (timeout == 0) revert InvalidTimeout();

        nonces[msg.sender]++;

        transferId = _generateTransferId(
            address(0),
            msg.value,
            destinationType,
            destinationData,
            nonce
        );

        if (processedTransferIds[transferId]) revert TransferAlreadyProcessed();
        processedTransferIds[transferId] = true;

        transfers[transferId] = Transfer({
            transferId: transferId,
            depositor: msg.sender,
            asset: address(0),
            amount: msg.value,
            destinationType: destinationType,
            destinationData: destinationData,
            timestamp: block.timestamp,
            timeout: timeout,
            status: TransferStatus.INITIATED,
            refunded: false
        });

        emit Deposit(
            transferId,
            msg.sender,
            address(0),
            msg.value,
            destinationType,
            destinationData,
            block.timestamp
        );

        return transferId;
    }

    /**
     * @notice Deposit ERC-20 tokens for cross-chain transfer
     * @param token ERC-20 token address
     * @param amount Amount to deposit
     * @param destinationType Type of destination (EVM, XRPL, Fabric)
     * @param destinationData Encoded destination address/identifier
     * @param timeout Timeout in seconds for refund eligibility
     * @param nonce User-provided nonce for replay protection
     * @return transferId Unique transfer identifier
     */
    function depositERC20(
        address token,
        uint256 amount,
        DestinationType destinationType,
        bytes calldata destinationData,
        uint256 timeout,
        bytes32 nonce
    ) external nonReentrant whenNotPaused returns (bytes32 transferId) {
        if (token == address(0)) revert ZeroAsset();
        if (amount == 0) revert ZeroAmount();
        if (destinationData.length == 0) revert ZeroRecipient();
        if (timeout == 0) revert InvalidTimeout();

        nonces[msg.sender]++;

        transferId = _generateTransferId(
            token,
            amount,
            destinationType,
            destinationData,
            nonce
        );

        if (processedTransferIds[transferId]) revert TransferAlreadyProcessed();
        processedTransferIds[transferId] = true;

        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

        transfers[transferId] = Transfer({
            transferId: transferId,
            depositor: msg.sender,
            asset: token,
            amount: amount,
            destinationType: destinationType,
            destinationData: destinationData,
            timestamp: block.timestamp,
            timeout: timeout,
            status: TransferStatus.INITIATED,
            refunded: false
        });

        emit Deposit(
            transferId,
            msg.sender,
            token,
            amount,
            destinationType,
            destinationData,
            block.timestamp
        );

        return transferId;
    }

    /**
     * @notice Update transfer status (operator only)
     * @param transferId Transfer identifier
     * @param newStatus New status
     */
    function updateTransferStatus(
        bytes32 transferId,
        TransferStatus newStatus
    ) external onlyRole(OPERATOR_ROLE) {
        Transfer storage transfer = transfers[transferId];
        if (transfer.transferId == bytes32(0)) revert TransferNotFound();

        TransferStatus oldStatus = transfer.status;
        transfer.status = newStatus;

        emit TransferStatusUpdated(transferId, oldStatus, newStatus);
    }

    /**
     * @notice Initiate refund (requires HSM signature)
     * @param request Refund request with HSM signature
     * @param hsmSigner HSM signer address
     */
    function initiateRefund(
        RefundRequest calldata request,
        address hsmSigner
    ) external onlyRole(REFUND_ROLE) {
        Transfer storage transfer = transfers[request.transferId];
        if (transfer.transferId == bytes32(0)) revert TransferNotFound();
        if (transfer.refunded) revert TransferNotRefundable();
        if (block.timestamp < transfer.timestamp + transfer.timeout) {
            revert TransferNotTimedOut();
        }

        // Verify HSM signature
        bytes32 structHash = keccak256(
            abi.encode(REFUND_TYPEHASH, request.transferId, request.deadline)
        );
        bytes32 hash = _hashTypedDataV4(structHash);

        if (ECDSA.recover(hash, request.hsmSignature) != hsmSigner) {
            revert InvalidSignature();
        }

        if (block.timestamp > request.deadline) revert InvalidSignature();

        transfer.status = TransferStatus.REFUND_PENDING;
        emit RefundInitiated(request.transferId, transfer.depositor, transfer.amount);
    }

    /**
     * @notice Execute refund after initiation
     * @param transferId Transfer identifier
     */
    function executeRefund(bytes32 transferId) external nonReentrant onlyRole(REFUND_ROLE) {
        Transfer storage transfer = transfers[transferId];
        if (transfer.transferId == bytes32(0)) revert TransferNotFound();
        if (transfer.refunded) revert TransferNotRefundable();
        if (transfer.status != TransferStatus.REFUND_PENDING) revert InvalidStatus();

        transfer.refunded = true;
        transfer.status = TransferStatus.REFUNDED;

        if (transfer.asset == address(0)) {
            (bool success, ) = transfer.depositor.call{value: transfer.amount}("");
            require(success, "Refund failed");
        } else {
            IERC20(transfer.asset).safeTransfer(transfer.depositor, transfer.amount);
        }

        emit RefundExecuted(transferId, transfer.depositor, transfer.amount);
    }

    /**
     * @notice Get transfer details
     * @param transferId Transfer identifier
     * @return Transfer struct
     */
    function getTransfer(bytes32 transferId) external view returns (Transfer memory) {
        return transfers[transferId];
    }

    /**
     * @notice Check if transfer is refundable
     * @param transferId Transfer identifier
     * @return True if refundable
     */
    function isRefundable(bytes32 transferId) external view returns (bool) {
        Transfer storage transfer = transfers[transferId];
        if (transfer.transferId == bytes32(0)) return false;
        if (transfer.refunded) return false;
        return block.timestamp >= transfer.timestamp + transfer.timeout;
    }

    /**
     * @notice Generate unique transfer ID
     * @param asset Asset address
     * @param amount Amount
     * @param destinationType Destination type
     * @param destinationData Destination data
     * @param nonce User nonce
     * @return transferId Unique identifier
     */
    function _generateTransferId(
        address asset,
        uint256 amount,
        DestinationType destinationType,
        bytes calldata destinationData,
        bytes32 nonce
    ) internal view returns (bytes32) {
        return
            keccak256(
                abi.encodePacked(
                    asset,
                    amount,
                    uint8(destinationType),
                    destinationData,
                    nonce,
                    msg.sender,
                    block.timestamp,
                    block.number
                )
            );
    }

    /**
     * @notice Pause contract
     */
    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }

    /**
     * @notice Unpause contract
     */
    function unpause() external onlyRole(PAUSER_ROLE) {
        _unpause();
    }
}
