//! EigenTrust power iteration algorithm.
//!
//! Implements the EigenTrust algorithm for computing global trust scores
//! in a web of trust. The algorithm is Sybil-resistant because trust
//! only flows from pre-trusted seed agents.
//!
//! # Algorithm
//!
//! The EigenTrust algorithm computes global trust scores using power iteration:
//!
//! ```text
//! T = (1-α)C^T * T + α * P
//! ```
//!
//! Where:
//! - T: Trust vector (what we're computing)
//! - C: Row-normalized adjacency matrix (trust edges)
//! - P: Seed trust vector (pre-trusted agents)
//! - α: Damping factor (probability of jumping to a seed)
//!
//! # Sybil Resistance
//!
//! The key insight is that isolated rings of agents (not connected to seeds)
//! receive NO propagated trust. Only seed-connected agents accumulate meaningful trust.

use super::model::{AgentScore, EigenTrustConfig, EigenTrustResult, EigenTrustState, TrustEdge};
use std::collections::HashMap;
use tracing::{debug, instrument};

/// Compute EigenTrust scores using power iteration.
///
/// # Arguments
/// * `edges` - All trust edges in the graph
/// * `seed_trust` - Pre-trusted agents and their seed weights (will be normalized)
/// * `config` - Algorithm configuration
/// * `timestamp` - Unix timestamp for the result
///
/// # Returns
/// `EigenTrustResult` containing the computed state and convergence status.
///
/// # Sybil Resistance
///
/// Agents not connected to the seed trust network receive near-zero trust.
/// This is achieved through the α * P term in the power iteration:
/// - α = 0.1 means 10% of trust comes directly from seeds each iteration
/// - Isolated rings have no path to seeds, so their trust decays to zero
///
/// # Performance
///
/// - Time: O(iterations * edges)
/// - Space: O(agents)
/// - Typical convergence: 10-15 iterations
#[instrument(skip(edges, seed_trust), fields(edge_count = edges.len(), seed_count = seed_trust.len()))]
pub fn compute_eigentrust_scores(
    edges: &[TrustEdge],
    seed_trust: &[([u8; 32], f32)],
    config: &EigenTrustConfig,
    timestamp: u64,
) -> EigenTrustResult {
    // Handle empty graph
    if edges.is_empty() && seed_trust.is_empty() {
        debug!("Empty graph, returning empty state");
        return EigenTrustResult { state: EigenTrustState::empty(timestamp), converged: true };
    }

    // Build agent → index mapping
    let mut agent_to_idx: HashMap<[u8; 32], usize> = HashMap::new();
    let mut idx_to_agent: Vec<[u8; 32]> = Vec::new();

    for edge in edges {
        if let std::collections::hash_map::Entry::Vacant(e) = agent_to_idx.entry(edge.from_agent) {
            e.insert(idx_to_agent.len());
            idx_to_agent.push(edge.from_agent);
        }
        if let std::collections::hash_map::Entry::Vacant(e) = agent_to_idx.entry(edge.to_agent) {
            e.insert(idx_to_agent.len());
            idx_to_agent.push(edge.to_agent);
        }
    }

    // Add seed agents that might not have edges
    for (agent, _) in seed_trust {
        if let std::collections::hash_map::Entry::Vacant(e) = agent_to_idx.entry(*agent) {
            e.insert(idx_to_agent.len());
            idx_to_agent.push(*agent);
        }
    }

    let n = idx_to_agent.len();
    if n == 0 {
        debug!("No agents in graph, returning empty state");
        return EigenTrustResult { state: EigenTrustState::empty(timestamp), converged: true };
    }

    debug!(agent_count = n, "Building adjacency matrix");

    // Build row-normalized adjacency matrix C
    // C[i][j] = normalized weight from i to j
    // We store as Vec<Vec<(target_idx, weight)>> for sparse representation
    let mut outgoing: Vec<Vec<(usize, f32)>> = vec![Vec::new(); n];
    let mut out_sum: Vec<f32> = vec![0.0; n];

    for edge in edges {
        if !edge.is_valid() {
            continue;
        }
        if let (Some(&from_idx), Some(&to_idx)) =
            (agent_to_idx.get(&edge.from_agent), agent_to_idx.get(&edge.to_agent))
        {
            outgoing[from_idx].push((to_idx, edge.weight));
            out_sum[from_idx] += edge.weight;
        }
    }

    // Normalize outgoing weights (row normalization)
    for (i, edges_list) in outgoing.iter_mut().enumerate() {
        let sum = out_sum[i];
        if sum > 0.0 {
            for (_, weight) in edges_list.iter_mut() {
                *weight /= sum;
            }
        }
    }

    // Build seed vector P (normalized)
    let mut p: Vec<f32> = vec![0.0; n];
    let mut p_sum = 0.0_f32;

    for (agent, weight) in seed_trust {
        if let Some(&idx) = agent_to_idx.get(agent) {
            p[idx] = *weight;
            p_sum += *weight;
        }
    }

    // Normalize P
    if p_sum > 0.0 {
        for pi in &mut p {
            *pi /= p_sum;
        }
    } else {
        // No seed trust: uniform distribution (fallback, not recommended)
        debug!("Warning: No seed trust provided, using uniform distribution");
        for pi in &mut p {
            *pi = 1.0 / n as f32;
        }
    }

    // Initialize trust vector T = P
    let mut t: Vec<f32> = p.clone();

    // Power iteration: T = (1-α) * C^T * T + α * P
    let alpha = config.alpha;
    let one_minus_alpha = 1.0 - alpha;
    let mut iterations = 0_u32;
    let mut convergence_delta = f32::MAX;

    for iter in 0..config.max_iterations {
        iterations = iter + 1;

        // Compute new_t = (1-α) * C^T * t + α * p
        let mut new_t: Vec<f32> = vec![0.0; n];

        // C^T * t: for each node i, collect trust from nodes that trust i
        // This is equivalent to: for each node j with outgoing edge to i,
        // add normalized_weight * t[j] to new_t[i]
        for (j, edges_list) in outgoing.iter().enumerate() {
            let t_j = t[j];
            for &(i, weight) in edges_list {
                new_t[i] += weight * t_j;
            }
        }

        // Handle dangling nodes (nodes with no outgoing edges)
        // Distribute their trust uniformly to seeds
        let mut dangling_mass = 0.0_f32;
        for (j, sum) in out_sum.iter().enumerate() {
            if *sum == 0.0 {
                dangling_mass += t[j];
            }
        }
        if dangling_mass > 0.0 {
            for (i, pi) in p.iter().enumerate() {
                new_t[i] += dangling_mass * pi;
            }
        }

        // Apply (1-α) factor and add α * p
        for i in 0..n {
            new_t[i] = one_minus_alpha * new_t[i] + alpha * p[i];
        }

        // Compute L1 norm of change
        convergence_delta = 0.0;
        for i in 0..n {
            convergence_delta += (new_t[i] - t[i]).abs();
        }

        debug!(iteration = iterations, delta = convergence_delta, "Power iteration step");

        // Update t
        t = new_t;

        // Check convergence
        if convergence_delta < config.epsilon {
            debug!(iterations, delta = convergence_delta, "Converged");
            break;
        }
    }

    // Normalize final scores to sum to 1.0
    let t_sum: f32 = t.iter().sum();
    if t_sum > 0.0 {
        for ti in &mut t {
            *ti /= t_sum;
        }
    }

    // Build result
    let scores: Vec<AgentScore> = idx_to_agent
        .into_iter()
        .zip(t)
        .map(|(agent, score)| AgentScore::new(agent, score))
        .collect();

    let converged = convergence_delta < config.epsilon;

    debug!(
        iterations,
        converged,
        delta = convergence_delta,
        agents = scores.len(),
        "EigenTrust computation complete"
    );

    EigenTrustResult {
        state: EigenTrustState { scores, computed_at: timestamp, iterations, convergence_delta },
        converged,
    }
}

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

    fn agent(id: u8) -> [u8; 32] {
        let mut arr = [0u8; 32];
        arr[0] = id;
        arr
    }

    #[test]
    fn test_empty_graph() {
        let result = compute_eigentrust_scores(&[], &[], &EigenTrustConfig::default(), 1000);

        assert!(result.converged);
        assert!(result.state.scores.is_empty());
    }

    #[test]
    fn test_single_seed_no_edges() {
        let seeds = vec![(agent(1), 1.0)];
        let result = compute_eigentrust_scores(&[], &seeds, &EigenTrustConfig::default(), 1000);

        assert!(result.converged);
        assert_eq!(result.state.scores.len(), 1);

        // Single seed gets all the trust
        let score = result.state.get_score(&agent(1));
        assert!((score - 1.0).abs() < 0.01);
    }

    #[test]
    fn test_simple_chain() {
        // Seed → A → B
        // Seed trusts A, A trusts B
        let seed = agent(0);
        let a = agent(1);
        let b = agent(2);

        let edges =
            vec![TrustEdge::new(seed, a, 1.0, 1000, None), TrustEdge::new(a, b, 1.0, 1000, None)];
        let seeds = vec![(seed, 1.0)];

        let result = compute_eigentrust_scores(&edges, &seeds, &EigenTrustConfig::default(), 1000);

        assert!(result.converged);

        // Seed should have highest trust (directly seeded)
        // A should have next highest (trusted by seed)
        // B should have lowest (trusted by A)
        let seed_score = result.state.get_score(&seed);
        let a_score = result.state.get_score(&a);
        let b_score = result.state.get_score(&b);

        assert!(seed_score > a_score);
        assert!(a_score > b_score);
        assert!(b_score > 0.0);
    }

    #[test]
    fn test_isolated_ring_gets_low_trust() {
        // This is the key Sybil resistance test
        //
        // Network:
        // - Seed agent S with seed trust
        // - Isolated ring: A → B → C → A (no connection to S)
        //
        // Expected: Ring agents get near-zero trust

        let s = agent(0);
        let a = agent(1);
        let b = agent(2);
        let c = agent(3);

        // S has no edges (just seed trust)
        // A → B → C → A forms isolated ring
        let edges = vec![
            TrustEdge::new(a, b, 1.0, 1000, None),
            TrustEdge::new(b, c, 1.0, 1000, None),
            TrustEdge::new(c, a, 1.0, 1000, None),
        ];
        let seeds = vec![(s, 1.0)];

        let result = compute_eigentrust_scores(&edges, &seeds, &EigenTrustConfig::default(), 1000);

        assert!(result.converged);

        // Seed retains most trust (since it's the only pre-trusted agent)
        let s_score = result.state.get_score(&s);
        assert!(s_score > 0.9, "Seed score should be high: {}", s_score);

        // Ring agents should have near-zero trust (not connected to seed)
        let a_score = result.state.get_score(&a);
        let b_score = result.state.get_score(&b);
        let c_score = result.state.get_score(&c);

        assert!(a_score < 0.05, "Isolated agent A should have low trust: {}", a_score);
        assert!(b_score < 0.05, "Isolated agent B should have low trust: {}", b_score);
        assert!(c_score < 0.05, "Isolated agent C should have low trust: {}", c_score);
    }

    #[test]
    fn test_ring_connected_to_seed_gets_trust() {
        // Network:
        // - Seed S → A (S trusts A)
        // - A → B → C → A (ring connected to seed via A)
        //
        // Expected: Ring agents get trust because connected to seed

        let s = agent(0);
        let a = agent(1);
        let b = agent(2);
        let c = agent(3);

        let edges = vec![
            TrustEdge::new(s, a, 1.0, 1000, None), // Seed trusts A
            TrustEdge::new(a, b, 1.0, 1000, None),
            TrustEdge::new(b, c, 1.0, 1000, None),
            TrustEdge::new(c, a, 1.0, 1000, None),
        ];
        let seeds = vec![(s, 1.0)];

        let result = compute_eigentrust_scores(&edges, &seeds, &EigenTrustConfig::default(), 1000);

        assert!(result.converged);

        // All agents should have non-trivial trust
        let s_score = result.state.get_score(&s);
        let a_score = result.state.get_score(&a);
        let b_score = result.state.get_score(&b);
        let c_score = result.state.get_score(&c);

        assert!(s_score > 0.0);
        assert!(a_score > 0.1, "Agent A connected to seed should have trust: {}", a_score);
        assert!(b_score > 0.05, "Agent B should have some trust: {}", b_score);
        assert!(c_score > 0.05, "Agent C should have some trust: {}", c_score);
    }

    #[test]
    fn test_multiple_seeds() {
        // Two seeds, each trusts one agent
        let s1 = agent(0);
        let s2 = agent(1);
        let a = agent(2);
        let b = agent(3);

        let edges =
            vec![TrustEdge::new(s1, a, 1.0, 1000, None), TrustEdge::new(s2, b, 1.0, 1000, None)];
        let seeds = vec![(s1, 1.0), (s2, 1.0)];

        let result = compute_eigentrust_scores(&edges, &seeds, &EigenTrustConfig::default(), 1000);

        assert!(result.converged);

        // Both seeds and their trusted agents should have trust
        let s1_score = result.state.get_score(&s1);
        let s2_score = result.state.get_score(&s2);
        let a_score = result.state.get_score(&a);
        let b_score = result.state.get_score(&b);

        // Seeds should have roughly equal trust (equal seed weight)
        assert!((s1_score - s2_score).abs() < 0.1);
        // Trusted agents should have roughly equal trust
        assert!((a_score - b_score).abs() < 0.1);
    }

    #[test]
    fn test_weighted_edges() {
        // Seed trusts A strongly, B weakly
        let s = agent(0);
        let a = agent(1);
        let b = agent(2);

        let edges = vec![
            TrustEdge::new(s, a, 0.9, 1000, None), // Strong trust
            TrustEdge::new(s, b, 0.1, 1000, None), // Weak trust
        ];
        let seeds = vec![(s, 1.0)];

        let result = compute_eigentrust_scores(&edges, &seeds, &EigenTrustConfig::default(), 1000);

        assert!(result.converged);

        let a_score = result.state.get_score(&a);
        let b_score = result.state.get_score(&b);

        // A should have significantly more trust than B
        assert!(a_score > b_score * 2.0, "A: {}, B: {}", a_score, b_score);
    }

    #[test]
    fn test_invalid_edges_ignored() {
        // Self-trust and zero-weight edges should be ignored
        let s = agent(0);
        let a = agent(1);

        let edges = vec![
            TrustEdge::new(s, a, 1.0, 1000, None), // Valid
            TrustEdge::new(a, a, 1.0, 1000, None), // Invalid: self-trust
            TrustEdge::new(s, a, 0.0, 1000, None), // Invalid: zero weight
        ];
        let seeds = vec![(s, 1.0)];

        let result = compute_eigentrust_scores(&edges, &seeds, &EigenTrustConfig::default(), 1000);

        assert!(result.converged);
        // Should not crash or produce weird results
        assert!(result.state.scores.len() >= 2);
    }

    #[test]
    fn test_convergence_within_max_iterations() {
        // A star topology should converge relatively quickly
        let seed = agent(0);
        let mut edges = Vec::new();

        // Create a star topology: seed trusts 10 agents
        for i in 1..=10 {
            edges.push(TrustEdge::new(seed, agent(i), 1.0, 1000, None));
        }

        let seeds = vec![(seed, 1.0)];
        let config = EigenTrustConfig::default();

        let result = compute_eigentrust_scores(&edges, &seeds, &config, 1000);

        assert!(
            result.converged,
            "Should converge, got delta={} after {} iterations",
            result.state.convergence_delta, result.state.iterations
        );
    }

    #[test]
    fn test_scores_sum_to_one() {
        let s = agent(0);
        let a = agent(1);
        let b = agent(2);

        let edges =
            vec![TrustEdge::new(s, a, 1.0, 1000, None), TrustEdge::new(a, b, 1.0, 1000, None)];
        let seeds = vec![(s, 1.0)];

        let result = compute_eigentrust_scores(&edges, &seeds, &EigenTrustConfig::default(), 1000);

        let sum: f32 = result.state.scores.iter().map(|s| s.score).sum();
        assert!((sum - 1.0).abs() < 0.01, "Scores should sum to 1.0, got {}", sum);
    }
}
