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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

/**
 * @title AereBridge
 * @notice Federated lock-and-release bridge for native AERE between AERE Network
 *         and external EVM chains (Ethereum, BSC, Polygon, etc — 75+ supported).
 * @dev Lock side: users deposit AERE here; off-chain federation observes the
 *      Locked event and mints wrapped-AERE on the destination chain.
 *      Release side: the federation attests to a destination-chain burn by signing
 *      (nonce, recipient, amount, srcChainId). Once `threshold` signatures from
 *      authorized signers are presented, the bridge releases AERE on this chain.
 *
 *      Replay protection: each (srcChainId, nonce) pair can only be released once.
 *      Each signer can only contribute one signature per release (enforced via
 *      sorted-address requirement).
 */
contract AereBridge is Ownable, ReentrancyGuard, Pausable {
    using ECDSA for bytes32;

    uint256 public constant MIN_DEPOSIT = 1 ether;       // 1 AERE
    uint256 public constant MAX_SIGNERS = 21;

    address[] public signers;
    mapping(address => bool) public isSigner;
    uint256 public threshold;

    uint256 public depositNonce;
    mapping(uint256 => mapping(uint256 => bool)) public processed; // srcChainId => srcNonce => done

    event SignerAdded(address indexed signer);
    event SignerRemoved(address indexed signer);
    event ThresholdUpdated(uint256 newThreshold);
    event Locked(uint256 indexed nonce, address indexed sender, uint256 indexed dstChainId, address dstRecipient, uint256 amount);
    event Released(uint256 indexed srcChainId, uint256 indexed srcNonce, address indexed recipient, uint256 amount);

    constructor(address[] memory initialSigners, uint256 _threshold) {
        require(initialSigners.length > 0 && initialSigners.length <= MAX_SIGNERS, "signers");
        require(_threshold > 0 && _threshold <= initialSigners.length, "threshold");
        for (uint256 i = 0; i < initialSigners.length; i++) {
            address s = initialSigners[i];
            require(s != address(0) && !isSigner[s], "bad signer");
            isSigner[s] = true;
            signers.push(s);
            emit SignerAdded(s);
        }
        threshold = _threshold;
        emit ThresholdUpdated(_threshold);
    }

    // ── Lock (outbound) ─────────────────────────────────────────
    function lock(uint256 dstChainId, address dstRecipient) external payable whenNotPaused nonReentrant {
        require(msg.value >= MIN_DEPOSIT, "min");
        require(dstRecipient != address(0), "recipient");
        uint256 n = ++depositNonce;
        emit Locked(n, msg.sender, dstChainId, dstRecipient, msg.value);
    }

    // ── Release (inbound) ───────────────────────────────────────
    /**
     * @param srcChainId   chain ID of the source chain where the burn occurred
     * @param srcNonce     bridge-issued nonce on the source chain (replay key)
     * @param recipient    AERE Network address to receive funds
     * @param amount       amount of AERE to release
     * @param signatures   array of ECDSA signatures over the canonical message,
     *                     ordered by signer address ascending (deduplication)
     */
    function release(
        uint256 srcChainId,
        uint256 srcNonce,
        address recipient,
        uint256 amount,
        bytes[] calldata signatures
    ) external nonReentrant whenNotPaused {
        require(!processed[srcChainId][srcNonce], "processed");
        require(signatures.length >= threshold, "threshold");
        require(amount > 0 && recipient != address(0), "params");
        require(address(this).balance >= amount, "liquidity");

        bytes32 message = keccak256(
            abi.encodePacked(
                address(this),
                block.chainid,
                srcChainId,
                srcNonce,
                recipient,
                amount
            )
        ).toEthSignedMessageHash();

        address last = address(0);
        for (uint256 i = 0; i < signatures.length; i++) {
            address recovered = message.recover(signatures[i]);
            require(isSigner[recovered], "not signer");
            require(recovered > last, "unsorted/dup");
            last = recovered;
        }

        processed[srcChainId][srcNonce] = true;
        (bool ok, ) = recipient.call{value: amount}("");
        require(ok, "transfer");
        emit Released(srcChainId, srcNonce, recipient, amount);
    }

    // ── Federation management ───────────────────────────────────
    function addSigner(address s) external onlyOwner {
        require(s != address(0) && !isSigner[s], "bad");
        require(signers.length < MAX_SIGNERS, "max");
        isSigner[s] = true;
        signers.push(s);
        emit SignerAdded(s);
    }

    function removeSigner(address s) external onlyOwner {
        require(isSigner[s], "not signer");
        isSigner[s] = false;
        for (uint256 i = 0; i < signers.length; i++) {
            if (signers[i] == s) {
                signers[i] = signers[signers.length - 1];
                signers.pop();
                break;
            }
        }
        if (threshold > signers.length) {
            threshold = signers.length;
            emit ThresholdUpdated(threshold);
        }
        emit SignerRemoved(s);
    }

    function setThreshold(uint256 newThreshold) external onlyOwner {
        require(newThreshold > 0 && newThreshold <= signers.length, "range");
        threshold = newThreshold;
        emit ThresholdUpdated(newThreshold);
    }

    function signerCount() external view returns (uint256) { return signers.length; }

    // ── Emergency ───────────────────────────────────────────────
    function pause() external onlyOwner { _pause(); }
    function unpause() external onlyOwner { _unpause(); }

    /// @notice Allow the foundation to seed liquidity for releases.
    receive() external payable {}
}
