//! ChaosNode: A simulated cluster node for chaos testing.
//!
//! Extends the SimNode pattern from partition_tolerance tests with
//! fault injection capabilities.

mod helpers;
mod node_ops;
mod node_state;

use crate::error::Result;
use crate::fault_injection::{ClockController, NetworkController, SkewedHlc};
use std::collections::HashMap;
use std::sync::Arc;
use stemedb_cluster::config::SwimConfig;
use stemedb_cluster::membership::{NodeId, SwimMembership};
use stemedb_cluster::sharding::RangeRouter;
use stemedb_merkle::MerkleTree;
use stemedb_storage::crdt::CrdtAssertionStore;
use stemedb_storage::HybridStore;
use tempfile::TempDir;

pub(crate) use helpers::{rand_hash, test_node_id, test_node_info};

/// A simulated cluster node for chaos testing.
///
/// Provides the same functionality as `SimNode` from partition_tolerance tests
/// but with additional support for fault injection via shared controllers.
///
/// # Fault Injection
///
/// The node respects the `NetworkController` and `ClockController` settings:
/// - Sync operations check for partitions before proceeding
/// - HLC timestamps can be skewed via the clock controller
/// - Network latency and drops are simulated during sync
pub struct ChaosNode {
    /// Node index in the cluster (0-based).
    pub index: usize,

    /// Unique node identifier.
    pub id: NodeId,

    /// SWIM membership manager.
    membership: Arc<SwimMembership>,

    /// Shard router for determining write locations.
    router: Arc<RangeRouter>,

    /// Underlying storage.
    #[allow(dead_code)]
    store: Arc<HybridStore>,

    /// CRDT store for assertion operations.
    pub(crate) crdt_store: Arc<CrdtAssertionStore<HybridStore>>,

    /// Merkle tree for convergence verification.
    pub(crate) merkle_tree: MerkleTree,

    /// Maps assertion hash -> (subject, data) for sync operations.
    pub(crate) hash_to_data: HashMap<[u8; 32], (String, Vec<u8>)>,

    /// Network fault controller (shared across cluster).
    network_controller: Arc<NetworkController>,

    /// Clock fault controller (shared across cluster).
    clock_controller: Arc<ClockController>,

    /// Skewed HLC for this node.
    hlc: SkewedHlc,

    /// Temp directory for storage (kept alive for node lifetime).
    _temp_dir: TempDir,

    /// Whether this node is "alive" (false = simulating node failure).
    alive: bool,
}

impl ChaosNode {
    /// Creates a new chaos node with the given index.
    ///
    /// # Arguments
    ///
    /// * `index` - Zero-based index in the cluster
    /// * `network_controller` - Shared network fault controller
    /// * `clock_controller` - Shared clock fault controller
    pub fn new(
        index: usize,
        network_controller: Arc<NetworkController>,
        clock_controller: Arc<ClockController>,
    ) -> Result<Self> {
        let id = test_node_id(index as u8);
        let info = test_node_info(index as u8);

        let temp_dir = TempDir::new().map_err(|e| {
            crate::error::ChaosError::InvalidConfig(format!("Failed to create temp dir: {e}"))
        })?;

        let store = Arc::new(HybridStore::open(temp_dir.path())?);
        let crdt_store = Arc::new(CrdtAssertionStore::new(store.clone(), *id.as_bytes()));

        let membership = Arc::new(SwimMembership::new(info, SwimConfig::default()));
        let router = Arc::new(RangeRouter::new(id));

        // Use create_hlc_shared so that inject_skew affects this HLC
        let hlc = clock_controller.create_hlc_shared(index);

        Ok(Self {
            index,
            id,
            membership,
            router,
            store,
            crdt_store,
            merkle_tree: MerkleTree::new(),
            hash_to_data: HashMap::new(),
            network_controller,
            clock_controller,
            hlc,
            _temp_dir: temp_dir,
            alive: true,
        })
    }

    /// Returns this node's unique identifier.
    pub fn node_id(&self) -> NodeId {
        self.id
    }
}

impl std::fmt::Debug for ChaosNode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ChaosNode")
            .field("index", &self.index)
            .field("id", &self.id.short_hex())
            .field("alive", &self.alive)
            .field("assertion_count", &self.assertion_count())
            .field("clock_offset_ms", &self.clock_offset_ms())
            .finish()
    }
}

#[cfg(test)]
mod tests;
