// Copyright 2024 Saorsa Labs Limited
//
// This software is dual-licensed under:
// - GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
// - Commercial License
//
// For AGPL-3.0 license, see LICENSE-AGPL-3.0
// For commercial licensing, contact: david@saorsalabs.com

//! Trust system integration for signed heartbeats.
//!
//! This module provides the bridge between the heartbeat system and EigenTrust,
//! adjusting trust scores based on heartbeat compliance.
//!
//! ## Trust Score Adjustments
//!
//! - **Successful heartbeat**: Small positive adjustment (+0.05 base, scaled by streak)
//! - **Missed heartbeat**: Negative adjustment (-0.1 for suspect, -0.2 for unresponsive)
//! - **Status transitions**: Larger adjustments for state changes
//!
//! ## Integration
//!
//! ```rust,ignore
//! use saorsa_core::attestation::{SignedHeartbeatManager, HeartbeatTrustIntegration, SignedPeerStatus};
//! use saorsa_core::adaptive::EigenTrustEngine;
//!
//! let trust_engine = Arc::new(EigenTrustEngine::new(HashSet::new()));
//! let trust_callback = HeartbeatTrustIntegration::new(trust_engine, local_id);
//! ```

use super::signed_heartbeat_manager::{SignedHeartbeatTrustCallback, SignedPeerStatus};
use crate::adaptive::{EigenTrustEngine, NodeStatisticsUpdate};
use crate::peer_record::UserId;
use std::sync::Arc;

/// Trust integration for heartbeat compliance.
///
/// Implements `HeartbeatTrustCallback` to update EigenTrust scores
/// based on heartbeat behavior.
pub struct HeartbeatTrustIntegration {
    /// Reference to the EigenTrust engine.
    trust_engine: Arc<EigenTrustEngine>,

    /// Local node's EntangledId (for self-trust updates).
    local_entangled_id: [u8; 32],

    /// Configuration for trust adjustments.
    config: HeartbeatTrustConfig,
}

/// Configuration for heartbeat trust adjustments.
#[derive(Debug, Clone)]
pub struct HeartbeatTrustConfig {
    /// Base trust bonus for successful heartbeat (before streak multiplier).
    pub success_bonus: f64,

    /// Maximum streak multiplier (caps the benefit of long streaks).
    pub max_streak_multiplier: f64,

    /// Trust penalty for becoming suspect.
    pub suspect_penalty: f64,

    /// Trust penalty for becoming unresponsive.
    pub unresponsive_penalty: f64,

    /// Trust recovery when going from suspect/unresponsive to healthy.
    pub recovery_bonus: f64,
}

impl Default for HeartbeatTrustConfig {
    fn default() -> Self {
        Self {
            success_bonus: 0.02,
            max_streak_multiplier: 3.0,
            suspect_penalty: 0.1,
            unresponsive_penalty: 0.25,
            recovery_bonus: 0.1,
        }
    }
}

impl HeartbeatTrustIntegration {
    /// Create a new heartbeat trust integration.
    pub fn new(trust_engine: Arc<EigenTrustEngine>, local_entangled_id: [u8; 32]) -> Self {
        Self {
            trust_engine,
            local_entangled_id,
            config: HeartbeatTrustConfig::default(),
        }
    }

    /// Create with custom configuration.
    pub fn with_config(
        trust_engine: Arc<EigenTrustEngine>,
        local_entangled_id: [u8; 32],
        config: HeartbeatTrustConfig,
    ) -> Self {
        Self {
            trust_engine,
            local_entangled_id,
            config,
        }
    }

    /// Convert EntangledId bytes to a NodeId for trust operations.
    fn to_node_id(entangled_id: &[u8; 32]) -> UserId {
        UserId::from_bytes(*entangled_id)
    }
}

#[async_trait::async_trait]
impl SignedHeartbeatTrustCallback for HeartbeatTrustIntegration {
    async fn on_status_change(
        &self,
        entangled_id: &[u8; 32],
        old_status: SignedPeerStatus,
        new_status: SignedPeerStatus,
    ) {
        let peer_id = Self::to_node_id(entangled_id);
        let local_id = Self::to_node_id(&self.local_entangled_id);

        match (old_status, new_status) {
            // Going from healthy/unknown to suspect
            (SignedPeerStatus::Healthy | SignedPeerStatus::Unknown, SignedPeerStatus::Suspect) => {
                tracing::info!(
                    peer = %hex::encode(&entangled_id[..8]),
                    "Peer became suspect, applying trust penalty"
                );
                // Record as failed interaction
                self.trust_engine
                    .update_local_trust(&local_id, &peer_id, false)
                    .await;
            }

            // Going from healthy/unknown/suspect to unresponsive
            (_, SignedPeerStatus::Unresponsive) => {
                tracing::warn!(
                    peer = %hex::encode(&entangled_id[..8]),
                    "Peer became unresponsive, applying severe trust penalty"
                );
                // Record multiple failed interactions for unresponsive
                for _ in 0..3 {
                    self.trust_engine
                        .update_local_trust(&local_id, &peer_id, false)
                        .await;
                }
            }

            // Recovery: going from suspect/unresponsive to healthy
            (
                SignedPeerStatus::Suspect | SignedPeerStatus::Unresponsive,
                SignedPeerStatus::Healthy,
            ) => {
                tracing::info!(
                    peer = %hex::encode(&entangled_id[..8]),
                    "Peer recovered to healthy, applying trust recovery"
                );
                // Record as successful interaction (recovery)
                self.trust_engine
                    .update_local_trust(&local_id, &peer_id, true)
                    .await;
            }

            // Going from unknown to healthy
            (SignedPeerStatus::Unknown, SignedPeerStatus::Healthy) => {
                tracing::debug!(
                    peer = %hex::encode(&entangled_id[..8]),
                    "Peer became healthy (first heartbeat)"
                );
                // Small positive for first successful heartbeat
                self.trust_engine
                    .update_local_trust(&local_id, &peer_id, true)
                    .await;
            }

            _ => {
                // No significant status change
            }
        }
    }

    async fn on_heartbeat_success(&self, entangled_id: &[u8; 32], streak: u32) {
        let peer_id = Self::to_node_id(entangled_id);
        let local_id = Self::to_node_id(&self.local_entangled_id);

        // Calculate streak multiplier (diminishing returns)
        let streak_multiplier =
            (1.0 + (streak as f64).ln().max(0.0)).min(self.config.max_streak_multiplier);

        tracing::trace!(
            peer = %hex::encode(&entangled_id[..8]),
            streak,
            multiplier = streak_multiplier,
            "Recording heartbeat success"
        );

        // Record successful interaction
        self.trust_engine
            .update_local_trust(&local_id, &peer_id, true)
            .await;

        // Update node uptime statistics (approximation: heartbeat interval as uptime increment)
        // This contributes to the multi-factor trust calculation
        self.trust_engine
            .update_node_stats(&peer_id, NodeStatisticsUpdate::Uptime(60))
            .await;

        // Record as correct response
        self.trust_engine
            .update_node_stats(&peer_id, NodeStatisticsUpdate::CorrectResponse)
            .await;
    }

    async fn on_heartbeat_miss(&self, entangled_id: &[u8; 32], missed_count: u32) {
        let peer_id = Self::to_node_id(entangled_id);
        let local_id = Self::to_node_id(&self.local_entangled_id);

        tracing::debug!(
            peer = %hex::encode(&entangled_id[..8]),
            missed_count,
            "Recording heartbeat miss"
        );

        // Record as failed interaction
        self.trust_engine
            .update_local_trust(&local_id, &peer_id, false)
            .await;

        // Update node statistics with failed response
        self.trust_engine
            .update_node_stats(&peer_id, NodeStatisticsUpdate::FailedResponse)
            .await;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashSet;

    #[tokio::test]
    async fn test_trust_integration_creation() {
        let engine = Arc::new(EigenTrustEngine::new(HashSet::new()));
        let local_id = [1u8; 32];

        let integration = HeartbeatTrustIntegration::new(engine, local_id);
        assert_eq!(integration.local_entangled_id, local_id);
    }

    #[tokio::test]
    async fn test_heartbeat_success_updates_trust() {
        let engine = Arc::new(EigenTrustEngine::new(HashSet::new()));
        let local_id = [1u8; 32];
        let peer_id = [2u8; 32];

        let integration = HeartbeatTrustIntegration::new(engine.clone(), local_id);

        // Record successful heartbeat
        integration.on_heartbeat_success(&peer_id, 1).await;

        // Should have positive trust
        let peer_node_id = HeartbeatTrustIntegration::to_node_id(&peer_id);
        let trust = engine.get_trust_async(&peer_node_id).await;
        assert!(trust >= 0.0);
    }

    #[tokio::test]
    async fn test_status_change_to_suspect_penalizes() {
        let engine = Arc::new(EigenTrustEngine::new(HashSet::new()));
        let local_id = [1u8; 32];
        let peer_id = [2u8; 32];

        let integration = HeartbeatTrustIntegration::new(engine.clone(), local_id);

        // First, establish some trust
        integration.on_heartbeat_success(&peer_id, 5).await;

        // Get initial trust
        let peer_node_id = HeartbeatTrustIntegration::to_node_id(&peer_id);
        let initial_trust = engine.get_trust_async(&peer_node_id).await;

        // Then mark as suspect
        integration
            .on_status_change(
                &peer_id,
                SignedPeerStatus::Healthy,
                SignedPeerStatus::Suspect,
            )
            .await;

        // Trust should be lower (or we at least recorded a failed interaction)
        // The actual trust computation happens in the background, so we just verify
        // the interaction was recorded
        let final_trust = engine.get_trust_async(&peer_node_id).await;

        // We can't directly compare trust values since computation is async,
        // but we verify no errors occurred
        assert!(initial_trust >= 0.0);
        assert!(final_trust >= 0.0);
    }

    #[tokio::test]
    async fn test_status_change_to_unresponsive_penalizes_severely() {
        let engine = Arc::new(EigenTrustEngine::new(HashSet::new()));
        let local_id = [1u8; 32];
        let peer_id = [2u8; 32];

        let integration = HeartbeatTrustIntegration::new(engine.clone(), local_id);

        // Mark as unresponsive directly
        integration
            .on_status_change(
                &peer_id,
                SignedPeerStatus::Healthy,
                SignedPeerStatus::Unresponsive,
            )
            .await;

        // Should have recorded multiple failed interactions (3x penalty)
        // We verify this indirectly - no errors occurred
        let peer_node_id = HeartbeatTrustIntegration::to_node_id(&peer_id);
        let trust = engine.get_trust_async(&peer_node_id).await;
        assert!(trust >= 0.0);
    }

    #[tokio::test]
    async fn test_recovery_to_healthy_improves_trust() {
        let engine = Arc::new(EigenTrustEngine::new(HashSet::new()));
        let local_id = [1u8; 32];
        let peer_id = [2u8; 32];

        let integration = HeartbeatTrustIntegration::new(engine.clone(), local_id);

        // First, mark as suspect
        integration
            .on_status_change(
                &peer_id,
                SignedPeerStatus::Healthy,
                SignedPeerStatus::Suspect,
            )
            .await;

        // Then recover to healthy
        integration
            .on_status_change(
                &peer_id,
                SignedPeerStatus::Suspect,
                SignedPeerStatus::Healthy,
            )
            .await;

        // Should have positive interaction recorded for recovery
        let peer_node_id = HeartbeatTrustIntegration::to_node_id(&peer_id);
        let trust = engine.get_trust_async(&peer_node_id).await;
        assert!(trust >= 0.0);
    }
}
