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

import "./IVaultfireIdentity.sol";
import "./IVaultfireReputation.sol";

/// @title ReputationDecay — Dynamic trust scoring with time-based decay for AI agents
/// @author Vaultfire Protocol
/// @notice Trust scores decay over time without new positive evidence, preventing
///         stale reputation gaming. Features context-specific reputation and
///         recovery mechanisms after failures. Integrates with existing reputation registry.
/// @dev Wraps the existing IVaultfireReputation with decay logic and context scoring.
contract ReputationDecay {
    // ═══════════════════════════════════════════════════
    // STRUCTS
    // ═══════════════════════════════════════════════════

    struct DecayProfile {
        uint256 baseScore;              // Score from reputation registry
        uint256 decayedScore;           // Current score after applying decay
        uint256 lastActivityAt;         // Timestamp of last positive activity
        uint256 lastDecayCalculation;   // Timestamp of last decay check
        uint256 totalActivities;        // Lifetime activity count
        uint256 positiveActivities;     // Lifetime positive activity count
        uint256 negativeActivities;     // Lifetime negative activity count
        bool exists;
    }

    struct ContextReputation {
        uint256 score;                  // Score in this specific context (0-100)
        uint256 activities;             // Number of activities in this context
        uint256 lastActivityAt;         // Last activity in this context
        bool exists;
    }

    struct ActivityRecord {
        address agent;
        string context;                 // e.g., "trading", "data_analysis", "code_review"
        bool positive;                  // Was this activity positive?
        uint256 weight;                 // Impact weight (1-10)
        uint256 timestamp;
        address reporter;               // Who reported this activity
    }

    // ═══════════════════════════════════════════════════
    // STATE
    // ═══════════════════════════════════════════════════

    IVaultfireIdentity public immutable identityRegistry;
    IVaultfireReputation public immutable reputationRegistry;

    address public owner;

    // Decay parameters
    uint256 public decayRatePerDay = 1;         // Points decayed per day of inactivity
    uint256 public decayGracePeriod = 7 days;   // No decay for this period after activity
    uint256 public minDecayedScore = 5;         // Floor — score never decays below this
    uint256 public recoveryBonusMax = 10;       // Max bonus points for recovery from failure

    // Agent profiles
    mapping(address => DecayProfile) public profiles;

    // Context-specific reputation: agent => context hash => ContextReputation
    mapping(address => mapping(bytes32 => ContextReputation)) public contextScores;
    mapping(address => bytes32[]) public agentContexts;

    // Activity log
    ActivityRecord[] public activityLog;
    mapping(address => uint256[]) public agentActivities;

    // Authorized reputation reporters
    mapping(address => bool) public reporters;

    uint256 public totalProfiles;

    // ═══════════════════════════════════════════════════
    // EVENTS
    // ═══════════════════════════════════════════════════

    event ActivityRecorded(address indexed agent, string context, bool positive, uint256 weight, address indexed reporter);
    event ScoreDecayed(address indexed agent, uint256 oldScore, uint256 newScore, uint256 daysSinceActivity);
    event ProfileCreated(address indexed agent, uint256 baseScore);
    event ReporterUpdated(address indexed reporter, bool authorized);
    event DecayParametersUpdated(uint256 ratePerDay, uint256 gracePeriod, uint256 minScore);

    // ═══════════════════════════════════════════════════
    // MODIFIERS
    // ═══════════════════════════════════════════════════

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

    modifier onlyReporter() {
        require(msg.sender == owner || reporters[msg.sender], "ReputationDecay: caller is not authorized reporter");
        _;
    }

    modifier onlyRegisteredAgent(address agent) {
        (string memory uri, , , ) = identityRegistry.getAgent(agent);
        require(bytes(uri).length > 0, "ReputationDecay: agent not registered");
        _;
    }

    // ═══════════════════════════════════════════════════
    // CONSTRUCTOR
    // ═══════════════════════════════════════════════════

    constructor(
        address _identityRegistry,
        address _reputationRegistry
    ) {
        require(_identityRegistry != address(0), "ReputationDecay: zero identity address");
        require(_reputationRegistry != address(0), "ReputationDecay: zero reputation address");

        identityRegistry = IVaultfireIdentity(_identityRegistry);
        reputationRegistry = IVaultfireReputation(_reputationRegistry);
        owner = msg.sender;
    }

    // ═══════════════════════════════════════════════════
    // PROFILE MANAGEMENT
    // ═══════════════════════════════════════════════════

    /// @notice Initialize or refresh a decay profile from the base reputation registry
    /// @param agent The agent to create/refresh profile for
    function initializeProfile(address agent) external onlyRegisteredAgent(agent) {
        (uint256 baseScore, , ) = reputationRegistry.getReputation(agent);

        DecayProfile storage profile = profiles[agent];
        if (!profile.exists) {
            profile.exists = true;
            profile.lastActivityAt = block.timestamp;
            profile.lastDecayCalculation = block.timestamp;
            totalProfiles++;
            emit ProfileCreated(agent, baseScore);
        }

        profile.baseScore = baseScore;
        profile.decayedScore = baseScore;
    }

    // ═══════════════════════════════════════════════════
    // ACTIVITY RECORDING
    // ═══════════════════════════════════════════════════

    /// @notice Record an activity for an agent (positive or negative)
    /// @param agent The agent to record activity for
    /// @param context The context/category (e.g., "trading", "code_review")
    /// @param positive Whether this was a positive activity
    /// @param weight Impact weight (1-10)
    function recordActivity(
        address agent,
        string calldata context,
        bool positive,
        uint256 weight
    ) external onlyReporter onlyRegisteredAgent(agent) {
        require(weight >= 1 && weight <= 10, "ReputationDecay: weight must be 1-10");
        require(bytes(context).length > 0, "ReputationDecay: empty context");

        DecayProfile storage profile = profiles[agent];
        require(profile.exists, "ReputationDecay: profile not initialized");

        // Apply any pending decay first
        _applyDecay(agent);

        // Update global profile
        profile.totalActivities++;
        if (positive) {
            profile.positiveActivities++;
            uint256 boost = weight;
            // Recovery bonus: if coming back from low score, get a bonus
            if (profile.decayedScore < 50 && positive) {
                uint256 recoveryBonus = (50 - profile.decayedScore) * recoveryBonusMax / 50;
                boost += recoveryBonus;
            }
            profile.decayedScore = _min(profile.decayedScore + boost, 100);
        } else {
            profile.negativeActivities++;
            uint256 penalty = weight * 2; // Negatives hit harder
            if (profile.decayedScore > penalty) {
                profile.decayedScore -= penalty;
            } else {
                profile.decayedScore = minDecayedScore;
            }
        }

        profile.lastActivityAt = block.timestamp;

        // Update context-specific reputation
        bytes32 contextHash = keccak256(abi.encodePacked(context));
        ContextReputation storage ctx = contextScores[agent][contextHash];
        if (!ctx.exists) {
            ctx.exists = true;
            ctx.score = 50; // Start at neutral
            agentContexts[agent].push(contextHash);
        }

        ctx.activities++;
        ctx.lastActivityAt = block.timestamp;
        if (positive) {
            ctx.score = _min(ctx.score + weight, 100);
        } else {
            uint256 ctxPenalty = weight * 2;
            if (ctx.score > ctxPenalty) {
                ctx.score -= ctxPenalty;
            } else {
                ctx.score = minDecayedScore;
            }
        }

        // Log activity
        uint256 activityIndex = activityLog.length;
        activityLog.push(ActivityRecord({
            agent: agent,
            context: context,
            positive: positive,
            weight: weight,
            timestamp: block.timestamp,
            reporter: msg.sender
        }));
        agentActivities[agent].push(activityIndex);

        emit ActivityRecorded(agent, context, positive, weight, msg.sender);
    }

    // ═══════════════════════════════════════════════════
    // DECAY CALCULATION
    // ═══════════════════════════════════════════════════

    /// @notice Apply time-based decay to an agent's score
    /// @param agent The agent to apply decay to
    function applyDecay(address agent) external {
        _applyDecay(agent);
    }

    function _applyDecay(address agent) internal {
        DecayProfile storage profile = profiles[agent];
        if (!profile.exists) return;

        uint256 timeSinceActivity = block.timestamp - profile.lastActivityAt;

        // No decay during grace period
        if (timeSinceActivity <= decayGracePeriod) {
            profile.lastDecayCalculation = block.timestamp;
            return;
        }

        uint256 decayDays = (timeSinceActivity - decayGracePeriod) / 1 days;
        if (decayDays == 0) return;

        uint256 oldScore = profile.decayedScore;
        uint256 totalDecay = decayDays * decayRatePerDay;

        if (profile.decayedScore > totalDecay + minDecayedScore) {
            profile.decayedScore -= totalDecay;
        } else {
            profile.decayedScore = minDecayedScore;
        }

        profile.lastDecayCalculation = block.timestamp;

        if (oldScore != profile.decayedScore) {
            emit ScoreDecayed(agent, oldScore, profile.decayedScore, decayDays);
        }
    }

    // ═══════════════════════════════════════════════════
    // VIEW FUNCTIONS
    // ═══════════════════════════════════════════════════

    /// @notice Get the current decayed score for an agent (read-only, doesn't update state)
    function getCurrentScore(address agent) external view returns (
        uint256 decayedScore,
        uint256 baseScore,
        uint256 daysSinceActivity,
        uint256 totalActivities
    ) {
        DecayProfile storage profile = profiles[agent];
        if (!profile.exists) return (0, 0, 0, 0);

        uint256 timeSinceActivity = block.timestamp - profile.lastActivityAt;
        uint256 days_ = timeSinceActivity / 1 days;

        // Calculate what the decayed score would be
        uint256 currentDecayed = profile.decayedScore;
        if (timeSinceActivity > decayGracePeriod) {
            uint256 decayDays = (timeSinceActivity - decayGracePeriod) / 1 days;
            uint256 totalDecay_ = decayDays * decayRatePerDay;
            if (currentDecayed > totalDecay_ + minDecayedScore) {
                currentDecayed -= totalDecay_;
            } else {
                currentDecayed = minDecayedScore;
            }
        }

        return (currentDecayed, profile.baseScore, days_, profile.totalActivities);
    }

    /// @notice Get context-specific score for an agent
    function getContextScore(address agent, string calldata context) external view returns (
        uint256 score,
        uint256 activities,
        uint256 lastActivityAt
    ) {
        bytes32 contextHash = keccak256(abi.encodePacked(context));
        ContextReputation storage ctx = contextScores[agent][contextHash];
        if (!ctx.exists) return (0, 0, 0);
        return (ctx.score, ctx.activities, ctx.lastActivityAt);
    }

    /// @notice Get all context hashes for an agent
    function getAgentContexts(address agent) external view returns (bytes32[] memory) {
        return agentContexts[agent];
    }

    /// @notice Get activity count
    function getActivityCount() external view returns (uint256) {
        return activityLog.length;
    }

    function _min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    // ═══════════════════════════════════════════════════
    // ADMIN
    // ═══════════════════════════════════════════════════

    /// @notice Update decay parameters
    function setDecayParameters(
        uint256 _ratePerDay,
        uint256 _gracePeriod,
        uint256 _minScore,
        uint256 _recoveryBonus
    ) external onlyOwner {
        require(_ratePerDay > 0 && _ratePerDay <= 10, "ReputationDecay: rate out of range");
        require(_gracePeriod >= 1 days, "ReputationDecay: grace period too short");
        require(_minScore <= 20, "ReputationDecay: min score too high");
        require(_recoveryBonus <= 20, "ReputationDecay: recovery bonus too high");

        decayRatePerDay = _ratePerDay;
        decayGracePeriod = _gracePeriod;
        minDecayedScore = _minScore;
        recoveryBonusMax = _recoveryBonus;

        emit DecayParametersUpdated(_ratePerDay, _gracePeriod, _minScore);
    }

    /// @notice Add or remove a reputation reporter
    function setReporter(address reporter, bool authorized) external onlyOwner {
        require(reporter != address(0), "ReputationDecay: zero reporter");
        reporters[reporter] = authorized;
        emit ReporterUpdated(reporter, authorized);
    }

    /// @notice Transfer ownership
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "ReputationDecay: zero owner");
        owner = newOwner;
    }
}
