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

/**
 * @title TransactionMirror
 * @notice Mirrors Chain-138 transactions to Ethereum Mainnet for Etherscan visibility
 * @dev Logs all Chain-138 transactions as events on Mainnet, making them searchable
 *      and viewable on Etherscan. This provides transparency and auditability.
 */
contract TransactionMirror {
    address public admin;
    bool public paused;

    // Chain-138 chain ID
    uint64 public constant CHAIN_138 = 138;

    // Maximum batch size to prevent gas limit issues
    uint256 public constant MAX_BATCH_SIZE = 100;

    // Transaction structure
    struct MirroredTransaction {
        bytes32 txHash;                 // Chain-138 transaction hash
        address from;                    // Sender address
        address to;                      // Recipient address (or contract)
        uint256 value;                   // Value transferred
        uint256 blockNumber;             // Chain-138 block number
        uint256 blockTimestamp;          // Block timestamp
        uint256 gasUsed;                 // Gas used
        bool success;                    // Transaction success status
        bytes data;                      // Transaction data (if any)
        bytes32 indexedHash;             // Indexed hash for searchability
    }

    // Mapping: txHash => MirroredTransaction
    mapping(bytes32 => MirroredTransaction) public transactions;

    // Array of all mirrored transaction hashes
    bytes32[] public mirroredTxHashes;

    // Mapping: txHash => bool (replay protection)
    mapping(bytes32 => bool) public processed;

    // Events (indexed for Etherscan searchability)
    event TransactionMirrored(
        bytes32 indexed txHash,
        address indexed from,
        address indexed to,
        uint256 value,
        uint256 blockNumber,
        uint256 blockTimestamp,
        uint256 gasUsed,
        bool success
    );

    event BatchTransactionsMirrored(
        uint256 count,
        uint256 startBlock,
        uint256 endBlock
    );

    event AdminChanged(address indexed newAdmin);
    event Paused();
    event Unpaused();

    modifier onlyAdmin() {
        require(msg.sender == admin, "only admin");
        _;
    }

    modifier whenNotPaused() {
        require(!paused, "paused");
        _;
    }

    constructor(address _admin) {
        require(_admin != address(0), "zero admin");
        admin = _admin;
    }

    /**
     * @notice Mirror a single Chain-138 transaction to Mainnet
     * @param txHash Chain-138 transaction hash
     * @param from Sender address
     * @param to Recipient address
     * @param value Value transferred
     * @param blockNumber Chain-138 block number
     * @param blockTimestamp Block timestamp
     * @param gasUsed Gas used
     * @param success Transaction success status
     * @param data Transaction data (optional)
     */
    function mirrorTransaction(
        bytes32 txHash,
        address from,
        address to,
        uint256 value,
        uint256 blockNumber,
        uint256 blockTimestamp,
        uint256 gasUsed,
        bool success,
        bytes calldata data
    ) external onlyAdmin whenNotPaused {
        require(txHash != bytes32(0), "invalid hash");
        require(!processed[txHash], "already mirrored");

        bytes32 indexedHash = keccak256(abi.encodePacked(CHAIN_138, txHash));

        transactions[txHash] = MirroredTransaction({
            txHash: txHash,
            from: from,
            to: to,
            value: value,
            blockNumber: blockNumber,
            blockTimestamp: blockTimestamp,
            gasUsed: gasUsed,
            success: success,
            data: data,
            indexedHash: indexedHash
        });

        mirroredTxHashes.push(txHash);
        processed[txHash] = true;

        emit TransactionMirrored(
            txHash,
            from,
            to,
            value,
            blockNumber,
            blockTimestamp,
            gasUsed,
            success
        );
    }

    /**
     * @notice Mirror multiple Chain-138 transactions in a batch
     * @param txHashes Array of transaction hashes
     * @param froms Array of sender addresses
     * @param tos Array of recipient addresses
     * @param values Array of values
     * @param blockNumbers Array of block numbers
     * @param blockTimestamps Array of timestamps
     * @param gasUseds Array of gas used
     * @param successes Array of success statuses
     * @param datas Array of transaction data
     */
    function mirrorBatchTransactions(
        bytes32[] calldata txHashes,
        address[] calldata froms,
        address[] calldata tos,
        uint256[] calldata values,
        uint256[] calldata blockNumbers,
        uint256[] calldata blockTimestamps,
        uint256[] calldata gasUseds,
        bool[] calldata successes,
        bytes[] calldata datas
    ) external onlyAdmin whenNotPaused {
        uint256 count = txHashes.length;
        require(count > 0, "empty batch");
        require(count <= MAX_BATCH_SIZE, "batch too large");
        require(
            count == froms.length &&
            count == tos.length &&
            count == values.length &&
            count == blockNumbers.length &&
            count == blockTimestamps.length &&
            count == gasUseds.length &&
            count == successes.length &&
            count == datas.length,
            "array length mismatch"
        );

        uint256 startBlock = blockNumbers[0];
        uint256 endBlock = blockNumbers[count - 1];

        // Process transactions in batches to avoid stack too deep
        for (uint256 i = 0; i < count; i++) {
            bytes32 txHash = txHashes[i];
            require(txHash != bytes32(0), "invalid hash");
            require(!processed[txHash], "already mirrored");

            bytes32 indexedHash = keccak256(abi.encodePacked(CHAIN_138, txHash));

            transactions[txHash] = MirroredTransaction({
                txHash: txHash,
                from: froms[i],
                to: tos[i],
                value: values[i],
                blockNumber: blockNumbers[i],
                blockTimestamp: blockTimestamps[i],
                gasUsed: gasUseds[i],
                success: successes[i],
                data: datas[i],
                indexedHash: indexedHash
            });

            mirroredTxHashes.push(txHash);
            processed[txHash] = true;

            emit TransactionMirrored(
                txHash,
                froms[i],
                tos[i],
                values[i],
                blockNumbers[i],
                blockTimestamps[i],
                gasUseds[i],
                successes[i]
            );
        }

        emit BatchTransactionsMirrored(count, startBlock, endBlock);
    }


    /**
     * @notice Get mirrored transaction details
     * @param txHash Chain-138 transaction hash
     * @return mirroredTx Mirrored transaction structure
     */
    function getTransaction(bytes32 txHash) external view returns (MirroredTransaction memory mirroredTx) {
        require(processed[txHash], "not mirrored");
        return transactions[txHash];
    }

    /**
     * @notice Check if a transaction is mirrored
     * @param txHash Chain-138 transaction hash
     * @return true if mirrored
     */
    function isMirrored(bytes32 txHash) external view returns (bool) {
        return processed[txHash];
    }

    /**
     * @notice Get total number of mirrored transactions
     * @return count Number of mirrored transactions
     */
    function getMirroredTransactionCount() external view returns (uint256) {
        return mirroredTxHashes.length;
    }

    /**
     * @notice Get mirrored transaction hash at index
     * @param index Index in mirroredTxHashes array
     * @return txHash Transaction hash
     */
    function getMirroredTransaction(uint256 index) external view returns (bytes32) {
        require(index < mirroredTxHashes.length, "out of bounds");
        return mirroredTxHashes[index];
    }

    /**
     * @notice Admin functions
     */
    function setAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "zero admin");
        admin = newAdmin;
        emit AdminChanged(newAdmin);
    }

    function pause() external onlyAdmin {
        paused = true;
        emit Paused();
    }

    function unpause() external onlyAdmin {
        paused = false;
        emit Unpaused();
    }
}

