//! Integration tests for SynorNode lifecycle.
//!
//! These tests verify:
//! - Node creation and configuration
//! - Service startup and shutdown
//! - State transitions
//! - Basic RPC connectivity
//! - Error handling and recovery

use std::path::PathBuf;
use std::time::Duration;

use tempfile::TempDir;
use tokio::time::timeout;

use synord::config::NodeConfig;
use synord::node::{NodeState, SynorNode};

/// Test timeout for async operations.
const TEST_TIMEOUT: Duration = Duration::from_secs(30);

/// Creates a test configuration with a temporary data directory.
fn create_test_config(temp_dir: &TempDir) -> NodeConfig {
    let mut config = NodeConfig::for_network("devnet").unwrap();

    // Use temporary directory
    config.data_dir = temp_dir.path().to_path_buf();

    // Disable mining for most tests
    config.mining.enabled = false;

    // Use random ports to avoid conflicts
    let port_base = 16000 + (std::process::id() % 1000) as u16;
    config.p2p.listen_addr = format!("127.0.0.1:{}", port_base);
    config.rpc.http_addr = format!("127.0.0.1:{}", port_base + 10);
    config.rpc.ws_addr = format!("127.0.0.1:{}", port_base + 11);

    // No seeds for isolated testing
    config.p2p.seeds = vec![];

    config
}

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

#[test]
fn test_config_for_networks() {
    // Mainnet
    let config = NodeConfig::for_network("mainnet").unwrap();
    assert_eq!(config.chain_id, 1);
    assert_eq!(config.network, "mainnet");

    // Testnet
    let config = NodeConfig::for_network("testnet").unwrap();
    assert_eq!(config.chain_id, 2);
    assert_eq!(config.network, "testnet");

    // Devnet
    let config = NodeConfig::for_network("devnet").unwrap();
    assert_eq!(config.chain_id, 3);
    assert_eq!(config.network, "devnet");
}

#[test]
fn test_config_unknown_network() {
    let result = NodeConfig::for_network("unknown");
    assert!(result.is_err());
}

#[test]
fn test_config_save_and_load() {
    let temp_dir = TempDir::new().unwrap();
    let config_path = temp_dir.path().join("config.toml");

    let config = NodeConfig::for_network("devnet").unwrap();
    config.save(&config_path).unwrap();

    let loaded = NodeConfig::load(&config_path).unwrap();
    assert_eq!(loaded.network, config.network);
    assert_eq!(loaded.chain_id, config.chain_id);
}

#[test]
fn test_config_paths() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

    assert_eq!(config.blocks_path(), temp_dir.path().join("blocks"));
    assert_eq!(config.chainstate_path(), temp_dir.path().join("chainstate"));
    assert_eq!(config.contracts_path(), temp_dir.path().join("contracts"));
    assert_eq!(config.keys_path(), temp_dir.path().join("keys"));
}

#[test]
fn test_config_with_builders() {
    let config = NodeConfig::for_network("devnet")
        .unwrap()
        .with_data_dir(Some(PathBuf::from("/tmp/test")))
        .with_rpc("0.0.0.0", 8080, 8081)
        .with_p2p("0.0.0.0", 9000, vec!["peer1:9000".to_string()])
        .with_mining(true, Some("synor1abc...".to_string()), 4);

    assert_eq!(config.data_dir, PathBuf::from("/tmp/test"));
    assert_eq!(config.rpc.http_addr, "0.0.0.0:8080");
    assert_eq!(config.rpc.ws_addr, "0.0.0.0:8081");
    assert_eq!(config.p2p.listen_addr, "0.0.0.0:9000");
    assert!(config.mining.enabled);
    assert_eq!(config.mining.threads, 4);
}

// ==================== Node Lifecycle Tests ====================

#[tokio::test]
async fn test_node_creation() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

    let result = timeout(TEST_TIMEOUT, SynorNode::new(config)).await;
    assert!(result.is_ok(), "Node creation timed out");

    let node_result = result.unwrap();
    assert!(
        node_result.is_ok(),
        "Node creation failed: {:?}",
        node_result.err()
    );
}

#[tokio::test]
async fn test_node_initial_state() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

    let node = SynorNode::new(config).await.unwrap();
    let state = node.state().await;

    assert_eq!(state, NodeState::Starting);
}

#[tokio::test]
async fn test_node_start_stop() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

    let node = SynorNode::new(config).await.unwrap();

    // Start the node
    let start_result = timeout(TEST_TIMEOUT, node.start()).await;
    assert!(start_result.is_ok(), "Node start timed out");
    assert!(start_result.unwrap().is_ok(), "Node start failed");

    let state = node.state().await;
    assert_eq!(state, NodeState::Running);

    // Stop the node
    let stop_result = timeout(TEST_TIMEOUT, node.stop()).await;
    assert!(stop_result.is_ok(), "Node stop timed out");
    assert!(stop_result.unwrap().is_ok(), "Node stop failed");

    let state = node.state().await;
    assert_eq!(state, NodeState::Stopped);
}

#[tokio::test]
async fn test_node_info() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);
    let expected_chain_id = config.chain_id;

    let node = SynorNode::new(config).await.unwrap();
    node.start().await.unwrap();

    let info = node.info().await;

    assert_eq!(info.chain_id, expected_chain_id);
    assert_eq!(info.network, "devnet");
    assert!(!info.is_mining); // Mining disabled
    assert!(info.peer_count == 0); // No seeds

    node.stop().await.unwrap();
}

#[tokio::test]
async fn test_node_services_accessible() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

    let node = SynorNode::new(config).await.unwrap();

    // Services should be accessible even before start
    // These return &Arc<T> directly, not Option
    let _ = node.storage(); // Storage is always created
    let _ = node.network();
    let _ = node.consensus();
    let _ = node.mempool();
    let _ = node.rpc();
    let _ = node.contract();
    assert!(node.miner().is_none()); // Mining disabled (this one is Option)
}

#[tokio::test]
async fn test_node_with_mining() {
    let temp_dir = TempDir::new().unwrap();
    let mut config = create_test_config(&temp_dir);

    // Enable mining
    config.mining.enabled = true;
    config.mining.coinbase_address = Some("tsynor1test...".to_string());
    config.mining.threads = 1;

    let node = SynorNode::new(config).await.unwrap();

    // Miner should be present
    assert!(node.miner().is_some());

    node.start().await.unwrap();

    let info = node.info().await;
    assert!(info.is_mining);

    node.stop().await.unwrap();
}

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

#[tokio::test]
async fn test_node_creates_directories() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

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

    // Directories shouldn't exist yet
    assert!(!blocks_path.exists());

    // Create node (this should create directories)
    let _node = SynorNode::new(config).await.unwrap();

    // Directories should now exist
    assert!(blocks_path.exists(), "blocks directory not created");
    assert!(chainstate_path.exists(), "chainstate directory not created");
    assert!(contracts_path.exists(), "contracts directory not created");
}

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

#[tokio::test]
async fn test_state_transitions() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

    let node = SynorNode::new(config).await.unwrap();

    // Initial state
    assert_eq!(node.state().await, NodeState::Starting);

    // After start
    node.start().await.unwrap();
    assert_eq!(node.state().await, NodeState::Running);

    // After stop
    node.stop().await.unwrap();
    assert_eq!(node.state().await, NodeState::Stopped);
}

// ==================== Error Handling Tests ====================

#[tokio::test]
async fn test_node_double_start() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

    let node = SynorNode::new(config).await.unwrap();

    // First start should succeed
    node.start().await.unwrap();

    // Second start might fail or be idempotent
    // This depends on implementation - just verify no panic
    let _ = node.start().await;

    node.stop().await.unwrap();
}

#[tokio::test]
async fn test_node_double_stop() {
    let temp_dir = TempDir::new().unwrap();
    let config = create_test_config(&temp_dir);

    let node = SynorNode::new(config).await.unwrap();
    node.start().await.unwrap();

    // First stop
    node.stop().await.unwrap();

    // Second stop should be safe (idempotent)
    let result = node.stop().await;
    assert!(result.is_ok(), "Double stop should be safe");
}

// ==================== Consensus Config Tests ====================

#[test]
fn test_consensus_config_for_networks() {
    use synord::config::ConsensusConfig;

    let mainnet = ConsensusConfig::for_network("mainnet");
    let devnet = ConsensusConfig::for_network("devnet");

    // Devnet should have faster finality
    assert!(devnet.finality_depth < mainnet.finality_depth);
    assert!(devnet.target_time_ms < mainnet.target_time_ms);
}

// ==================== Default Config Tests ====================

#[test]
fn test_storage_config_defaults() {
    use synord::config::StorageConfig;

    let config = StorageConfig::default();
    assert_eq!(config.db_type, "rocksdb");
    assert!(config.cache_size_mb > 0);
    assert!(!config.pruning.enabled);
}

#[test]
fn test_p2p_config_defaults() {
    use synord::config::P2PConfig;

    let config = P2PConfig::default();
    assert!(config.max_inbound > 0);
    assert!(config.max_outbound > 0);
    assert!(config.connection_timeout > 0);
}

#[test]
fn test_rpc_config_defaults() {
    use synord::config::RpcConfig;

    let config = RpcConfig::default();
    assert!(config.http_enabled);
    assert!(config.ws_enabled);
    assert!(config.cors);
}

#[test]
fn test_mining_config_defaults() {
    use synord::config::MiningConfig;

    let config = MiningConfig::default();
    assert!(!config.enabled);
    assert!(config.coinbase_address.is_none());
    assert!(!config.gpu_enabled);
}

#[test]
fn test_vm_config_defaults() {
    use synord::config::VmConfig;

    let config = VmConfig::default();
    assert!(config.enabled);
    assert!(config.max_gas_per_block > 0);
    assert!(config.max_contract_size > 0);
    assert!(config.max_call_depth > 0);
}
