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

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title MultisigGovernanceV3
 * @notice V3: Threshold enforcement, time-locked execution, signer rotation limits, proposal expiry.
 */
contract MultisigGovernanceV3 is ReentrancyGuard {

    uint256 public constant EXECUTION_TIMELOCK     = 24 hours;
    uint256 public constant PROPOSAL_EXPIRY        = 7 days;
    uint256 public constant SIGNER_CHANGE_COOLDOWN = 7 days;
    uint256 public constant MIN_THRESHOLD_BPS      = 5000; // 50%

    error ThresholdTooLow();
    error ProposalNotFound();
    error ProposalExpired();
    error TimelockNotExpired();
    error AlreadySigned();
    error NotSigner();
    error AlreadyExecuted();
    error SignerChangeCooldown();
    error NotGuardian();
    error InsufficientSignatures();
    error GuardianAlreadyApproved();
    error AddressAlreadySigner();
    error AddressNotSigner();
    error CannotRemoveLastSigner();

    struct Proposal {
        bytes   data;
        address target;
        uint256 value;
        uint64  createdAt;
        uint64  executableAt;
        uint256 signatures;
        bool    executed;
        uint256 nonce;
    }

    mapping(uint256 => Proposal)                     public proposals;
    mapping(uint256 => mapping(address => bool))     public hasSigned;
    mapping(address => bool)                         public isSigner;
    address[3]                                       public guardians;
    address[]                                        public signers;
    uint256                                          public threshold;
    uint256                                          public proposalCount;
    uint256                                          public globalNonce;
    uint256                                          public lastSignerChange;

    /// @notice Guardian approval accumulator for emergency execute
    mapping(bytes32 => mapping(address => bool))     public guardianApprovals;
    mapping(bytes32 => uint256)                      public guardianApprovalCount;

    event ProposalCreated(uint256 indexed proposalId, address target, uint256 value);
    event ProposalSigned(uint256 indexed proposalId, address signer);
    event ProposalExecuted(uint256 indexed proposalId);
    event SignerAdded(address indexed signer);
    event SignerRemoved(address indexed signer);
    event ThresholdUpdated(uint256 newThreshold);
    event EmergencyApproved(bytes32 indexed callHash, address indexed guardian, uint256 approvalCount);
    event EmergencyExecuted(bytes32 indexed callHash, address target);

    constructor(address[] memory _signers, uint256 _threshold, address[3] memory _guardians) {
        require(_signers.length > 0, "no signers");
        require(_threshold > 0 && _threshold <= _signers.length, "bad threshold");
        // Enforce MIN_THRESHOLD_BPS
        require(_threshold * 10000 / _signers.length >= MIN_THRESHOLD_BPS, "threshold too low");

        for (uint256 i = 0; i < _signers.length; i++) {
            isSigner[_signers[i]] = true;
            signers.push(_signers[i]);
        }
        threshold = _threshold;
        guardians = _guardians;
    }

    modifier onlySigner() {
        if (!isSigner[msg.sender]) revert NotSigner();
        _;
    }

    function createProposal(
        address target,
        uint256 value,
        bytes calldata data
    ) external onlySigner returns (uint256 proposalId) {
        proposalId = ++proposalCount;
        proposals[proposalId] = Proposal({
            data:         data,
            target:       target,
            value:        value,
            createdAt:    uint64(block.timestamp),
            executableAt: uint64(block.timestamp + EXECUTION_TIMELOCK),
            signatures:   0,
            executed:     false,
            nonce:        ++globalNonce
        });
        emit ProposalCreated(proposalId, target, value);
    }

    function signProposal(uint256 proposalId) external onlySigner nonReentrant {
        Proposal storage p = proposals[proposalId];
        if (p.createdAt == 0)                             revert ProposalNotFound();
        if (block.timestamp > p.createdAt + PROPOSAL_EXPIRY) revert ProposalExpired();
        if (hasSigned[proposalId][msg.sender])            revert AlreadySigned();
        hasSigned[proposalId][msg.sender] = true;
        p.signatures++;
        emit ProposalSigned(proposalId, msg.sender);
    }

    function executeProposal(uint256 proposalId) external nonReentrant {
        Proposal storage p = proposals[proposalId];
        if (p.createdAt == 0)                              revert ProposalNotFound();
        if (p.executed)                                    revert AlreadyExecuted();
        if (block.timestamp < p.executableAt)              revert TimelockNotExpired();
        if (block.timestamp > p.createdAt + PROPOSAL_EXPIRY) revert ProposalExpired();
        if (p.signatures < threshold)                      revert InsufficientSignatures();

        p.executed = true;
        (bool ok,) = p.target.call{value: p.value}(p.data);
        require(ok, "execution failed");
        emit ProposalExecuted(proposalId);
    }

    function proposeSignerChange(address add, address remove) external onlySigner {
        if (block.timestamp < lastSignerChange + SIGNER_CHANGE_COOLDOWN) revert SignerChangeCooldown();
        if (isSigner[add]) revert AddressAlreadySigner();
        if (!isSigner[remove]) revert AddressNotSigner();
        if (signers.length <= 1) revert CannotRemoveLastSigner();

        // Remove old signer
        isSigner[remove] = false;
        for (uint256 i = 0; i < signers.length; i++) {
            if (signers[i] == remove) {
                signers[i] = signers[signers.length - 1];
                signers.pop();
                break;
            }
        }
        emit SignerRemoved(remove);

        // Add new signer
        isSigner[add] = true;
        signers.push(add);
        emit SignerAdded(add);

        lastSignerChange = block.timestamp;
    }

    function emergencyExecute(bytes calldata data, address target) external nonReentrant {
        // Requires all 3 guardians to approve the same (target, data) hash
        bool isGuardian = false;
        for (uint256 i = 0; i < 3; i++) {
            if (guardians[i] == msg.sender) { isGuardian = true; break; }
        }
        if (!isGuardian) revert NotGuardian();

        bytes32 callHash = keccak256(abi.encode(target, data));
        if (guardianApprovals[callHash][msg.sender]) revert GuardianAlreadyApproved();
        guardianApprovals[callHash][msg.sender] = true;
        guardianApprovalCount[callHash]++;

        emit EmergencyApproved(callHash, msg.sender, guardianApprovalCount[callHash]);

        if (guardianApprovalCount[callHash] < 3) return; // Need all 3 guardians

        (bool ok,) = target.call(data);
        require(ok, "emergency execution failed");
        emit EmergencyExecuted(callHash, target);
    }

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

    receive() external payable {}
}
