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

import "./IERC8004.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/// @dev The subset of the LIVE, deployed AereAIReputation (chain 2800, 0x781e…feBF) this adapter
///      uses. Reputation there is keyed by (operator address, bytes32 agentId): `attest` is gated
///      to a registered attestor set fixed at deploy, and `scoreOf` / `statsOf` are public reads.
interface IAereAIReputationLive {
    function attest(address operator, bytes32 agentId, int8 delta, bytes32 evidenceHash, string calldata evidenceUri)
        external;

    function scoreOf(address operator, bytes32 agentId) external view returns (uint256);

    function statsOf(address operator, bytes32 agentId)
        external
        view
        returns (uint256 positiveCount, uint256 disputeCount, uint256 lifetimeSlashed);

    function isAttestor(address who) external view returns (bool);
}

/// @dev The one Identity-adapter read used to resolve an ERC-8004 agentId to its Aere operator
///      (the agent's controller), which is the operator address the live reputation is keyed by.
interface IAereIdentity8004 {
    function isRegistered(uint256 agentId) external view returns (bool);
    function getAgent(uint256 agentId)
        external
        view
        returns (IERC8004Identity.AgentInfo memory);
}

/**
 * @title AereReputationRegistry8004, an ERC-8004 Reputation adapter over the deployed AereAIReputation
 *
 * @notice Exposes the ERC-8004 Reputation surface (attest / read feedback) over Aere Network's
 *         ALREADY-DEPLOYED composable reputation primitive, AereAIReputation. It does NOT redeploy
 *         or modify the live reputation contract.
 *
 *         READ (adapts the deployed contract directly). `getScore` and `getStats` map an ERC-8004
 *         agentId to the live reputation's (operator, agentId) key and read `scoreOf` / `statsOf`
 *         straight from the deployed AereAIReputation. This works against the currently-live
 *         instance as-is, with no change to it.
 *
 *         WRITE (forwards feedback to the live contract). `giveFeedback` maps an ERC-8004 feedback
 *         signal to the live `attest(operator, agentId, delta, evidenceHash, uri)` and forwards it.
 *
 * @dev    KEY MAPPING. The live reputation is keyed by (operator address, bytes32 agentId). This
 *         adapter derives:
 *           operator = the agent's current controller, resolved from the Identity adapter.
 *           agentKey = bytes32(agentId)  (the ERC-8004 / DID agent id, left-padded).
 *         So an ERC-8004 read/write addresses exactly one live reputation slot, deterministically.
 *
 * @dev    HONEST WRITE CONSTRAINT. AereAIReputation's attestor set is fixed at its deploy and has
 *         no add path. For `giveFeedback` to land in the live score, THIS adapter must be a
 *         registered attestor on the reputation instance it is constructed with. The currently-live
 *         AereAIReputation lists the Foundation attestor only (immutable), so pairing this adapter
 *         for WRITE-THROUGH is a Foundation governance decision, external to this contract and
 *         founder-gated. [VERIFY: on mainnet, register this adapter as an attestor before relying on
 *         giveFeedback, or have the Foundation attestor call the live attest directly.] The READ
 *         path needs no such authorization and works against the live instance today.
 *
 * @dev    [VERIFY: confirm against the finalized ERC-8004 interface] ERC-8004 reputation feedback in
 *         later drafts carries a richer payload (score, tag, response URI). Here the feedback signal
 *         is normalized to the live model's delta in {-1, 0, +1} (+1 positive, -1 dispute, 0
 *         neutral) so no information is invented beyond what the deployed contract records.
 *
 *         HONEST SCOPE. Application-layer reputation only. Does NOT change AERE consensus, holds no
 *         funds.
 *
 * @dev    CALLER GATE (feedback authorization). Because every caller collapses into ONE on-chain
 *         attestor identity (this adapter) on the live AereAIReputation, an ungated write path would
 *         let ANY address inflate or tank any agent's reputation, defeating the live contract's
 *         curated-attestor model and its (attestor, evidenceHash) dedup. To mirror that curated model,
 *         `giveFeedback` is gated to an owner-managed allowlist of authorized feedback authors
 *         (`isAuthorizedFeedbackAuthor`). The owner curates WHO may submit feedback; it CANNOT set a
 *         reputation value directly, cannot bypass the {-1, 0, +1} delta clamp every author is held to,
 *         and holds no funds. This is the least-surprising design that closes the "anyone can write"
 *         hole while keeping the score honest and the read path fully permissionless.
 */
contract AereReputationRegistry8004 is IERC8004Reputation, Ownable {
    /// @notice The LIVE, deployed AereAIReputation this adapter presents an ERC-8004 face over.
    IAereAIReputationLive public immutable REPUTATION;

    /// @notice The Aere ERC-8004 Identity adapter, used to resolve agentId -> operator (controller).
    IAereIdentity8004 public immutable IDENTITY;

    /// @notice Owner-curated set of addresses allowed to submit feedback through this adapter. Mirrors
    ///         the live AereAIReputation curated-attestor model at the adapter boundary: only an
    ///         authorized author may drive an agent's live reputation via `giveFeedback`.
    mapping(address => bool) public isAuthorizedFeedbackAuthor;

    error ZeroAddress();
    error AgentNotRegistered(uint256 agentId);
    error InvalidDelta(int8 delta);
    error AdapterNotAttestor();
    error NotAuthorizedFeedbackAuthor(address caller);

    /// @notice Emitted when the owner adds or removes an authorized feedback author.
    event FeedbackAuthorSet(address indexed author, bool authorized);

    constructor(address reputation, address identity) {
        if (reputation == address(0) || identity == address(0)) revert ZeroAddress();
        REPUTATION = IAereAIReputationLive(reputation);
        IDENTITY = IAereIdentity8004(identity);
    }

    // ==========================================================================
    //                       Feedback-author administration
    // ==========================================================================

    /// @notice Authorize `author` to submit feedback through this adapter. Owner-only. This power is
    ///         scoped to feedback AUTHORIZATION only: it cannot set a reputation value, cannot exceed
    ///         the {-1, 0, +1} delta clamp, and moves no funds.
    function addFeedbackAuthor(address author) external onlyOwner {
        if (author == address(0)) revert ZeroAddress();
        isAuthorizedFeedbackAuthor[author] = true;
        emit FeedbackAuthorSet(author, true);
    }

    /// @notice Revoke `author`'s authorization to submit feedback through this adapter. Owner-only.
    function removeFeedbackAuthor(address author) external onlyOwner {
        isAuthorizedFeedbackAuthor[author] = false;
        emit FeedbackAuthorSet(author, false);
    }

    // ==========================================================================
    //                                  Write
    // ==========================================================================

    /**
     * @notice Record ERC-8004 feedback about an agent, forwarding it to the live AereAIReputation.
     *         The feedback signal is `delta` in {-1, 0, +1}. Resolves the live operator from the
     *         Identity adapter and calls the deployed `attest`. Reverts fail-closed if this adapter
     *         is not an authorized attestor on the live reputation (see contract NatSpec).
     * @dev    Gated to the owner-curated `isAuthorizedFeedbackAuthor` set: an unauthorized caller
     *         reverts NotAuthorizedFeedbackAuthor, so this adapter cannot act as an open reputation
     *         firehose once it is a live attestor. Every authorized author is still bound by the
     *         {-1, 0, +1} delta clamp, so authorization cannot forge an arbitrary score.
     * @param agentId      the ERC-8004 / DID agent id.
     * @param delta        +1 positive, -1 dispute, 0 neutral (audit-trail only).
     * @param evidenceHash a 32-byte commitment to the off-chain evidence (non-zero on the live path).
     * @param evidenceURI  an optional public URI for the evidence.
     */
    function giveFeedback(uint256 agentId, int8 delta, bytes32 evidenceHash, string calldata evidenceURI) external {
        if (!isAuthorizedFeedbackAuthor[msg.sender]) revert NotAuthorizedFeedbackAuthor(msg.sender);
        if (!IDENTITY.isRegistered(agentId)) revert AgentNotRegistered(agentId);
        if (delta < -1 || delta > 1) revert InvalidDelta(delta);
        // Surface the missing-authorization case with a clear adapter-level error rather than an
        // opaque revert bubbling up from the live contract.
        if (!REPUTATION.isAttestor(address(this))) revert AdapterNotAttestor();

        address operator = _operatorOf(agentId);
        REPUTATION.attest(operator, _agentKey(agentId), delta, evidenceHash, evidenceURI);

        emit NewFeedback(agentId, msg.sender, delta, evidenceHash, evidenceURI);
    }

    // ==========================================================================
    //                                  Read
    // ==========================================================================

    /// @inheritdoc IERC8004Reputation
    /// @notice The agent's live reputation score (0..10000), read straight from AereAIReputation.
    function getScore(uint256 agentId) external view returns (uint256) {
        return REPUTATION.scoreOf(_operatorOf(agentId), _agentKey(agentId));
    }

    /// @notice The raw live reputation counters for an agent (positive / dispute / lifetime slashed).
    function getStats(uint256 agentId)
        external
        view
        returns (uint256 positiveCount, uint256 disputeCount, uint256 lifetimeSlashed)
    {
        return REPUTATION.statsOf(_operatorOf(agentId), _agentKey(agentId));
    }

    /// @notice True iff this adapter is authorized to write feedback into the live reputation.
    function canWriteThrough() external view returns (bool) {
        return REPUTATION.isAttestor(address(this));
    }

    /// @notice The (operator, agentKey) pair this adapter maps an ERC-8004 agentId to on the live contract.
    function liveKeyOf(uint256 agentId) external view returns (address operator, bytes32 agentKey) {
        return (_operatorOf(agentId), _agentKey(agentId));
    }

    // ==========================================================================
    //                                 Internal
    // ==========================================================================

    function _operatorOf(uint256 agentId) internal view returns (address) {
        return IDENTITY.getAgent(agentId).agentAddress; // current controller
    }

    function _agentKey(uint256 agentId) internal pure returns (bytes32) {
        return bytes32(agentId);
    }
}
