//! Node configuration.

use std::fs;
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};
use tracing::info;

/// Complete node configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NodeConfig {
    /// Network name (mainnet, testnet, devnet).
    pub network: String,

    /// Chain ID.
    pub chain_id: u64,

    /// Data directory.
    pub data_dir: PathBuf,

    /// Storage configuration.
    #[serde(default)]
    pub storage: StorageConfig,

    /// Network/P2P configuration.
    #[serde(default)]
    pub p2p: P2PConfig,

    /// RPC configuration.
    #[serde(default)]
    pub rpc: RpcConfig,

    /// Mining configuration.
    #[serde(default)]
    pub mining: MiningConfig,

    /// Consensus configuration.
    #[serde(default)]
    pub consensus: ConsensusConfig,

    /// VM configuration.
    #[serde(default)]
    pub vm: VmConfig,

    /// Logging configuration.
    #[serde(default)]
    pub logging: LoggingConfig,

    /// Metrics configuration.
    #[serde(default)]
    pub metrics: MetricsConfig,
}

impl NodeConfig {
    /// Creates default config for a network.
    pub fn for_network(network: &str) -> anyhow::Result<Self> {
        // Chain IDs match synor-network convention:
        // 0 = mainnet, 1 = testnet, 2+ = devnet/local
        let (chain_id, data_dir_name) = match network {
            "mainnet" => (0, "synor"),
            "testnet" => (1, "synor-testnet"),
            "devnet" => (2, "synor-devnet"),
            _ => anyhow::bail!("Unknown network: {}", network),
        };

        let data_dir = dirs::data_dir()
            .unwrap_or_else(|| PathBuf::from("."))
            .join(data_dir_name);

        Ok(NodeConfig {
            network: network.to_string(),
            chain_id,
            data_dir,
            storage: StorageConfig::default(),
            p2p: P2PConfig::for_network(network),
            rpc: RpcConfig::for_network(network),
            mining: MiningConfig::default(),
            consensus: ConsensusConfig::for_network(network),
            vm: VmConfig::default(),
            logging: LoggingConfig::default(),
            metrics: MetricsConfig::default(),
        })
    }

    /// Loads config from file or creates default.
    pub fn load_or_default(path: &Path, network: &str) -> anyhow::Result<Self> {
        if path.exists() {
            Self::load(path)
        } else {
            info!("Config file not found, using defaults");
            Self::for_network(network)
        }
    }

    /// Loads config from file.
    pub fn load(path: &Path) -> anyhow::Result<Self> {
        let content = fs::read_to_string(path)?;
        let config: NodeConfig = toml::from_str(&content)?;
        Ok(config)
    }

    /// Saves config to file.
    pub fn save(&self, path: &Path) -> anyhow::Result<()> {
        let content = toml::to_string_pretty(self)?;
        fs::write(path, content)?;
        Ok(())
    }

    /// Sets data directory.
    pub fn with_data_dir(mut self, data_dir: Option<PathBuf>) -> Self {
        if let Some(dir) = data_dir {
            self.data_dir = dir;
        }
        self
    }

    /// Sets mining configuration.
    pub fn with_mining(mut self, enabled: bool, coinbase: Option<String>, threads: usize) -> Self {
        if enabled {
            self.mining.enabled = true;
        }
        if let Some(addr) = coinbase {
            self.mining.coinbase_address = Some(addr);
        }
        if threads > 0 {
            self.mining.threads = threads;
        }
        self
    }

    /// Sets RPC configuration.
    pub fn with_rpc(mut self, host: &str, rpc_port: u16, ws_port: u16) -> Self {
        self.rpc.http_addr = format!("{}:{}", host, rpc_port);
        self.rpc.ws_addr = format!("{}:{}", host, ws_port);
        self
    }

    /// Sets P2P configuration.
    pub fn with_p2p(mut self, host: &str, port: u16, seeds: Vec<String>) -> Self {
        self.p2p.listen_addr = format!("{}:{}", host, port);
        if !seeds.is_empty() {
            self.p2p.seeds = seeds;
        }
        self
    }

    /// Returns paths for various data.
    pub fn blocks_path(&self) -> PathBuf {
        self.data_dir.join("blocks")
    }

    pub fn chainstate_path(&self) -> PathBuf {
        self.data_dir.join("chainstate")
    }

    pub fn contracts_path(&self) -> PathBuf {
        self.data_dir.join("contracts")
    }

    pub fn keys_path(&self) -> PathBuf {
        self.data_dir.join("keys")
    }
}

/// Storage configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StorageConfig {
    /// Database type (rocksdb, sled).
    pub db_type: String,

    /// Cache size in MB.
    pub cache_size_mb: usize,

    /// Max open files.
    pub max_open_files: i32,

    /// Enable compression.
    pub compression: bool,

    /// Pruning mode.
    pub pruning: PruningConfig,
}

impl Default for StorageConfig {
    fn default() -> Self {
        StorageConfig {
            db_type: "rocksdb".to_string(),
            cache_size_mb: 512,
            max_open_files: 1024,
            compression: true,
            pruning: PruningConfig::default(),
        }
    }
}

/// Pruning configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PruningConfig {
    /// Enable pruning.
    pub enabled: bool,

    /// Keep last N blocks.
    pub keep_blocks: u64,

    /// Pruning interval in blocks.
    pub interval: u64,
}

impl Default for PruningConfig {
    fn default() -> Self {
        PruningConfig {
            enabled: false,
            keep_blocks: 100_000,
            interval: 1000,
        }
    }
}

/// P2P network configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct P2PConfig {
    /// Listen address.
    pub listen_addr: String,

    /// External address (for NAT).
    pub external_addr: Option<String>,

    /// Seed nodes.
    pub seeds: Vec<String>,

    /// Maximum inbound connections.
    pub max_inbound: usize,

    /// Maximum outbound connections.
    pub max_outbound: usize,

    /// Connection timeout in seconds.
    pub connection_timeout: u64,

    /// Enable UPnP.
    pub upnp: bool,

    /// Ban duration in seconds.
    pub ban_duration: u64,
}

impl Default for P2PConfig {
    fn default() -> Self {
        P2PConfig {
            listen_addr: "0.0.0.0:16100".to_string(),
            external_addr: None,
            seeds: vec![],
            max_inbound: 125,
            max_outbound: 8,
            connection_timeout: 30,
            upnp: true,
            ban_duration: 86400, // 24 hours
        }
    }
}

impl P2PConfig {
    /// Creates config for a network.
    ///
    /// # Environment Variables
    ///
    /// The following environment variables can override seed configuration:
    /// - `SYNOR_BOOTSTRAP_PEERS`: Comma-separated list of bootstrap peer multiaddrs
    ///   Format: `/dns4/<hostname>/tcp/<port>/p2p/<peer_id>`
    ///   Example: `SYNOR_BOOTSTRAP_PEERS=/dns4/seed1.synor.cc/tcp/17511/p2p/12D3KooWAbCd...`
    ///
    /// This allows operators to configure seed nodes without recompiling.
    pub fn for_network(network: &str) -> Self {
        let mut config = P2PConfig::default();

        match network {
            "mainnet" => {
                config.listen_addr = "/ip4/0.0.0.0/tcp/16511".to_string();
                // Mainnet seeds - will be populated after mainnet seed node deployment
                // IMPORTANT: Addresses must include /p2p/<peer_id> for secure dialing
                // Override with SYNOR_BOOTSTRAP_PEERS env var after deployment
                config.seeds = vec![];
            }
            "testnet" => {
                config.listen_addr = "/ip4/0.0.0.0/tcp/17511".to_string();
                // Testnet seeds - populate after deploying seed nodes
                // IMPORTANT: Addresses must include /p2p/<peer_id> for secure dialing
                // Override with SYNOR_BOOTSTRAP_PEERS env var after deployment
                config.seeds = vec![];
            }
            "devnet" => {
                config.listen_addr = "/ip4/0.0.0.0/tcp/18511".to_string();
                // Devnet uses mDNS for local discovery, no seeds needed
                config.seeds = vec![];
            }
            _ => {}
        };

        // Allow environment variable override for bootstrap peers
        // This enables operators to configure seeds at runtime
        if let Ok(peers_env) = std::env::var("SYNOR_BOOTSTRAP_PEERS") {
            let peers: Vec<String> = peers_env
                .split(',')
                .map(|s| s.trim().to_string())
                .filter(|s| !s.is_empty())
                .collect();
            if !peers.is_empty() {
                config.seeds = peers;
            }
        }

        config
    }
}

/// RPC configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RpcConfig {
    /// Enable HTTP RPC.
    pub http_enabled: bool,

    /// HTTP bind address.
    pub http_addr: String,

    /// Enable WebSocket RPC.
    pub ws_enabled: bool,

    /// WebSocket bind address.
    pub ws_addr: String,

    /// Enable CORS.
    pub cors: bool,

    /// Allowed origins.
    pub cors_origins: Vec<String>,

    /// Maximum batch size.
    pub max_batch_size: usize,

    /// Maximum response size.
    pub max_response_size: usize,

    /// Rate limit (requests per second, 0 = unlimited).
    pub rate_limit: u32,

    /// Maximum connections.
    pub max_connections: u32,
}

impl Default for RpcConfig {
    fn default() -> Self {
        RpcConfig {
            http_enabled: true,
            http_addr: "127.0.0.1:16110".to_string(),
            ws_enabled: true,
            ws_addr: "127.0.0.1:16111".to_string(),
            cors: true,
            cors_origins: vec!["*".to_string()],
            max_batch_size: 100,
            max_response_size: 10 * 1024 * 1024, // 10MB
            rate_limit: 0,
            max_connections: 100,
        }
    }
}

impl RpcConfig {
    /// Creates config for a network.
    pub fn for_network(network: &str) -> Self {
        let mut config = RpcConfig::default();

        match network {
            "mainnet" => {
                config.http_addr = "127.0.0.1:16110".to_string();
                config.ws_addr = "127.0.0.1:16111".to_string();
            }
            "testnet" => {
                config.http_addr = "127.0.0.1:17110".to_string();
                config.ws_addr = "127.0.0.1:17111".to_string();
            }
            "devnet" => {
                config.http_addr = "127.0.0.1:18110".to_string();
                config.ws_addr = "127.0.0.1:18111".to_string();
            }
            _ => {}
        }

        config
    }
}

/// Mining configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MiningConfig {
    /// Enable mining.
    pub enabled: bool,

    /// Coinbase address for rewards.
    pub coinbase_address: Option<String>,

    /// Number of mining threads (0 = auto).
    pub threads: usize,

    /// Extra data for coinbase.
    pub extra_data: String,

    /// Mining intensity (0.0 - 1.0).
    pub intensity: f32,

    /// Enable GPU mining.
    pub gpu_enabled: bool,

    /// GPU device indices.
    pub gpu_devices: Vec<usize>,
}

impl Default for MiningConfig {
    fn default() -> Self {
        MiningConfig {
            enabled: false,
            coinbase_address: None,
            threads: 0, // Auto-detect
            extra_data: "synord".to_string(),
            intensity: 1.0,
            gpu_enabled: false,
            gpu_devices: vec![],
        }
    }
}

/// Consensus configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConsensusConfig {
    /// GHOSTDAG K parameter.
    pub ghostdag_k: u8,

    /// Merge depth.
    pub merge_depth: u64,

    /// Finality depth.
    pub finality_depth: u64,

    /// Target block time in milliseconds.
    pub target_time_ms: u64,

    /// Difficulty adjustment window.
    pub difficulty_window: u64,

    /// Max block size.
    pub max_block_size: usize,

    /// Max block mass.
    pub max_block_mass: u64,
}

impl Default for ConsensusConfig {
    fn default() -> Self {
        ConsensusConfig {
            ghostdag_k: 18,
            merge_depth: 3600,     // ~1 hour
            finality_depth: 86400, // ~24 hours
            target_time_ms: 1000,
            difficulty_window: 2641,
            max_block_size: 1_000_000,
            max_block_mass: 500_000,
        }
    }
}

impl ConsensusConfig {
    /// Creates config for a network.
    pub fn for_network(network: &str) -> Self {
        let mut config = ConsensusConfig::default();

        match network {
            "mainnet" => {
                // Mainnet: 1 second blocks, high finality for security
                config.target_time_ms = 1000;
                config.finality_depth = 86400; // ~24 hours at 1 BPS
                config.merge_depth = 3600; // ~1 hour at 1 BPS
            }
            "testnet" => {
                // Testnet: Fast 100ms blocks for development testing
                config.target_time_ms = 100;
                config.finality_depth = 36000; // ~1 hour at 10 BPS
                config.merge_depth = 360; // ~36 seconds at 10 BPS
                config.ghostdag_k = 18;
            }
            "devnet" => {
                // Devnet: Very fast for local testing
                config.target_time_ms = 100;
                config.finality_depth = 100;
                config.merge_depth = 36;
            }
            _ => {}
        }

        config
    }
}

/// VM configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VmConfig {
    /// Enable smart contracts.
    pub enabled: bool,

    /// Maximum gas per block.
    pub max_gas_per_block: u64,

    /// Maximum contract size.
    pub max_contract_size: usize,

    /// Maximum call depth.
    pub max_call_depth: u32,

    /// Maximum memory pages.
    pub max_memory_pages: u32,

    /// Execution timeout in milliseconds.
    pub execution_timeout_ms: u64,
}

impl Default for VmConfig {
    fn default() -> Self {
        VmConfig {
            enabled: true,
            max_gas_per_block: 100_000_000,
            max_contract_size: 24 * 1024, // 24KB
            max_call_depth: 16,
            max_memory_pages: 256, // 16MB
            execution_timeout_ms: 5000,
        }
    }
}

/// Logging configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LoggingConfig {
    /// Log level (trace, debug, info, warn, error).
    pub level: String,

    /// Enable JSON format.
    pub json: bool,

    /// Log file path.
    pub file: Option<PathBuf>,

    /// Maximum log file size in MB.
    pub max_size_mb: usize,

    /// Number of log files to keep.
    pub max_files: usize,
}

impl Default for LoggingConfig {
    fn default() -> Self {
        LoggingConfig {
            level: "info".to_string(),
            json: false,
            file: None,
            max_size_mb: 100,
            max_files: 5,
        }
    }
}

/// Metrics configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MetricsConfig {
    /// Enable metrics.
    pub enabled: bool,

    /// Metrics bind address.
    pub addr: String,

    /// Enable Prometheus endpoint.
    pub prometheus: bool,
}

impl Default for MetricsConfig {
    fn default() -> Self {
        MetricsConfig {
            enabled: false,
            addr: "127.0.0.1:9090".to_string(),
            prometheus: true,
        }
    }
}

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

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

        let config = NodeConfig::for_network("testnet").unwrap();
        assert_eq!(config.chain_id, 1);
        assert_eq!(config.consensus.target_time_ms, 100); // Fast testnet

        let config = NodeConfig::for_network("devnet").unwrap();
        assert_eq!(config.chain_id, 2);
    }

    #[test]
    fn test_config_save_load() {
        let dir = tempdir().unwrap();
        let path = dir.path().join("config.toml");

        let config = NodeConfig::for_network("mainnet").unwrap();
        config.save(&path).unwrap();

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