// 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 {OMNLJurisdictionPolicyRegistry} from "./OMNLJurisdictionPolicyRegistry.sol";

/**
 * @title OMNLNotaryRegistry
 * @notice On-chain notarization of compliance evidence hashes (packages, matrices, journal refs)
 *         with jurisdiction-scoped threshold ECDSA multisig.
 */
contract OMNLNotaryRegistry is AccessControl {
    bytes32 public constant NOTARY_ADMIN_ROLE = keccak256("NOTARY_ADMIN_ROLE");

    bytes32 public constant NOTARIZATION_TYPEHASH = keccak256(
        "OMNLNotarization(uint256 chainId,address registry,bytes32 jurisdictionId,bytes32 matrixControlId,bytes32 contentHash,bytes32 merkleRoot,bytes32 metadataHash,uint256 nonce)"
    );

    enum NotarizationKind {
        EvidencePackage,
        ComplianceMatrix,
        JournalEntry,
        Iso20022Message,
        CustodianAttestation,
        PolicyProfile,
        Other
    }

    struct NotarizationRecord {
        bytes32 jurisdictionId;
        bytes32 matrixControlId;
        bytes32 contentHash;
        bytes32 merkleRoot;
        bytes32 metadataHash;
        NotarizationKind kind;
        uint256 notarizedAt;
        address submitter;
        uint256 version;
        bool revoked;
    }

    OMNLJurisdictionPolicyRegistry public immutable jurisdictionRegistry;

    mapping(bytes32 notarizationKey => NotarizationRecord) private _records;
    mapping(bytes32 notarizationKey => uint256) public notarizationNonce;

    mapping(address => bool) public isNotarySigner;
    uint256 public globalNotaryThreshold;

    event NotarySignerSet(address indexed signer, bool active);
    event NotaryThresholdUpdated(uint256 threshold);
    event Notarized(
        bytes32 indexed notarizationKey,
        bytes32 indexed jurisdictionId,
        bytes32 indexed matrixControlId,
        bytes32 contentHash,
        bytes32 merkleRoot,
        NotarizationKind kind,
        uint256 version,
        address submitter
    );
    event NotarizationRevoked(bytes32 indexed notarizationKey, address indexed by);

    constructor(address admin, address jurisdictionRegistry_) {
        require(admin != address(0), "OMNLNotaryRegistry: zero admin");
        require(jurisdictionRegistry_ != address(0), "OMNLNotaryRegistry: zero registry");
        jurisdictionRegistry = OMNLJurisdictionPolicyRegistry(jurisdictionRegistry_);
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(NOTARY_ADMIN_ROLE, admin);
    }

    function setNotarySigner(address signer, bool active) external onlyRole(NOTARY_ADMIN_ROLE) {
        isNotarySigner[signer] = active;
        emit NotarySignerSet(signer, active);
    }

    function setGlobalNotaryThreshold(uint256 threshold) external onlyRole(NOTARY_ADMIN_ROLE) {
        globalNotaryThreshold = threshold;
        emit NotaryThresholdUpdated(threshold);
    }

    function notarizationKey(
        bytes32 jurisdictionId,
        bytes32 matrixControlId,
        bytes32 contentHash
    ) public pure returns (bytes32) {
        return keccak256(abi.encode(jurisdictionId, matrixControlId, contentHash));
    }

    function notarizeAttested(
        bytes32 jurisdictionId,
        bytes32 matrixControlId,
        bytes32 contentHash,
        bytes32 merkleRoot,
        bytes32 metadataHash,
        NotarizationKind kind,
        uint256 nonce,
        bytes[] calldata signatures
    ) external returns (bytes32 key) {
        key = notarizationKey(jurisdictionId, matrixControlId, contentHash);
        require(
            _records[key].contentHash == bytes32(0) || _records[key].revoked,
            "OMNLNotaryRegistry: active notarization exists"
        );
        require(nonce == notarizationNonce[key], "OMNLNotaryRegistry: nonce");

        uint256 threshold = _effectiveNotaryThreshold(jurisdictionId);
        require(threshold > 0, "OMNLNotaryRegistry: threshold off");
        _verifyThresholdSignatures(
            jurisdictionId,
            matrixControlId,
            contentHash,
            merkleRoot,
            metadataHash,
            nonce,
            signatures,
            threshold
        );

        uint256 nextVersion = (_records[key].version) + 1;
        notarizationNonce[key] = nonce + 1;

        _records[key] = NotarizationRecord({
            jurisdictionId: jurisdictionId,
            matrixControlId: matrixControlId,
            contentHash: contentHash,
            merkleRoot: merkleRoot,
            metadataHash: metadataHash,
            kind: kind,
            notarizedAt: block.timestamp,
            submitter: msg.sender,
            version: nextVersion,
            revoked: false
        });

        emit Notarized(key, jurisdictionId, matrixControlId, contentHash, merkleRoot, kind, nextVersion, msg.sender);
    }

    function revokeNotarization(bytes32 key) external onlyRole(NOTARY_ADMIN_ROLE) {
        require(_records[key].contentHash != bytes32(0), "OMNLNotaryRegistry: missing");
        _records[key].revoked = true;
        emit NotarizationRevoked(key, msg.sender);
    }

    function getNotarization(bytes32 key) external view returns (NotarizationRecord memory) {
        return _records[key];
    }

    function isNotarized(bytes32 jurisdictionId, bytes32 matrixControlId, bytes32 contentHash)
        external
        view
        returns (bool)
    {
        bytes32 key = notarizationKey(jurisdictionId, matrixControlId, contentHash);
        NotarizationRecord memory r = _records[key];
        return r.contentHash != bytes32(0) && !r.revoked;
    }

    function _effectiveNotaryThreshold(bytes32 jurisdictionId) internal view returns (uint256) {
        OMNLJurisdictionPolicyRegistry.JurisdictionPolicy memory p = jurisdictionRegistry.getPolicy(jurisdictionId);
        if (p.notaryThreshold > 0) return uint256(p.notaryThreshold);
        return globalNotaryThreshold;
    }

    function _verifyThresholdSignatures(
        bytes32 jurisdictionId,
        bytes32 matrixControlId,
        bytes32 contentHash,
        bytes32 merkleRoot,
        bytes32 metadataHash,
        uint256 nonce,
        bytes[] calldata signatures,
        uint256 threshold
    ) internal view {
        bytes32 digest = keccak256(
            abi.encode(
                NOTARIZATION_TYPEHASH,
                block.chainid,
                address(this),
                jurisdictionId,
                matrixControlId,
                contentHash,
                merkleRoot,
                metadataHash,
                nonce
            )
        );
        bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest));

        uint256 n = signatures.length;
        require(n >= threshold, "OMNLNotaryRegistry: 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(isNotarySigner[recovered], "OMNLNotaryRegistry: not signer");
            for (uint256 j = 0; j < uniq; j++) {
                require(seen[j] != recovered, "OMNLNotaryRegistry: duplicate signer");
            }
            seen[uniq++] = recovered;
        }
        require(uniq >= threshold, "OMNLNotaryRegistry: threshold");
    }
}
