// 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/IChainAdapter.sol";

/**
 * @title TronAdapter
 * @notice Bridge adapter for Tron using an off-chain relay/oracle path
 * @dev Keeps fulfillment ids to make relay retries idempotent.
 */
contract TronAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
    bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");

    bool public isActive;
    uint256 public minFinalityBlocks;

    mapping(bytes32 => BridgeRequest) public bridgeRequests;
    mapping(bytes32 => string) public txHashes;
    mapping(bytes32 => uint256) public finalizedBlocks;
    mapping(bytes32 => bytes32) public fulfillmentIdsByRequest;
    mapping(bytes32 => bool) public usedFulfillmentIds;
    mapping(address => uint256) public nonces;

    event TronBridgeInitiated(
        bytes32 indexed requestId,
        address indexed sender,
        address indexed token,
        uint256 amount,
        string destination
    );

    event TronBridgeConfirmed(
        bytes32 indexed requestId,
        string txHash,
        uint256 finalizedBlock,
        bytes32 indexed fulfillmentId
    );

    event TronBridgeMarkedFailed(bytes32 indexed requestId, string reason);

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(BRIDGE_OPERATOR_ROLE, admin);
        _grantRole(ORACLE_ROLE, admin);
        isActive = true;
        minFinalityBlocks = 20;
    }

    function getChainType() external pure override returns (string memory) {
        return "Tron";
    }

    function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
        return (0, "Tron-Mainnet");
    }

    function validateDestination(bytes calldata destination) external pure override returns (bool) {
        uint256 length = destination.length;
        return length >= 34 && length <= 36;
    }

    function bridge(
        address token,
        uint256 amount,
        bytes calldata destination,
        bytes calldata recipient
    ) external payable override nonReentrant returns (bytes32 requestId) {
        require(isActive, "Adapter inactive");
        require(amount > 0, "Zero amount");
        require(this.validateDestination(destination), "Invalid Tron destination");

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

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

        bridgeRequests[requestId] = BridgeRequest({
            sender: msg.sender,
            token: token,
            amount: amount,
            destinationData: destination,
            requestId: requestId,
            status: BridgeStatus.Locked,
            createdAt: block.timestamp,
            completedAt: 0
        });

        emit TronBridgeInitiated(requestId, msg.sender, token, amount, string(destination));
        return requestId;
    }

    function confirmTransaction(
        bytes32 requestId,
        string calldata txHash,
        uint256 finalizedBlock,
        bytes32 fulfillmentId
    ) external onlyRole(ORACLE_ROLE) {
        BridgeRequest storage request = bridgeRequests[requestId];
        require(request.status == BridgeStatus.Locked, "Invalid status");
        require(bytes(txHash).length > 0, "Missing Tron tx hash");
        require(finalizedBlock >= minFinalityBlocks, "Insufficient finality");
        require(fulfillmentId != bytes32(0), "Missing fulfillment id");
        require(!usedFulfillmentIds[fulfillmentId], "Fulfillment already used");

        usedFulfillmentIds[fulfillmentId] = true;
        fulfillmentIdsByRequest[requestId] = fulfillmentId;
        txHashes[requestId] = txHash;
        finalizedBlocks[requestId] = finalizedBlock;
        request.status = BridgeStatus.Confirmed;
        request.completedAt = block.timestamp;

        emit TronBridgeConfirmed(requestId, txHash, finalizedBlock, fulfillmentId);
    }

    function markFailed(bytes32 requestId, string calldata reason) external onlyRole(ORACLE_ROLE) {
        BridgeRequest storage request = bridgeRequests[requestId];
        require(request.status == BridgeStatus.Locked, "Invalid status");

        request.status = BridgeStatus.Failed;
        request.completedAt = block.timestamp;

        emit TronBridgeMarkedFailed(requestId, reason);
    }

    function getBridgeStatus(bytes32 requestId) external view override returns (BridgeRequest memory) {
        return bridgeRequests[requestId];
    }

    function cancelBridge(bytes32 requestId) external override nonReentrant returns (bool) {
        BridgeRequest storage request = bridgeRequests[requestId];
        require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
        require(msg.sender == request.sender, "Not request sender");

        if (request.token == address(0)) {
            payable(request.sender).transfer(request.amount);
        } else {
            IERC20(request.token).safeTransfer(request.sender, request.amount);
        }

        request.status = BridgeStatus.Cancelled;
        request.completedAt = block.timestamp;
        return true;
    }

    function estimateFee(
        address,
        uint256,
        bytes calldata
    ) external pure override returns (uint256 fee) {
        return 1000000000000000;
    }

    function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
        isActive = _isActive;
    }

    function setMinFinalityBlocks(uint256 blocks_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(blocks_ > 0, "Zero finality");
        minFinalityBlocks = blocks_;
    }
}
