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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * @title AereStaking
 * @notice Delegated stake pool for AERE Network validators
 * @dev Token holders delegate AERE to validators and earn 8% APY (matches genesis rewardRate).
 *      Validators earn block rewards for producing blocks; a portion is shared with delegators.
 *
 * Whitepaper alignment:
 *   - 100k AERE proposal threshold for governance (used elsewhere)
 *   - 7-day voting window
 *   - 5% slashing rate for misbehaving validators
 *   - 500 AERE minimum self-stake (genesis-defined)
 */
contract AereStaking is Ownable, ReentrancyGuard {
    uint256 public constant MIN_VALIDATOR_SELF_STAKE = 500 ether;       // 500 AERE
    uint256 public constant MIN_DELEGATION = 1 ether;                    // 1 AERE
    uint256 public constant UNBONDING_PERIOD = 7 days;
    uint256 public constant ANNUAL_REWARD_RATE_BPS = 800;                // 8% APY
    uint256 public constant SLASH_RATE_BPS = 500;                        // 5%

    struct Validator {
        bool active;
        uint256 selfStake;
        uint256 totalDelegated;
        uint256 commissionBps;       // out of 10000
        uint256 lastRewardBlock;
        uint256 accumulatedRewards;
    }

    struct Delegation {
        uint256 amount;
        uint256 stakedAt;
        uint256 lastClaimBlock;
    }

    struct UnbondRequest {
        uint256 amount;
        uint256 readyAt;
    }

    mapping(address => Validator) public validators;
    mapping(address => mapping(address => Delegation)) public delegations; // validator => delegator => stake
    mapping(address => UnbondRequest[]) public unbondQueue;

    address[] public validatorList;
    uint256 public totalStaked;

    event ValidatorRegistered(address indexed validator, uint256 selfStake, uint256 commissionBps);
    event ValidatorDeactivated(address indexed validator);
    event Delegated(address indexed validator, address indexed delegator, uint256 amount);
    event UnbondInitiated(address indexed validator, address indexed delegator, uint256 amount, uint256 readyAt);
    event UnbondClaimed(address indexed delegator, uint256 amount);
    event RewardsClaimed(address indexed delegator, uint256 amount);
    event ValidatorSlashed(address indexed validator, uint256 amount, string reason);

    /**
     * @notice Register as a validator. Sender deposits self-stake (msg.value).
     * @param commissionBps Validator's cut of delegator rewards (e.g. 1000 = 10%)
     */
    function registerValidator(uint256 commissionBps) external payable nonReentrant {
        require(msg.value >= MIN_VALIDATOR_SELF_STAKE, "AereStaking: insufficient self-stake");
        require(commissionBps <= 5000, "AereStaking: max commission 50%");
        require(!validators[msg.sender].active, "AereStaking: already registered");

        validators[msg.sender] = Validator({
            active: true,
            selfStake: msg.value,
            totalDelegated: 0,
            commissionBps: commissionBps,
            lastRewardBlock: block.number,
            accumulatedRewards: 0
        });
        validatorList.push(msg.sender);
        totalStaked += msg.value;
        emit ValidatorRegistered(msg.sender, msg.value, commissionBps);
    }

    /**
     * @notice Delegate AERE to a validator. msg.value is the delegation amount.
     */
    function delegate(address validator) external payable nonReentrant {
        require(msg.value >= MIN_DELEGATION, "AereStaking: below minimum delegation");
        require(validators[validator].active, "AereStaking: validator not active");

        Delegation storage d = delegations[validator][msg.sender];
        if (d.amount == 0) {
            d.stakedAt = block.timestamp;
            d.lastClaimBlock = block.number;
        }
        d.amount += msg.value;
        validators[validator].totalDelegated += msg.value;
        totalStaked += msg.value;
        emit Delegated(validator, msg.sender, msg.value);
    }

    /**
     * @notice Initiate unbonding. Funds release after 7 days.
     */
    function unbond(address validator, uint256 amount) external nonReentrant {
        Delegation storage d = delegations[validator][msg.sender];
        require(d.amount >= amount, "AereStaking: insufficient delegation");

        d.amount -= amount;
        validators[validator].totalDelegated -= amount;
        totalStaked -= amount;

        unbondQueue[msg.sender].push(UnbondRequest({
            amount: amount,
            readyAt: block.timestamp + UNBONDING_PERIOD
        }));
        emit UnbondInitiated(validator, msg.sender, amount, block.timestamp + UNBONDING_PERIOD);
    }

    /**
     * @notice Claim all matured unbonding requests.
     */
    function claimUnbonded() external nonReentrant {
        uint256 total = 0;
        UnbondRequest[] storage queue = unbondQueue[msg.sender];
        uint256 i = 0;
        while (i < queue.length) {
            if (queue[i].readyAt <= block.timestamp) {
                total += queue[i].amount;
                queue[i] = queue[queue.length - 1];
                queue.pop();
            } else {
                i++;
            }
        }
        require(total > 0, "AereStaking: nothing to claim");
        (bool ok, ) = msg.sender.call{value: total}("");
        require(ok, "AereStaking: transfer failed");
        emit UnbondClaimed(msg.sender, total);
    }

    /**
     * @notice Pending rewards for a delegator (linear approximation: 8% APY).
     */
    function pendingRewards(address validator, address delegator) public view returns (uint256) {
        Delegation memory d = delegations[validator][delegator];
        if (d.amount == 0) return 0;
        uint256 blocksElapsed = block.number - d.lastClaimBlock;
        // 100ms blocks → 315,360,000 blocks/year. Reward per block = stake * 8% / 315360000.
        uint256 grossReward = (d.amount * ANNUAL_REWARD_RATE_BPS * blocksElapsed) / (315360000 * 10000);
        // Subtract validator commission
        uint256 commission = (grossReward * validators[validator].commissionBps) / 10000;
        return grossReward - commission;
    }

    function claimRewards(address validator) external nonReentrant {
        uint256 reward = pendingRewards(validator, msg.sender);
        require(reward > 0, "AereStaking: nothing to claim");
        delegations[validator][msg.sender].lastClaimBlock = block.number;
        // In production, rewards come from the protocol-level mining supply (2.52B AERE).
        // Until that's wired up via consensus, this contract must be funded by foundation.
        require(address(this).balance >= reward, "AereStaking: pool insufficient (needs foundation top-up)");
        (bool ok, ) = msg.sender.call{value: reward}("");
        require(ok, "AereStaking: transfer failed");
        emit RewardsClaimed(msg.sender, reward);
    }

    /**
     * @notice Slash a validator (only callable by AereConsensus or owner).
     */
    function slash(address validator, string calldata reason) external onlyOwner {
        Validator storage v = validators[validator];
        require(v.active, "AereStaking: validator not active");
        uint256 slashAmount = (v.selfStake * SLASH_RATE_BPS) / 10000;
        v.selfStake -= slashAmount;
        totalStaked -= slashAmount;
        if (v.selfStake < MIN_VALIDATOR_SELF_STAKE) {
            v.active = false;
            emit ValidatorDeactivated(validator);
        }
        emit ValidatorSlashed(validator, slashAmount, reason);
    }

    function validatorCount() external view returns (uint256) {
        return validatorList.length;
    }

    /// @notice Allow foundation to seed the reward pool
    receive() external payable {}
}
