//! Convergence checking and assertions.

use crate::error::{ChaosError, Result};
use std::collections::HashSet;
use std::time::Duration;
use tracing::{info, instrument};

use super::types::TestCluster;

/// Extension trait for convergence operations.
pub trait TestClusterConvergenceExt {
    /// Waits for all alive nodes to converge (same canonical Merkle root).
    ///
    /// Polls at regular intervals and syncs until convergence or timeout.
    ///
    /// # Arguments
    ///
    /// * `timeout` - Maximum time to wait for convergence
    fn wait_for_convergence(
        &mut self,
        timeout: Duration,
    ) -> impl std::future::Future<Output = Result<()>> + Send;

    /// Checks if all alive nodes have converged (same canonical Merkle root).
    fn is_converged(&self) -> bool;

    /// Asserts that all alive nodes have converged.
    ///
    /// # Panics
    ///
    /// Panics with diagnostic information if nodes have not converged.
    /// This is intentional for test assertions.
    fn assert_converged(&self);

    /// Gets the canonical Merkle roots of all alive nodes.
    ///
    /// Useful for debugging convergence issues.
    fn get_roots(&self) -> Vec<(usize, Option<[u8; 32]>)>;

    /// Gets assertion counts for all nodes.
    fn get_assertion_counts(&self) -> Vec<(usize, usize, bool)>;
}

impl TestClusterConvergenceExt for TestCluster {
    #[instrument(skip(self))]
    async fn wait_for_convergence(&mut self, timeout: Duration) -> Result<()> {
        use super::sync::TestClusterSyncExt;

        let deadline = tokio::time::Instant::now() + timeout;
        let poll_interval = Duration::from_millis(100);

        while tokio::time::Instant::now() < deadline {
            // Sync all nodes
            self.sync_all().await?;

            // Check convergence
            if self.is_converged() {
                info!("Cluster converged");
                return Ok(());
            }

            tokio::time::sleep(poll_interval).await;
        }

        Err(ChaosError::ConvergenceTimeout(timeout))
    }

    fn is_converged(&self) -> bool {
        let alive_nodes: Vec<_> = self.nodes.iter().filter(|n| n.is_alive()).collect();

        if alive_nodes.len() <= 1 {
            return true;
        }

        let first_root = alive_nodes[0].canonical_merkle_root();

        alive_nodes.iter().skip(1).all(|n| n.canonical_merkle_root() == first_root)
    }

    #[allow(clippy::panic)]
    fn assert_converged(&self) {
        let alive_nodes: Vec<_> = self.nodes.iter().filter(|n| n.is_alive()).collect();

        if alive_nodes.len() <= 1 {
            return;
        }

        let first_root = alive_nodes[0].canonical_merkle_root();
        let first_idx = alive_nodes[0].index;

        for node in &alive_nodes[1..] {
            let root = node.canonical_merkle_root();
            if root != first_root {
                let first_hex =
                    first_root.map(|r| hex::encode(&r[..8])).unwrap_or_else(|| "empty".to_string());
                let node_hex =
                    root.map(|r| hex::encode(&r[..8])).unwrap_or_else(|| "empty".to_string());

                panic!(
                    "Cluster not converged!\n\
                     Node {} root: {} (count: {})\n\
                     Node {} root: {} (count: {})",
                    first_idx,
                    first_hex,
                    alive_nodes[0].assertion_count(),
                    node.index,
                    node_hex,
                    node.assertion_count()
                );
            }
        }
    }

    fn get_roots(&self) -> Vec<(usize, Option<[u8; 32]>)> {
        self.nodes
            .iter()
            .filter(|n| n.is_alive())
            .map(|n| (n.index, n.canonical_merkle_root()))
            .collect()
    }

    fn get_assertion_counts(&self) -> Vec<(usize, usize, bool)> {
        self.nodes.iter().map(|n| (n.index, n.assertion_count(), n.is_alive())).collect()
    }
}

/// Summary of cluster state for debugging and assertions.
#[derive(Debug, Clone)]
pub struct ClusterSummary {
    /// Total number of nodes.
    pub node_count: usize,
    /// Number of alive nodes.
    pub alive_count: usize,
    /// Total assertions across all nodes.
    pub total_assertions: usize,
    /// Whether alive nodes have converged.
    pub converged: bool,
    /// Number of unique Merkle roots among alive nodes.
    pub unique_root_count: usize,
    /// Total messages dropped by network controller.
    pub messages_dropped: u64,
    /// Total messages delayed by network controller.
    pub messages_delayed: u64,
    /// Maximum configured clock skew in milliseconds.
    pub max_clock_skew_ms: i64,
}

/// Extension trait for cluster summary.
pub trait TestClusterSummaryExt {
    /// Returns a summary of cluster state for debugging.
    fn summary(&self) -> ClusterSummary;
}

impl TestClusterSummaryExt for TestCluster {
    fn summary(&self) -> ClusterSummary {
        let alive_count = self.nodes.iter().filter(|n| n.is_alive()).count();
        let total_assertions: usize = self.nodes.iter().map(|n| n.assertion_count()).sum();
        let roots = self.get_roots();
        let converged = self.is_converged();

        let unique_roots: HashSet<_> =
            roots.iter().filter_map(|(_, r)| r.map(|r| hex::encode(&r[..8]))).collect();

        ClusterSummary {
            node_count: self.nodes.len(),
            alive_count,
            total_assertions,
            converged,
            unique_root_count: unique_roots.len(),
            messages_dropped: self.network.total_dropped(),
            messages_delayed: self.network.total_delayed(),
            max_clock_skew_ms: self.clock.max_skew_ms(),
        }
    }
}
