//! Synor node implementation.
//!
//! The node orchestrates all components: storage, networking, consensus, RPC, etc.
#![allow(dead_code)]

use std::sync::Arc;

use tokio::sync::{broadcast, RwLock};
use tracing::{info, warn};

use crate::config::NodeConfig;
use crate::services::{
    ConsensusService, ContractService, MempoolService, MinerService, NetworkService, RpcService,
    StorageService, SyncService,
};

/// Node state.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NodeState {
    /// Node is starting up.
    Starting,
    /// Node is syncing with the network.
    Syncing,
    /// Node is fully synced and running.
    Running,
    /// Node is shutting down.
    Stopping,
    /// Node has stopped.
    Stopped,
}

/// Synor blockchain node.
pub struct SynorNode {
    /// Configuration.
    config: NodeConfig,

    /// Current state.
    state: RwLock<NodeState>,

    /// Storage service.
    storage: Arc<StorageService>,

    /// Network service.
    network: Arc<NetworkService>,

    /// Sync service.
    sync: Arc<SyncService>,

    /// Consensus service.
    consensus: Arc<ConsensusService>,

    /// Mempool service.
    mempool: Arc<MempoolService>,

    /// RPC service.
    rpc: Arc<RpcService>,

    /// Contract service.
    contract: Arc<ContractService>,

    /// Miner service.
    miner: Option<Arc<MinerService>>,

    /// Shutdown signal sender.
    shutdown_tx: broadcast::Sender<()>,
}

impl SynorNode {
    /// Creates a new node.
    pub async fn new(config: NodeConfig) -> anyhow::Result<Self> {
        info!("Initializing Synor node...");

        // Create data directories
        std::fs::create_dir_all(&config.data_dir)?;
        std::fs::create_dir_all(config.blocks_path())?;
        std::fs::create_dir_all(config.chainstate_path())?;
        std::fs::create_dir_all(config.contracts_path())?;

        // Create shutdown channel
        let (shutdown_tx, _) = broadcast::channel(1);

        // Initialize storage
        info!("Initializing storage...");
        let storage = Arc::new(StorageService::new(&config).await?);

        // Initialize network
        info!("Initializing P2P network...");
        let network = Arc::new(NetworkService::new(&config, shutdown_tx.subscribe()).await?);

        // Initialize consensus (before sync, as sync depends on consensus)
        info!("Initializing consensus...");
        let consensus = Arc::new(ConsensusService::new(
            storage.clone(),
            &config,
            shutdown_tx.subscribe(),
        )?);

        // Initialize sync (needs storage, network, and consensus)
        info!("Initializing sync service...");
        let sync = Arc::new(SyncService::new(
            storage.clone(),
            network.clone(),
            consensus.clone(),
            &config,
            shutdown_tx.subscribe(),
        )?);

        // Initialize mempool
        info!("Initializing mempool...");
        let mempool = Arc::new(MempoolService::new(
            consensus.clone(),
            &config,
            shutdown_tx.subscribe(),
        )?);

        // Initialize contract service
        info!("Initializing contract service...");
        let contract = Arc::new(ContractService::new(config.chain_id));

        // Initialize RPC
        info!("Initializing RPC server...");
        let rpc = Arc::new(RpcService::new(
            storage.clone(),
            network.clone(),
            consensus.clone(),
            mempool.clone(),
            contract.clone(),
            &config,
        )?);

        // Initialize miner if enabled
        let miner = if config.mining.enabled {
            info!("Initializing miner...");
            Some(Arc::new(
                MinerService::new(
                    consensus.clone(),
                    mempool.clone(),
                    &config,
                    shutdown_tx.subscribe(),
                )
                .await?,
            ))
        } else {
            None
        };

        Ok(SynorNode {
            config,
            state: RwLock::new(NodeState::Starting),
            storage,
            network,
            sync,
            consensus,
            mempool,
            rpc,
            contract,
            miner,
            shutdown_tx,
        })
    }

    /// Starts all node services.
    pub async fn start(&self) -> anyhow::Result<()> {
        info!("Starting Synor node services...");

        // Update state
        *self.state.write().await = NodeState::Starting;

        // Start storage
        self.storage.start().await?;
        info!("Storage service started");

        // Start network
        self.network.start().await?;
        info!(
            addr = %self.config.p2p.listen_addr,
            "P2P network started"
        );

        // Start sync
        self.sync.start().await?;
        info!("Sync service started");

        // Start consensus
        self.consensus.start().await?;
        info!("Consensus service started");

        // Start mempool
        self.mempool.start().await?;
        self.mempool.spawn_cleanup_task();
        info!("Mempool service started");

        // Start contract service (needs database from storage)
        if let Some(db) = self.storage.database().await {
            self.contract.start(db).await?;
            info!("Contract service started");
        }

        // Start RPC
        self.rpc.start().await?;
        info!(
            http = %self.config.rpc.http_addr,
            ws = %self.config.rpc.ws_addr,
            "RPC server started"
        );

        // Start miner if enabled
        if let Some(ref miner) = self.miner {
            miner.start().await?;
            info!(threads = self.config.mining.threads, "Miner started");
        }

        // Update state
        *self.state.write().await = NodeState::Running;

        info!("All services started successfully");
        Ok(())
    }

    /// Stops all node services.
    pub async fn stop(&self) -> anyhow::Result<()> {
        info!("Stopping Synor node services...");

        // Update state
        *self.state.write().await = NodeState::Stopping;

        // Send shutdown signal
        let _ = self.shutdown_tx.send(());

        // Stop miner first
        if let Some(ref miner) = self.miner {
            if let Err(e) = miner.stop().await {
                warn!("Error stopping miner: {}", e);
            }
        }

        // Stop RPC
        if let Err(e) = self.rpc.stop().await {
            warn!("Error stopping RPC: {}", e);
        }

        // Stop contract service
        if let Err(e) = self.contract.stop().await {
            warn!("Error stopping contract service: {}", e);
        }

        // Stop mempool
        if let Err(e) = self.mempool.stop().await {
            warn!("Error stopping mempool: {}", e);
        }

        // Stop consensus
        if let Err(e) = self.consensus.stop().await {
            warn!("Error stopping consensus: {}", e);
        }

        // Stop sync
        if let Err(e) = self.sync.stop().await {
            warn!("Error stopping sync: {}", e);
        }

        // Stop network
        if let Err(e) = self.network.stop().await {
            warn!("Error stopping network: {}", e);
        }

        // Stop storage last
        if let Err(e) = self.storage.stop().await {
            warn!("Error stopping storage: {}", e);
        }

        // Update state
        *self.state.write().await = NodeState::Stopped;

        info!("All services stopped");
        Ok(())
    }

    /// Returns current node state.
    pub async fn state(&self) -> NodeState {
        *self.state.read().await
    }

    /// Returns node configuration.
    pub fn config(&self) -> &NodeConfig {
        &self.config
    }

    /// Returns storage service.
    pub fn storage(&self) -> &Arc<StorageService> {
        &self.storage
    }

    /// Returns network service.
    pub fn network(&self) -> &Arc<NetworkService> {
        &self.network
    }

    /// Returns consensus service.
    pub fn consensus(&self) -> &Arc<ConsensusService> {
        &self.consensus
    }

    /// Returns mempool service.
    pub fn mempool(&self) -> &Arc<MempoolService> {
        &self.mempool
    }

    /// Returns RPC service.
    pub fn rpc(&self) -> &Arc<RpcService> {
        &self.rpc
    }

    /// Returns sync service.
    pub fn sync(&self) -> &Arc<SyncService> {
        &self.sync
    }

    /// Returns miner service if enabled.
    pub fn miner(&self) -> Option<&Arc<MinerService>> {
        self.miner.as_ref()
    }

    /// Returns contract service.
    pub fn contract(&self) -> &Arc<ContractService> {
        &self.contract
    }
}

/// Node info for RPC.
#[derive(Clone, Debug)]
pub struct NodeInfo {
    /// Node version.
    pub version: String,
    /// Network name.
    pub network: String,
    /// Chain ID.
    pub chain_id: u64,
    /// Current block height.
    pub block_height: u64,
    /// Current blue score.
    pub blue_score: u64,
    /// Number of connected peers.
    pub peer_count: usize,
    /// Is syncing.
    pub is_syncing: bool,
    /// Is mining.
    pub is_mining: bool,
}

impl SynorNode {
    /// Gets current node info.
    pub async fn info(&self) -> NodeInfo {
        let state = self.state().await;

        NodeInfo {
            version: env!("CARGO_PKG_VERSION").to_string(),
            network: self.config.network.clone(),
            chain_id: self.config.chain_id,
            block_height: self.consensus.current_height().await,
            blue_score: self.consensus.current_blue_score().await,
            peer_count: self.network.peer_count().await,
            is_syncing: state == NodeState::Syncing,
            is_mining: self.miner.is_some() && self.config.mining.enabled,
        }
    }
}

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

    // ==================== NodeState Tests ====================

    #[test]
    fn test_node_state_starting() {
        let state = NodeState::Starting;
        assert_eq!(state, NodeState::Starting);
        assert_ne!(state, NodeState::Running);
        assert_ne!(state, NodeState::Stopped);
    }

    #[test]
    fn test_node_state_syncing() {
        let state = NodeState::Syncing;
        assert_eq!(state, NodeState::Syncing);
        assert_ne!(state, NodeState::Running);
    }

    #[test]
    fn test_node_state_running() {
        let state = NodeState::Running;
        assert_eq!(state, NodeState::Running);
        assert_ne!(state, NodeState::Stopping);
    }

    #[test]
    fn test_node_state_stopping() {
        let state = NodeState::Stopping;
        assert_eq!(state, NodeState::Stopping);
        assert_ne!(state, NodeState::Stopped);
    }

    #[test]
    fn test_node_state_stopped() {
        let state = NodeState::Stopped;
        assert_eq!(state, NodeState::Stopped);
        assert_ne!(state, NodeState::Starting);
    }

    #[test]
    fn test_node_state_clone() {
        let state = NodeState::Running;
        let cloned = state;
        assert_eq!(state, cloned);
    }

    #[test]
    fn test_node_state_debug() {
        let state = NodeState::Running;
        let debug_str = format!("{:?}", state);
        assert_eq!(debug_str, "Running");
    }

    #[test]
    fn test_node_state_all_variants_are_distinct() {
        let states = [NodeState::Starting,
            NodeState::Syncing,
            NodeState::Running,
            NodeState::Stopping,
            NodeState::Stopped];

        for i in 0..states.len() {
            for j in (i + 1)..states.len() {
                assert_ne!(states[i], states[j], "All states should be distinct");
            }
        }
    }

    #[test]
    fn test_node_state_copy_semantics() {
        let state1 = NodeState::Running;
        let state2 = state1;
        assert_eq!(state1, state2);
    }

    // ==================== NodeInfo Tests ====================

    #[test]
    fn test_node_info_default_creation() {
        let info = NodeInfo {
            version: "0.1.0".to_string(),
            network: "mainnet".to_string(),
            chain_id: 0,
            block_height: 0,
            blue_score: 0,
            peer_count: 0,
            is_syncing: false,
            is_mining: false,
        };

        assert_eq!(info.version, "0.1.0");
        assert_eq!(info.network, "mainnet");
        assert_eq!(info.chain_id, 0);
        assert_eq!(info.block_height, 0);
        assert!(!info.is_syncing);
        assert!(!info.is_mining);
    }

    #[test]
    fn test_node_info_clone() {
        let info = NodeInfo {
            version: "0.1.0".to_string(),
            network: "testnet".to_string(),
            chain_id: 1,
            block_height: 1000,
            blue_score: 500,
            peer_count: 10,
            is_syncing: true,
            is_mining: false,
        };

        let cloned = info.clone();
        assert_eq!(cloned.version, info.version);
        assert_eq!(cloned.network, info.network);
        assert_eq!(cloned.chain_id, info.chain_id);
        assert_eq!(cloned.block_height, info.block_height);
        assert_eq!(cloned.blue_score, info.blue_score);
        assert_eq!(cloned.peer_count, info.peer_count);
        assert_eq!(cloned.is_syncing, info.is_syncing);
        assert_eq!(cloned.is_mining, info.is_mining);
    }

    #[test]
    fn test_node_info_debug() {
        let info = NodeInfo {
            version: "0.1.0".to_string(),
            network: "devnet".to_string(),
            chain_id: 2,
            block_height: 100,
            blue_score: 50,
            peer_count: 5,
            is_syncing: false,
            is_mining: true,
        };

        let debug_str = format!("{:?}", info);
        assert!(debug_str.contains("version"));
        assert!(debug_str.contains("network"));
        assert!(debug_str.contains("devnet"));
    }

    #[test]
    fn test_node_info_all_networks() {
        for (network, chain_id) in [("mainnet", 0), ("testnet", 1), ("devnet", 2)] {
            let info = NodeInfo {
                version: "0.1.0".to_string(),
                network: network.to_string(),
                chain_id,
                block_height: 0,
                blue_score: 0,
                peer_count: 0,
                is_syncing: false,
                is_mining: false,
            };

            assert_eq!(info.network, network);
            assert_eq!(info.chain_id, chain_id);
        }
    }

    #[test]
    fn test_node_info_syncing_state() {
        let syncing_info = NodeInfo {
            version: "0.1.0".to_string(),
            network: "mainnet".to_string(),
            chain_id: 0,
            block_height: 1000,
            blue_score: 1000,
            peer_count: 5,
            is_syncing: true,
            is_mining: false,
        };

        assert!(syncing_info.is_syncing);
        assert!(!syncing_info.is_mining);
    }

    #[test]
    fn test_node_info_mining_state() {
        let mining_info = NodeInfo {
            version: "0.1.0".to_string(),
            network: "mainnet".to_string(),
            chain_id: 0,
            block_height: 10000,
            blue_score: 10000,
            peer_count: 50,
            is_syncing: false,
            is_mining: true,
        };

        assert!(!mining_info.is_syncing);
        assert!(mining_info.is_mining);
    }

    #[test]
    fn test_node_info_large_values() {
        let info = NodeInfo {
            version: "1.0.0".to_string(),
            network: "mainnet".to_string(),
            chain_id: 0,
            block_height: u64::MAX,
            blue_score: u64::MAX,
            peer_count: usize::MAX,
            is_syncing: false,
            is_mining: false,
        };

        assert_eq!(info.block_height, u64::MAX);
        assert_eq!(info.blue_score, u64::MAX);
        assert_eq!(info.peer_count, usize::MAX);
    }

    // ==================== NodeConfig Integration Tests ====================

    #[test]
    fn test_node_config_for_network_creates_valid_config() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert_eq!(config.network, "mainnet");
        assert_eq!(config.chain_id, 0);
    }

    #[test]
    fn test_node_config_mining_disabled_by_default() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert!(!config.mining.enabled);
        assert!(config.mining.coinbase_address.is_none());
    }

    #[test]
    fn test_node_config_mining_enabled_configuration() {
        let config = crate::config::NodeConfig::for_network("mainnet")
            .unwrap()
            .with_mining(true, Some("synor:test".to_string()), 4);

        assert!(config.mining.enabled);
        assert_eq!(config.mining.coinbase_address, Some("synor:test".to_string()));
        assert_eq!(config.mining.threads, 4);
    }

    // ==================== Directory Creation Tests ====================

    #[test]
    fn test_directory_paths_are_correct() {
        let dir = tempdir().unwrap();
        let mut config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        config.data_dir = dir.path().to_path_buf();

        let blocks_path = config.blocks_path();
        let chainstate_path = config.chainstate_path();
        let contracts_path = config.contracts_path();

        assert_eq!(blocks_path, dir.path().join("blocks"));
        assert_eq!(chainstate_path, dir.path().join("chainstate"));
        assert_eq!(contracts_path, dir.path().join("contracts"));
    }

    #[test]
    fn test_directories_can_be_created() {
        let dir = tempdir().unwrap();
        let mut config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        config.data_dir = dir.path().to_path_buf();

        std::fs::create_dir_all(&config.data_dir).unwrap();
        std::fs::create_dir_all(config.blocks_path()).unwrap();
        std::fs::create_dir_all(config.chainstate_path()).unwrap();
        std::fs::create_dir_all(config.contracts_path()).unwrap();

        assert!(config.data_dir.exists());
        assert!(config.blocks_path().exists());
        assert!(config.chainstate_path().exists());
        assert!(config.contracts_path().exists());
    }

    // ==================== State Transition Tests ====================

    #[test]
    fn test_valid_state_transition_starting_to_running() {
        let initial = NodeState::Starting;
        let target = NodeState::Running;
        assert_ne!(initial, target);
    }

    #[test]
    fn test_valid_state_transition_running_to_stopping() {
        let initial = NodeState::Running;
        let target = NodeState::Stopping;
        assert_ne!(initial, target);
    }

    #[test]
    fn test_valid_state_transition_stopping_to_stopped() {
        let initial = NodeState::Stopping;
        let target = NodeState::Stopped;
        assert_ne!(initial, target);
    }

    #[test]
    fn test_valid_state_transition_starting_to_syncing() {
        let initial = NodeState::Starting;
        let target = NodeState::Syncing;
        assert_ne!(initial, target);
    }

    #[test]
    fn test_valid_state_transition_syncing_to_running() {
        let initial = NodeState::Syncing;
        let target = NodeState::Running;
        assert_ne!(initial, target);
    }

    // ==================== RPC Address Configuration Tests ====================

    #[test]
    fn test_rpc_addresses_for_mainnet() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert_eq!(config.rpc.http_addr, "127.0.0.1:16110");
        assert_eq!(config.rpc.ws_addr, "127.0.0.1:16111");
    }

    #[test]
    fn test_rpc_addresses_for_testnet() {
        let config = crate::config::NodeConfig::for_network("testnet").unwrap();
        assert_eq!(config.rpc.http_addr, "127.0.0.1:17110");
        assert_eq!(config.rpc.ws_addr, "127.0.0.1:17111");
    }

    #[test]
    fn test_rpc_addresses_for_devnet() {
        let config = crate::config::NodeConfig::for_network("devnet").unwrap();
        assert_eq!(config.rpc.http_addr, "127.0.0.1:18110");
        assert_eq!(config.rpc.ws_addr, "127.0.0.1:18111");
    }

    // ==================== Consensus Configuration Tests ====================

    #[test]
    fn test_consensus_ghostdag_k_parameter() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert_eq!(config.consensus.ghostdag_k, 18);
    }

    #[test]
    fn test_consensus_target_time_mainnet() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert_eq!(config.consensus.target_time_ms, 1000);
    }

    #[test]
    fn test_consensus_target_time_testnet() {
        let config = crate::config::NodeConfig::for_network("testnet").unwrap();
        assert_eq!(config.consensus.target_time_ms, 100);
    }

    // ==================== Mining Configuration Tests ====================

    #[test]
    fn test_mining_default_extra_data() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert_eq!(config.mining.extra_data, "synord");
    }

    #[test]
    fn test_mining_default_intensity() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert_eq!(config.mining.intensity, 1.0);
    }

    #[test]
    fn test_mining_gpu_disabled_by_default() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert!(!config.mining.gpu_enabled);
        assert!(config.mining.gpu_devices.is_empty());
    }

    // ==================== VM Configuration Tests ====================

    #[test]
    fn test_vm_enabled_by_default() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert!(config.vm.enabled);
    }

    #[test]
    fn test_vm_max_contract_size() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert_eq!(config.vm.max_contract_size, 24 * 1024);
    }

    #[test]
    fn test_vm_execution_timeout() {
        let config = crate::config::NodeConfig::for_network("mainnet").unwrap();
        assert_eq!(config.vm.execution_timeout_ms, 5000);
    }

    // ==================== Network Port Tests ====================

    #[test]
    fn test_all_networks_have_different_ports() {
        let mainnet = crate::config::NodeConfig::for_network("mainnet").unwrap();
        let testnet = crate::config::NodeConfig::for_network("testnet").unwrap();
        let devnet = crate::config::NodeConfig::for_network("devnet").unwrap();

        assert_ne!(mainnet.rpc.http_addr, testnet.rpc.http_addr);
        assert_ne!(mainnet.rpc.http_addr, devnet.rpc.http_addr);
        assert_ne!(testnet.rpc.http_addr, devnet.rpc.http_addr);

        assert_ne!(mainnet.rpc.ws_addr, testnet.rpc.ws_addr);
        assert_ne!(mainnet.rpc.ws_addr, devnet.rpc.ws_addr);
        assert_ne!(testnet.rpc.ws_addr, devnet.rpc.ws_addr);
    }

    #[test]
    fn test_all_networks_have_different_data_dirs() {
        let mainnet = crate::config::NodeConfig::for_network("mainnet").unwrap();
        let testnet = crate::config::NodeConfig::for_network("testnet").unwrap();
        let devnet = crate::config::NodeConfig::for_network("devnet").unwrap();

        assert_ne!(mainnet.data_dir, testnet.data_dir);
        assert_ne!(mainnet.data_dir, devnet.data_dir);
        assert_ne!(testnet.data_dir, devnet.data_dir);
    }

    // ==================== Version Tests ====================

    #[test]
    fn test_cargo_pkg_version_is_defined() {
        let version = env!("CARGO_PKG_VERSION");
        assert!(!version.is_empty());
        let parts: Vec<&str> = version.split('.').collect();
        assert!(parts.len() >= 2, "Version should have at least major.minor");
    }
}
