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

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {OMNLNotaryRegistry} from "./OMNLNotaryRegistry.sol";

/**
 * @title OMNLComplianceMultisig
 * @notice Web3 multisig for OMNL admin operations: arbitrary calls + notarization-gated executions.
 *         Supports threshold ECDSA (HSM/cold signers) and on-wallet confirm/execute flow.
 * @dev Production: prefer Gnosis Safe as owner of DEFAULT_ADMIN roles; this contract coordinates OMNL-specific gates.
 */
contract OMNLComplianceMultisig is AccessControl, ReentrancyGuard {
    bytes32 public constant ADMIN_SIGNER_ROLE = keccak256("ADMIN_SIGNER_ROLE");
    bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");

    bytes32 public constant EXECUTION_TYPEHASH = keccak256(
        "OMNLMultisigExecution(uint256 chainId,address multisig,bytes32 jurisdictionId,uint256 nonce,address target,uint256 value,bytes32 calldataHash)"
    );

    OMNLNotaryRegistry public immutable notaryRegistry;

    uint256 public adminThreshold;
    uint256 public executionNonce;

    struct PendingExecution {
        bytes32 jurisdictionId;
        address target;
        uint256 value;
        bytes data;
        bytes32 requiredContentHash;
        bytes32 requiredMatrixControlId;
        bool requireNotarization;
        bool executed;
        uint256 submittedAt;
    }

    mapping(uint256 executionId => PendingExecution) public pendingExecutions;
    mapping(uint256 executionId => mapping(address => bool)) public executionConfirmations;
    mapping(uint256 executionId => uint256) public executionConfirmationCount;
    uint256 public pendingCount;

    event ExecutionSubmitted(
        uint256 indexed executionId,
        bytes32 indexed jurisdictionId,
        address target,
        bool requireNotarization,
        bytes32 requiredContentHash
    );
    event ExecutionConfirmed(uint256 indexed executionId, address indexed signer);
    event ExecutionRevoked(uint256 indexed executionId, address indexed signer);
    event ExecutionCompleted(uint256 indexed executionId, bool success);
    event AdminThresholdUpdated(uint256 threshold);

    constructor(address admin, address notaryRegistry_) {
        require(admin != address(0), "OMNLComplianceMultisig: zero admin");
        require(notaryRegistry_ != address(0), "OMNLComplianceMultisig: zero notary");
        notaryRegistry = OMNLNotaryRegistry(notaryRegistry_);
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(ADMIN_SIGNER_ROLE, admin);
        _grantRole(PROPOSER_ROLE, admin);
        adminThreshold = 1;
    }

    function setAdminThreshold(uint256 threshold) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(threshold > 0, "OMNLComplianceMultisig: threshold");
        adminThreshold = threshold;
        emit AdminThresholdUpdated(threshold);
    }

    function submitExecution(
        bytes32 jurisdictionId,
        address target,
        uint256 value,
        bytes calldata data,
        bool requireNotarization,
        bytes32 requiredContentHash,
        bytes32 requiredMatrixControlId
    ) external onlyRole(PROPOSER_ROLE) returns (uint256 executionId) {
        require(target != address(0), "OMNLComplianceMultisig: zero target");
        executionId = pendingCount++;
        pendingExecutions[executionId] = PendingExecution({
            jurisdictionId: jurisdictionId,
            target: target,
            value: value,
            data: data,
            requiredContentHash: requiredContentHash,
            requiredMatrixControlId: requiredMatrixControlId,
            requireNotarization: requireNotarization,
            executed: false,
            submittedAt: block.timestamp
        });
        emit ExecutionSubmitted(executionId, jurisdictionId, target, requireNotarization, requiredContentHash);
        _confirmExecution(executionId, msg.sender);
    }

    function confirmExecution(uint256 executionId) external onlyRole(ADMIN_SIGNER_ROLE) {
        _confirmExecution(executionId, msg.sender);
    }

    function revokeExecutionConfirmation(uint256 executionId) external onlyRole(ADMIN_SIGNER_ROLE) {
        require(!pendingExecutions[executionId].executed, "OMNLComplianceMultisig: executed");
        require(executionConfirmations[executionId][msg.sender], "OMNLComplianceMultisig: not confirmed");
        executionConfirmations[executionId][msg.sender] = false;
        executionConfirmationCount[executionId]--;
        emit ExecutionRevoked(executionId, msg.sender);
    }

    function executePending(uint256 executionId) external nonReentrant onlyRole(ADMIN_SIGNER_ROLE) {
        PendingExecution storage ex = pendingExecutions[executionId];
        require(!ex.executed, "OMNLComplianceMultisig: executed");
        require(executionConfirmationCount[executionId] >= adminThreshold, "OMNLComplianceMultisig: confirmations");

        if (ex.requireNotarization) {
            require(
                notaryRegistry.isNotarized(ex.jurisdictionId, ex.requiredMatrixControlId, ex.requiredContentHash),
                "OMNLComplianceMultisig: not notarized"
            );
        }

        ex.executed = true;
        (bool ok,) = ex.target.call{value: ex.value}(ex.data);
        emit ExecutionCompleted(executionId, ok);
        require(ok, "OMNLComplianceMultisig: call failed");
    }

    /// @notice Threshold ECDSA path for cold/HSM signers (no prior on-chain confirm).
    function executeAttested(
        bytes32 jurisdictionId,
        address target,
        uint256 value,
        bytes calldata data,
        bool requireNotarization,
        bytes32 requiredContentHash,
        bytes32 requiredMatrixControlId,
        uint256 nonce,
        bytes[] calldata signatures
    ) external nonReentrant {
        require(target != address(0), "OMNLComplianceMultisig: zero target");
        require(nonce == executionNonce, "OMNLComplianceMultisig: nonce");

        bytes32 calldataHash = keccak256(data);
        bytes32 digest = keccak256(
            abi.encode(
                EXECUTION_TYPEHASH,
                block.chainid,
                address(this),
                jurisdictionId,
                nonce,
                target,
                value,
                calldataHash
            )
        );
        bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest));
        _verifyAdminSignatures(ethSignedMessageHash, signatures);

        if (requireNotarization) {
            require(
                notaryRegistry.isNotarized(jurisdictionId, requiredMatrixControlId, requiredContentHash),
                "OMNLComplianceMultisig: not notarized"
            );
        }

        executionNonce = nonce + 1;
        (bool ok,) = target.call{value: value}(data);
        emit ExecutionCompleted(type(uint256).max, ok);
        require(ok, "OMNLComplianceMultisig: call failed");
    }

    function _confirmExecution(uint256 executionId, address signer) internal {
        PendingExecution storage ex = pendingExecutions[executionId];
        require(!ex.executed, "OMNLComplianceMultisig: executed");
        require(!executionConfirmations[executionId][signer], "OMNLComplianceMultisig: confirmed");
        executionConfirmations[executionId][signer] = true;
        executionConfirmationCount[executionId]++;
        emit ExecutionConfirmed(executionId, signer);
    }

    function _verifyAdminSignatures(bytes32 ethSignedMessageHash, bytes[] calldata signatures) internal view {
        uint256 n = signatures.length;
        require(n >= adminThreshold, "OMNLComplianceMultisig: sig count");
        address[] memory seen = new address[](n);
        uint256 uniq = 0;
        for (uint256 i = 0; i < n; i++) {
            address recovered = ECDSA.recover(ethSignedMessageHash, signatures[i]);
            require(hasRole(ADMIN_SIGNER_ROLE, recovered), "OMNLComplianceMultisig: not signer");
            for (uint256 j = 0; j < uniq; j++) {
                require(seen[j] != recovered, "OMNLComplianceMultisig: duplicate");
            }
            seen[uniq++] = recovered;
        }
        require(uniq >= adminThreshold, "OMNLComplianceMultisig: threshold");
    }

    receive() external payable {}
}
