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

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

/**
 * @dev Minimal view interface into the already-deployed AereStaking contract.
 *      AereStaking exposes `validatorList`, `validatorCount`, `delegations` and
 *      `totalStaked` as public state — no change to AereStaking is required.
 */
interface IAereStaking {
    function validatorCount() external view returns (uint256);
    function validatorList(uint256 index) external view returns (address);
    function delegations(address validator, address delegator)
        external view returns (uint256 amount, uint256 stakedAt, uint256 lastClaimBlock);
    function totalStaked() external view returns (uint256);
}

/**
 * @title AereGovernanceStaked
 * @notice On-chain governance for AERE Network.
 *
 * Voting power = the total AERE a holder has STAKED via AereStaking, summed
 * across every validator they delegate to. "Stake to vote": only committed
 * holders govern, and the 7-day unbonding period makes vote-borrowing
 * impractical. Quorum is measured against total AERE staked network-wide.
 *
 * Replaces the earlier AereGovernance, which read voting power from an ERC-20
 * balance (only counted wrapped AERE) and depended on a getCirculatingSupply()
 * method the wrapped token does not implement.
 */
contract AereGovernanceStaked is Ownable, ReentrancyGuard {
    IAereStaking public immutable staking;

    enum ProposalStatus { Active, Passed, Failed, Executed, Cancelled }
    enum VoteType { Yes, No, Abstain }

    struct Proposal {
        uint256 id;
        string title;
        string description;
        address proposer;
        uint256 startTime;
        uint256 endTime;
        uint256 yesVotes;
        uint256 noVotes;
        uint256 abstainVotes;
        ProposalStatus status;
        uint256 executionTime;
        bytes executionData;
    }

    struct Vote {
        VoteType voteType;
        uint256 votingPower;
        bool hasVoted;
    }

    mapping(uint256 => Proposal) public proposals;
    mapping(uint256 => mapping(address => Vote)) public votes;

    uint256 public proposalCount;
    uint256 public constant VOTING_PERIOD = 7 days;
    uint256 public constant EXECUTION_DELAY = 2 days;
    uint256 public constant PROPOSAL_THRESHOLD = 100000 ether; // 100k staked AERE
    uint256 public constant QUORUM_PERCENTAGE = 20;            // 20% of total staked

    event ProposalCreated(uint256 indexed proposalId, address indexed proposer, string title);
    event VoteCast(uint256 indexed proposalId, address indexed voter, VoteType voteType, uint256 votingPower);
    event ProposalFinalized(uint256 indexed proposalId, ProposalStatus status);
    event ProposalExecuted(uint256 indexed proposalId);
    event ProposalCancelled(uint256 indexed proposalId);

    constructor(address _staking) {
        require(_staking != address(0), "staking=0");
        staking = IAereStaking(_staking);
    }

    /**
     * @notice Voting power of an account = total AERE it has delegated across
     *         all validators in AereStaking.
     */
    function votingPowerOf(address account) public view returns (uint256 total) {
        uint256 n = staking.validatorCount();
        for (uint256 i = 0; i < n; i++) {
            address v = staking.validatorList(i);
            (uint256 amount, , ) = staking.delegations(v, account);
            total += amount;
        }
    }

    function createProposal(
        string memory title,
        string memory description,
        bytes memory executionData
    ) external returns (uint256) {
        require(votingPowerOf(msg.sender) >= PROPOSAL_THRESHOLD, "Insufficient staked AERE to propose");

        proposalCount++;
        uint256 proposalId = proposalCount;
        proposals[proposalId] = Proposal({
            id: proposalId,
            title: title,
            description: description,
            proposer: msg.sender,
            startTime: block.timestamp,
            endTime: block.timestamp + VOTING_PERIOD,
            yesVotes: 0,
            noVotes: 0,
            abstainVotes: 0,
            status: ProposalStatus.Active,
            executionTime: 0,
            executionData: executionData
        });

        emit ProposalCreated(proposalId, msg.sender, title);
        return proposalId;
    }

    function castVote(uint256 proposalId, VoteType voteType) external {
        Proposal storage proposal = proposals[proposalId];
        require(proposal.status == ProposalStatus.Active, "Proposal not active");
        require(block.timestamp <= proposal.endTime, "Voting period ended");
        require(!votes[proposalId][msg.sender].hasVoted, "Already voted");

        uint256 voterPower = votingPowerOf(msg.sender);
        require(voterPower > 0, "No voting power - stake AERE first");

        votes[proposalId][msg.sender] = Vote({
            voteType: voteType,
            votingPower: voterPower,
            hasVoted: true
        });

        if (voteType == VoteType.Yes) {
            proposal.yesVotes += voterPower;
        } else if (voteType == VoteType.No) {
            proposal.noVotes += voterPower;
        } else {
            proposal.abstainVotes += voterPower;
        }

        emit VoteCast(proposalId, msg.sender, voteType, voterPower);
    }

    function finalizeProposal(uint256 proposalId) external {
        Proposal storage proposal = proposals[proposalId];
        require(proposal.status == ProposalStatus.Active, "Proposal not active");
        require(block.timestamp > proposal.endTime, "Voting still active");

        uint256 totalVotes = proposal.yesVotes + proposal.noVotes + proposal.abstainVotes;
        uint256 quorumRequired = (staking.totalStaked() * QUORUM_PERCENTAGE) / 100;

        if (totalVotes >= quorumRequired && proposal.yesVotes > proposal.noVotes) {
            proposal.status = ProposalStatus.Passed;
            proposal.executionTime = block.timestamp + EXECUTION_DELAY;
        } else {
            proposal.status = ProposalStatus.Failed;
        }

        emit ProposalFinalized(proposalId, proposal.status);
    }

    function executeProposal(uint256 proposalId) external nonReentrant {
        Proposal storage proposal = proposals[proposalId];
        require(proposal.status == ProposalStatus.Passed, "Proposal not passed");
        require(block.timestamp >= proposal.executionTime, "Execution delay not met");

        proposal.status = ProposalStatus.Executed;

        if (proposal.executionData.length > 0) {
            (bool success, ) = address(this).call(proposal.executionData);
            require(success, "Execution failed");
        }

        emit ProposalExecuted(proposalId);
    }

    function cancelProposal(uint256 proposalId) external {
        Proposal storage proposal = proposals[proposalId];
        require(
            msg.sender == proposal.proposer || msg.sender == owner(),
            "Not authorized to cancel"
        );
        require(proposal.status == ProposalStatus.Active, "Proposal not active");

        proposal.status = ProposalStatus.Cancelled;
        emit ProposalCancelled(proposalId);
    }

    function getProposal(uint256 proposalId) external view returns (Proposal memory) {
        return proposals[proposalId];
    }

    function getVote(uint256 proposalId, address voter) external view returns (Vote memory) {
        return votes[proposalId][voter];
    }

    function quorumRequired() external view returns (uint256) {
        return (staking.totalStaked() * QUORUM_PERCENTAGE) / 100;
    }
}
