//! Server configuration
//!
//! This module defines the configuration schema for Nightlight server.
//! Configuration follows the unified T027 pattern with TlsConfig support.

use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

/// Main server configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// Server network configuration
    pub server: ServerConfig,

    /// Storage configuration
    pub storage: StorageConfig,

    /// TLS configuration (optional, enables mTLS)
    #[serde(default)]
    pub tls: Option<TlsConfig>,
}

/// Server network configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    /// gRPC server listen address (for PromQL queries)
    pub grpc_addr: String,

    /// HTTP server listen address (for Prometheus remote_write)
    pub http_addr: String,

    /// Maximum concurrent gRPC streams
    #[serde(default = "default_max_concurrent_streams")]
    pub max_concurrent_streams: u32,

    /// Query timeout in seconds
    #[serde(default = "default_query_timeout")]
    pub query_timeout_seconds: u64,

    /// Maximum samples per query (prevents memory exhaustion)
    #[serde(default = "default_max_samples_per_query")]
    pub max_samples_per_query: u64,
}

fn default_max_concurrent_streams() -> u32 {
    100
}

fn default_query_timeout() -> u64 {
    30
}

fn default_max_samples_per_query() -> u64 {
    10_000_000 // 10M samples
}

/// Storage configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageConfig {
    /// Data directory for time-series blocks
    pub data_dir: String,

    /// Data retention period in days
    #[serde(default = "default_retention_days")]
    pub retention_days: u32,

    /// WAL segment size in megabytes
    #[serde(default = "default_wal_segment_size")]
    pub wal_segment_size_mb: u32,

    /// Block duration for time-series chunks (in hours)
    #[serde(default = "default_block_duration_hours")]
    pub block_duration_hours: u32,

    /// Maximum head block samples before flushing to disk
    #[serde(default = "default_max_head_samples")]
    pub max_head_samples: u64,

    /// Compaction interval in seconds
    #[serde(default = "default_compaction_interval")]
    pub compaction_interval_seconds: u64,
}

fn default_retention_days() -> u32 {
    15
}

fn default_wal_segment_size() -> u32 {
    128
}

fn default_block_duration_hours() -> u32 {
    2
}

fn default_max_head_samples() -> u64 {
    1_000_000
}

fn default_compaction_interval() -> u64 {
    3600 // 1 hour
}

/// TLS configuration for gRPC and HTTP servers
///
/// This follows the unified T027 TlsConfig pattern used across all services.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
    /// Path to server certificate file (PEM format)
    pub cert_file: String,

    /// Path to server private key file (PEM format)
    pub key_file: String,

    /// Path to CA certificate file for client verification (optional, enables mTLS)
    pub ca_file: Option<String>,

    /// Require client certificates (mTLS mode)
    #[serde(default)]
    pub require_client_cert: bool,
}

impl Config {
    /// Load configuration from a YAML file
    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
        let content = fs::read_to_string(path)?;
        let config = serde_yaml::from_str(&content)?;
        Ok(config)
    }

    /// Save configuration to a YAML file
    #[cfg(test)]
    pub fn save(&self, path: &str) -> Result<()> {
        let content = serde_yaml::to_string(self)?;
        fs::write(path, content)?;
        Ok(())
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            server: ServerConfig {
                grpc_addr: "127.0.0.1:9100".to_string(),
                http_addr: "127.0.0.1:9101".to_string(),
                max_concurrent_streams: default_max_concurrent_streams(),
                query_timeout_seconds: default_query_timeout(),
                max_samples_per_query: default_max_samples_per_query(),
            },
            storage: StorageConfig {
                data_dir: "./data".to_string(),
                retention_days: default_retention_days(),
                wal_segment_size_mb: default_wal_segment_size(),
                block_duration_hours: default_block_duration_hours(),
                max_head_samples: default_max_head_samples(),
                compaction_interval_seconds: default_compaction_interval(),
            },
            tls: None,
        }
    }
}

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

    #[test]
    fn test_default_config() {
        let config = Config::default();
        assert_eq!(config.server.grpc_addr, "127.0.0.1:9100");
        assert_eq!(config.server.http_addr, "127.0.0.1:9101");
        assert_eq!(config.storage.retention_days, 15);
        assert!(config.tls.is_none());
    }

    #[test]
    fn test_config_roundtrip() {
        let config = Config::default();

        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_str().unwrap();

        config.save(path).unwrap();
        let loaded = Config::from_file(path).unwrap();

        assert_eq!(loaded.server.grpc_addr, config.server.grpc_addr);
        assert_eq!(loaded.storage.data_dir, config.storage.data_dir);
    }

    #[test]
    fn test_tls_config() {
        let config = Config {
            server: ServerConfig {
                grpc_addr: "0.0.0.0:9100".to_string(),
                http_addr: "0.0.0.0:9101".to_string(),
                max_concurrent_streams: 100,
                query_timeout_seconds: 30,
                max_samples_per_query: 10_000_000,
            },
            storage: StorageConfig {
                data_dir: "/var/lib/nightlight".to_string(),
                retention_days: 30,
                wal_segment_size_mb: 256,
                block_duration_hours: 2,
                max_head_samples: 1_000_000,
                compaction_interval_seconds: 3600,
            },
            tls: Some(TlsConfig {
                cert_file: "/etc/nightlight/cert.pem".to_string(),
                key_file: "/etc/nightlight/key.pem".to_string(),
                ca_file: Some("/etc/nightlight/ca.pem".to_string()),
                require_client_cert: true,
            }),
        };

        let temp_file = NamedTempFile::new().unwrap();
        let path = temp_file.path().to_str().unwrap();

        config.save(path).unwrap();
        let loaded = Config::from_file(path).unwrap();

        assert!(loaded.tls.is_some());
        let tls = loaded.tls.unwrap();
        assert_eq!(tls.cert_file, "/etc/nightlight/cert.pem");
        assert!(tls.require_client_cert);
    }
}
