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

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

/// @title AgentSLAEnforcer — On-chain SLA commitments and enforcement for AI agents
/// @author Vaultfire Protocol
/// @notice Agents commit to on-chain Service Level Agreements (uptime, response time,
///         accuracy). SLA violations trigger automatic bond penalties. Performance
///         history is verifiable on-chain for partner evaluation.
/// @dev Integrates with bonds for penalty enforcement and reputation for performance tracking.
contract AgentSLAEnforcer {
    // ═══════════════════════════════════════════════════
    // STRUCTS
    // ═══════════════════════════════════════════════════

    struct SLACommitment {
        address agent;                  // Agent making the commitment
        uint256 uptimeTarget;           // Target uptime in basis points (9500 = 95%)
        uint256 responseTimeMs;         // Max response time in milliseconds
        uint256 accuracyTarget;         // Target accuracy in basis points (9800 = 98%)
        uint256 stakedAmount;           // Native token staked as SLA guarantee (wei)
        uint256 penaltyRate;            // Penalty per violation in basis points of stake
        uint256 activeSince;            // When SLA became active
        uint256 expiresAt;              // When SLA expires
        uint256 totalViolations;        // Count of violations
        uint256 totalPenalties;         // Total penalties deducted (wei)
        bool active;                    // Whether SLA is currently active
    }

    struct SLAReport {
        uint256 slaId;                  // SLA being reported on
        address reporter;               // Who submitted the report
        uint256 measuredUptime;         // Measured uptime (basis points)
        uint256 measuredResponseTime;   // Measured response time (ms)
        uint256 measuredAccuracy;       // Measured accuracy (basis points)
        uint256 reportedAt;             // Timestamp
        uint256 periodStart;            // Start of measurement period
        uint256 periodEnd;              // End of measurement period
        bool uptimeViolation;           // Did uptime violate SLA?
        bool responseTimeViolation;     // Did response time violate SLA?
        bool accuracyViolation;         // Did accuracy violate SLA?
        uint256 penaltyApplied;         // Penalty amount applied (wei)
    }

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

    IVaultfireIdentity public immutable identityRegistry;
    IVaultfireBonds public immutable bondsRegistry;
    IVaultfireReputation public immutable reputationRegistry;

    address public owner;

    uint256 public totalSLAs;
    uint256 public totalReports;
    uint256 public totalViolations;
    uint256 public totalPenaltiesCollected;

    uint256 public constant MIN_UPTIME_TARGET = 5000;        // 50% minimum
    uint256 public constant MAX_RESPONSE_TIME = 60000;       // 60 seconds max
    uint256 public constant MIN_ACCURACY_TARGET = 5000;      // 50% minimum
    uint256 public constant MIN_SLA_DURATION = 7 days;
    uint256 public constant MAX_SLA_DURATION = 365 days;
    uint256 public constant MAX_PENALTY_RATE = 1000;          // 10% max per violation
    uint256 public constant MIN_REPORT_PERIOD = 1 hours;     // Reports must cover at least 1 hour

    mapping(uint256 => SLACommitment) public slas;
    mapping(uint256 => SLAReport) public reports;
    mapping(address => uint256[]) public agentSLAs;
    mapping(address => uint256[]) public agentReports;

    // Authorized SLA monitors (can submit performance reports)
    mapping(address => bool) public monitors;

    // Penalty pool — collected penalties held for redistribution
    uint256 public penaltyPool;

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

    event SLACreated(uint256 indexed slaId, address indexed agent, uint256 uptimeTarget, uint256 responseTimeMs, uint256 accuracyTarget, uint256 stake);
    event SLAExpired(uint256 indexed slaId, address indexed agent, uint256 totalViolations);
    event SLAReportSubmitted(uint256 indexed reportId, uint256 indexed slaId, address indexed reporter, bool hasViolation);
    event SLAViolation(uint256 indexed slaId, address indexed agent, uint256 penalty, string violationType);
    event SLAStakeReturned(uint256 indexed slaId, address indexed agent, uint256 amount);
    event MonitorUpdated(address indexed monitor, bool authorized);
    event PenaltyWithdrawn(address indexed to, uint256 amount);

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

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

    modifier onlyMonitor() {
        require(msg.sender == owner || monitors[msg.sender], "AgentSLAEnforcer: caller is not authorized monitor");
        _;
    }

    modifier onlyRegisteredAgent(address agent) {
        (string memory uri, bool active, , ) = identityRegistry.getAgent(agent);
        require(bytes(uri).length > 0 && active, "AgentSLAEnforcer: agent not registered or inactive");
        _;
    }

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

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

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

    // ═══════════════════════════════════════════════════
    // SLA CREATION
    // ═══════════════════════════════════════════════════

    /// @notice Create an SLA commitment with staked guarantee
    /// @param uptimeTarget Target uptime (basis points, e.g., 9950 = 99.5%)
    /// @param responseTimeMs Max response time in milliseconds
    /// @param accuracyTarget Target accuracy (basis points, e.g., 9800 = 98%)
    /// @param penaltyRate Penalty per violation (basis points of stake, e.g., 500 = 5%)
    /// @param duration SLA duration in seconds
    function createSLA(
        uint256 uptimeTarget,
        uint256 responseTimeMs,
        uint256 accuracyTarget,
        uint256 penaltyRate,
        uint256 duration
    ) external payable onlyRegisteredAgent(msg.sender) {
        require(msg.value > 0, "AgentSLAEnforcer: must stake native token");
        require(uptimeTarget >= MIN_UPTIME_TARGET && uptimeTarget <= 10000, "AgentSLAEnforcer: invalid uptime target");
        require(responseTimeMs > 0 && responseTimeMs <= MAX_RESPONSE_TIME, "AgentSLAEnforcer: invalid response time");
        require(accuracyTarget >= MIN_ACCURACY_TARGET && accuracyTarget <= 10000, "AgentSLAEnforcer: invalid accuracy target");
        require(penaltyRate > 0 && penaltyRate <= MAX_PENALTY_RATE, "AgentSLAEnforcer: invalid penalty rate");
        require(duration >= MIN_SLA_DURATION && duration <= MAX_SLA_DURATION, "AgentSLAEnforcer: invalid duration");

        uint256 slaId = totalSLAs;
        totalSLAs++;

        slas[slaId] = SLACommitment({
            agent: msg.sender,
            uptimeTarget: uptimeTarget,
            responseTimeMs: responseTimeMs,
            accuracyTarget: accuracyTarget,
            stakedAmount: msg.value,
            penaltyRate: penaltyRate,
            activeSince: block.timestamp,
            expiresAt: block.timestamp + duration,
            totalViolations: 0,
            totalPenalties: 0,
            active: true
        });

        agentSLAs[msg.sender].push(slaId);

        emit SLACreated(slaId, msg.sender, uptimeTarget, responseTimeMs, accuracyTarget, msg.value);
    }

    // ═══════════════════════════════════════════════════
    // PERFORMANCE REPORTING
    // ═══════════════════════════════════════════════════

    /// @notice Submit a performance report for an SLA period
    /// @param slaId The SLA being reported on
    /// @param measuredUptime Measured uptime (basis points)
    /// @param measuredResponseTime Measured response time (ms)
    /// @param measuredAccuracy Measured accuracy (basis points)
    /// @param periodStart Start of measurement period
    /// @param periodEnd End of measurement period
    function submitReport(
        uint256 slaId,
        uint256 measuredUptime,
        uint256 measuredResponseTime,
        uint256 measuredAccuracy,
        uint256 periodStart,
        uint256 periodEnd
    ) external onlyMonitor {
        SLACommitment storage sla = slas[slaId];
        require(sla.active, "AgentSLAEnforcer: SLA not active");
        require(block.timestamp < sla.expiresAt, "AgentSLAEnforcer: SLA expired");
        require(periodEnd > periodStart, "AgentSLAEnforcer: invalid period");
        require(periodEnd - periodStart >= MIN_REPORT_PERIOD, "AgentSLAEnforcer: report period too short");
        require(periodEnd <= block.timestamp, "AgentSLAEnforcer: future period not allowed");
        require(measuredUptime <= 10000, "AgentSLAEnforcer: uptime out of range");
        require(measuredAccuracy <= 10000, "AgentSLAEnforcer: accuracy out of range");

        // Check for violations
        bool uptimeViolation = measuredUptime < sla.uptimeTarget;
        bool responseViolation = measuredResponseTime > sla.responseTimeMs;
        bool accuracyViolation = measuredAccuracy < sla.accuracyTarget;
        bool hasViolation = uptimeViolation || responseViolation || accuracyViolation;

        uint256 penalty = 0;
        if (hasViolation) {
            // Calculate penalty
            uint256 violationCount = 0;
            if (uptimeViolation) violationCount++;
            if (responseViolation) violationCount++;
            if (accuracyViolation) violationCount++;

            // Penalty = stake * penaltyRate * violations / 10000
            uint256 remainingStake = sla.stakedAmount - sla.totalPenalties;
            penalty = (remainingStake * sla.penaltyRate * violationCount) / 10000;

            // Cap penalty at remaining stake
            if (penalty > remainingStake) {
                penalty = remainingStake;
            }

            sla.totalViolations += violationCount;
            sla.totalPenalties += penalty;
            totalViolations += violationCount;
            totalPenaltiesCollected += penalty;
            penaltyPool += penalty;

            // Emit violation events
            if (uptimeViolation) {
                emit SLAViolation(slaId, sla.agent, penalty / violationCount, "uptime");
            }
            if (responseViolation) {
                emit SLAViolation(slaId, sla.agent, penalty / violationCount, "response_time");
            }
            if (accuracyViolation) {
                emit SLAViolation(slaId, sla.agent, penalty / violationCount, "accuracy");
            }

            // If all stake consumed, deactivate SLA
            if (sla.stakedAmount - sla.totalPenalties == 0) {
                sla.active = false;
                emit SLAExpired(slaId, sla.agent, sla.totalViolations);
            }
        }

        // Record report
        uint256 reportId = totalReports;
        totalReports++;

        reports[reportId] = SLAReport({
            slaId: slaId,
            reporter: msg.sender,
            measuredUptime: measuredUptime,
            measuredResponseTime: measuredResponseTime,
            measuredAccuracy: measuredAccuracy,
            reportedAt: block.timestamp,
            periodStart: periodStart,
            periodEnd: periodEnd,
            uptimeViolation: uptimeViolation,
            responseTimeViolation: responseViolation,
            accuracyViolation: accuracyViolation,
            penaltyApplied: penalty
        });

        agentReports[sla.agent].push(reportId);

        emit SLAReportSubmitted(reportId, slaId, msg.sender, hasViolation);
    }

    // ═══════════════════════════════════════════════════
    // SLA LIFECYCLE
    // ═══════════════════════════════════════════════════

    /// @notice Expire an SLA and return remaining stake to agent
    function expireSLA(uint256 slaId) external {
        SLACommitment storage sla = slas[slaId];
        require(sla.active, "AgentSLAEnforcer: SLA not active");
        require(block.timestamp >= sla.expiresAt, "AgentSLAEnforcer: SLA not expired");

        sla.active = false;

        // Return remaining stake to agent
        uint256 remainingStake = sla.stakedAmount - sla.totalPenalties;
        if (remainingStake > 0) {
            (bool sent, ) = sla.agent.call{value: remainingStake}("");
            require(sent, "AgentSLAEnforcer: stake return failed");
            emit SLAStakeReturned(slaId, sla.agent, remainingStake);
        }

        emit SLAExpired(slaId, sla.agent, sla.totalViolations);
    }

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

    /// @notice Get agent's SLA performance summary
    function getAgentPerformance(address agent) external view returns (
        uint256 activeSLAs,
        uint256 totalStaked,
        uint256 totalPenalized,
        uint256 violationCount
    ) {
        uint256[] storage ids = agentSLAs[agent];
        for (uint256 i = 0; i < ids.length; i++) {
            SLACommitment storage sla = slas[ids[i]];
            if (sla.active && block.timestamp < sla.expiresAt) {
                activeSLAs++;
                totalStaked += sla.stakedAmount - sla.totalPenalties;
            }
            totalPenalized += sla.totalPenalties;
            violationCount += sla.totalViolations;
        }
    }

    /// @notice Get all SLA IDs for an agent
    function getAgentSLAs(address agent) external view returns (uint256[] memory) {
        return agentSLAs[agent];
    }

    /// @notice Get all report IDs for an agent
    function getAgentReports(address agent) external view returns (uint256[] memory) {
        return agentReports[agent];
    }

    /// @notice Get global SLA stats
    function getSLAStats() external view returns (
        uint256 _totalSLAs,
        uint256 _totalReports,
        uint256 _totalViolations,
        uint256 _totalPenalties,
        uint256 _penaltyPool
    ) {
        return (totalSLAs, totalReports, totalViolations, totalPenaltiesCollected, penaltyPool);
    }

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

    /// @notice Add or remove an SLA monitor
    function setMonitor(address monitor, bool authorized) external onlyOwner {
        require(monitor != address(0), "AgentSLAEnforcer: zero monitor");
        monitors[monitor] = authorized;
        emit MonitorUpdated(monitor, authorized);
    }

    /// @notice Withdraw collected penalties from the pool
    function withdrawPenalties(address to, uint256 amount) external onlyOwner {
        require(to != address(0), "AgentSLAEnforcer: zero address");
        require(amount <= penaltyPool, "AgentSLAEnforcer: exceeds pool");
        penaltyPool -= amount;
        (bool sent, ) = to.call{value: amount}("");
        require(sent, "AgentSLAEnforcer: withdrawal failed");
        emit PenaltyWithdrawn(to, amount);
    }

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