// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { EIP712BaseV3 } from "../signatures/EIP712BaseV3.sol";
import { SignatureVerifierV3 } from "../signatures/SignatureVerifierV3.sol";

/// @title EvidenceRegistryV3
/// @notice Attestation registry with mandated fields from assignment §8:
///         attester, attester authority, subject, bond/mandate, evidence
///         schema, evidence commitment, observation period, timestamp, nonce,
///         signature, expiration, independence classification.
///         Duplicate attestations within the same round are rejected.
contract EvidenceRegistryV3 is EIP712BaseV3 {
    bytes32 public constant GOVERNANCE_POLICY_MODULE_ID = keccak256("Vaultfire.BaseV3.EvidenceRegistry");

    struct Attestation {
        address attester;
        bytes32 attesterAuthority; // authority tag hash (e.g. governance policy id)
        bytes32 subject; // agentId/wallet hash
        bytes32 bondOrMandate; // bondId or mandateHash bytes32-cast
        bytes32 evidenceSchema; // schema hash
        bytes32 evidenceCommitment; // content hash or merkle root
        uint64 observationStart;
        uint64 observationEnd;
        uint64 timestamp;
        uint64 expiresAt;
        uint96 nonce;
        uint8 independenceClass; // 0=self, 1=econ-backed, 2=independent, 3=disputed, 4=expired, 5=legacy, 6=cross-chain
        uint256 round; // uniqueness scope (same round + same subject + same attester = dup)
    }

    bytes32 public constant ATTESTATION_TYPEHASH = keccak256(
        "Attestation(address attester,bytes32 attesterAuthority,bytes32 subject,bytes32 bondOrMandate,bytes32 evidenceSchema,bytes32 evidenceCommitment,uint64 observationStart,uint64 observationEnd,uint64 timestamp,uint64 expiresAt,uint96 nonce,uint8 independenceClass,uint256 round)"
    );

    error DuplicateAttestation();
    error ExpiredAttestation();
    error BadSignature();
    error UnauthorizedAttester();
    error ZeroAddress();
    error NonceUsed();
    error BadAttestation();

    address public owner;
    mapping(address => bool) public authorizedAttester;
    mapping(address => mapping(uint96 => bool)) public nonceUsed;
    // (round, subject, attester) => attestationHash
    mapping(uint256 => mapping(bytes32 => mapping(address => bytes32))) public roundRecord;

    event AttestationRegisteredV3(
        bytes32 indexed attestationHash,
        address indexed attester,
        bytes32 indexed subject,
        bytes32 bondOrMandate,
        uint256 round,
        uint8 independenceClass
    );
    event AttesterSetV3(address indexed a, bool authorized);
    event OwnerSetV3(address indexed prev, address indexed curr);

    constructor(address _owner) EIP712BaseV3("3.0.0") {
        if (_owner == address(0)) revert ZeroAddress();
        owner = _owner;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    function setAttester(address a, bool v) external onlyOwner {
        authorizedAttester[a] = v;
        emit AttesterSetV3(a, v);
    }

    function hashAttestation(Attestation calldata a) public pure returns (bytes32) {
        return keccak256(
            abi.encode(
                ATTESTATION_TYPEHASH,
                a.attester,
                a.attesterAuthority,
                a.subject,
                a.bondOrMandate,
                a.evidenceSchema,
                a.evidenceCommitment,
                a.observationStart,
                a.observationEnd,
                a.timestamp,
                a.expiresAt,
                a.nonce,
                a.independenceClass,
                a.round
            )
        );
    }

    function register(Attestation calldata a, bytes calldata sig) external returns (bytes32 attestationHash) {
        if (!authorizedAttester[a.attester]) revert UnauthorizedAttester();
        if (
            a.attester == address(0) || a.subject == bytes32(0) || a.bondOrMandate == bytes32(0)
                || a.attesterAuthority == bytes32(0) || a.evidenceSchema == bytes32(0)
                || a.evidenceCommitment == bytes32(0) || a.independenceClass > 6
                || a.observationEnd < a.observationStart || a.timestamp < a.observationStart
                || a.timestamp > a.observationEnd || a.observationEnd > block.timestamp || a.timestamp > block.timestamp
        ) {
            revert BadAttestation();
        }
        if (a.expiresAt != 0 && a.expiresAt <= block.timestamp) revert ExpiredAttestation();
        if (nonceUsed[a.attester][a.nonce]) revert NonceUsed();
        if (roundRecord[a.round][a.subject][a.attester] != bytes32(0)) revert DuplicateAttestation();
        attestationHash = hashAttestation(a);
        bytes32 digest = _hashTypedData(attestationHash);
        if (!SignatureVerifierV3.verify(a.attester, digest, sig)) revert BadSignature();
        nonceUsed[a.attester][a.nonce] = true;
        roundRecord[a.round][a.subject][a.attester] = attestationHash;
        emit AttestationRegisteredV3(
            attestationHash, a.attester, a.subject, a.bondOrMandate, a.round, a.independenceClass
        );
    }
}
