//! Database configuration types
//! 
//! This module defines configuration structures for database connections.

use serde::{Deserialize, Serialize};
use std::time::Duration;
use crate::error::{CommonError, CommonResult};

/// Database configuration
/// 
/// Configuration for database connections including connection pooling settings.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DatabaseConfig {
    /// Database connection URL
    pub url: String,
    /// Maximum number of connections in the pool
    pub max_connections: u32,
    /// Minimum number of connections in the pool
    #[serde(default = "default_min_connections")]
    pub min_connections: u32,
    /// Connection timeout
    #[serde(with = "duration_secs", default = "default_connection_timeout")]
    pub connection_timeout: Duration,
    /// Idle timeout before closing connections
    #[serde(with = "duration_secs", default = "default_idle_timeout")]
    pub idle_timeout: Duration,
    /// Maximum lifetime of a connection
    #[serde(with = "duration_secs", default = "default_max_lifetime")]
    pub max_lifetime: Duration,
    /// Enable SQL query logging
    #[serde(default = "default_false")]
    pub enable_logging: bool,
    /// Run migrations on startup
    #[serde(default = "default_false")]
    pub migrate_on_start: bool,
}

fn default_min_connections() -> u32 {
    1
}

fn default_connection_timeout() -> Duration {
    Duration::from_secs(30)
}

fn default_idle_timeout() -> Duration {
    Duration::from_secs(600)
}

fn default_max_lifetime() -> Duration {
    Duration::from_secs(3600)
}

fn default_false() -> bool {
    false
}

/// Serialize/Deserialize Duration as seconds
mod duration_secs {
    use serde::{Deserialize, Deserializer, Serializer};
    use std::time::Duration;

    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_u64(duration.as_secs())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
    where
        D: Deserializer<'de>,
    {
        let secs = u64::deserialize(deserializer)?;
        Ok(Duration::from_secs(secs))
    }
}

impl Default for DatabaseConfig {
    fn default() -> Self {
        Self {
            url: "postgresql://user:password@localhost/database".to_string(),
            max_connections: 10,
            min_connections: 1,
            connection_timeout: Duration::from_secs(30),
            idle_timeout: Duration::from_secs(600),
            max_lifetime: Duration::from_secs(3600),
            enable_logging: false,
            migrate_on_start: false,
        }
    }
}

impl DatabaseConfig {
    /// Create a new database configuration
    pub fn new(url: String, max_connections: u32) -> Self {
        Self {
            url,
            max_connections,
            min_connections: 1,
            connection_timeout: Duration::from_secs(30),
            idle_timeout: Duration::from_secs(600),
            max_lifetime: Duration::from_secs(3600),
            enable_logging: false,
            migrate_on_start: false,
        }
    }

    /// Validate the database configuration
    pub fn validate(&self) -> CommonResult<()> {
        if self.url.is_empty() {
            return Err(CommonError::ValidationError(
                "Database URL cannot be empty".to_string()
            ));
        }

        if !self.url.starts_with("postgresql://") && !self.url.starts_with("postgres://") {
            return Err(CommonError::ValidationError(
                "Database URL must start with postgresql:// or postgres://".to_string()
            ));
        }

        if self.max_connections == 0 {
            return Err(CommonError::ValidationError(
                "Max connections must be greater than 0".to_string()
            ));
        }

        if self.min_connections > self.max_connections {
            return Err(CommonError::ValidationError(
                "Min connections cannot be greater than max connections".to_string()
            ));
        }

        if self.connection_timeout.as_secs() == 0 {
            return Err(CommonError::ValidationError(
                "Connection timeout must be greater than 0".to_string()
            ));
        }

        Ok(())
    }

    /// Get the database name from the URL
    pub fn database_name(&self) -> Option<String> {
        self.url
            .split('/')
            .last()
            .and_then(|s| s.split('?').next())
            .map(|s| s.to_string())
    }

    /// Get the host from the URL
    pub fn host(&self) -> Option<String> {
        self.url
            .strip_prefix("postgresql://")
            .or_else(|| self.url.strip_prefix("postgres://"))
            .and_then(|s| {
                // Remove user:password@ if present
                let s = if s.contains('@') {
                    s.split('@').nth(1).unwrap_or(s)
                } else {
                    s
                };
                // Extract host:port
                s.split('/').next().map(|s| {
                    if s.contains(':') {
                        s.split(':').next().unwrap_or(s).to_string()
                    } else {
                        s.to_string()
                    }
                })
            })
    }

    /// Get the port from the URL
    pub fn port(&self) -> Option<u16> {
        self.url
            .strip_prefix("postgresql://")
            .or_else(|| self.url.strip_prefix("postgres://"))
            .and_then(|s| {
                let s = if s.contains('@') {
                    s.split('@').nth(1).unwrap_or(s)
                } else {
                    s
                };
                s.split('/').next().and_then(|s| {
                    if s.contains(':') {
                        s.split(':').nth(1).and_then(|p| p.parse().ok())
                    } else {
                        None
                    }
                })
            })
            .or(Some(5432)) // Default PostgreSQL port
    }
}

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

    #[test]
    fn test_database_config_default() {
        let config = DatabaseConfig::default();
        assert!(!config.url.is_empty());
        assert_eq!(config.max_connections, 10);
        assert_eq!(config.min_connections, 1);
    }

    #[test]
    fn test_database_config_new() {
        let config = DatabaseConfig::new("postgresql://localhost/test".to_string(), 20);
        assert_eq!(config.url, "postgresql://localhost/test");
        assert_eq!(config.max_connections, 20);
        assert_eq!(config.min_connections, 1);
    }

    #[test]
    fn test_database_config_validate_success() {
        let config = DatabaseConfig::new("postgresql://user:pass@localhost/db".to_string(), 10);
        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_database_config_validate_empty_url() {
        let mut config = DatabaseConfig::default();
        config.url = "".to_string();
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_database_config_validate_invalid_url() {
        let config = DatabaseConfig::new("mysql://localhost/db".to_string(), 10);
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_database_config_validate_zero_max_connections() {
        let config = DatabaseConfig::new("postgresql://localhost/db".to_string(), 0);
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_database_config_validate_min_greater_than_max() {
        let mut config = DatabaseConfig::new("postgresql://localhost/db".to_string(), 10);
        config.min_connections = 20;
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_database_config_database_name() {
        let config = DatabaseConfig::new("postgresql://localhost/mydb".to_string(), 10);
        assert_eq!(config.database_name(), Some("mydb".to_string()));
    }

    #[test]
    fn test_database_config_database_name_with_params() {
        let config = DatabaseConfig::new("postgresql://localhost/mydb?sslmode=require".to_string(), 10);
        assert_eq!(config.database_name(), Some("mydb".to_string()));
    }

    #[test]
    fn test_database_config_host() {
        let config = DatabaseConfig::new("postgresql://localhost/mydb".to_string(), 10);
        assert_eq!(config.host(), Some("localhost".to_string()));
    }

    #[test]
    fn test_database_config_host_with_auth() {
        let config = DatabaseConfig::new("postgresql://user:pass@localhost/mydb".to_string(), 10);
        assert_eq!(config.host(), Some("localhost".to_string()));
    }

    #[test]
    fn test_database_config_port() {
        let config = DatabaseConfig::new("postgresql://localhost:5433/mydb".to_string(), 10);
        assert_eq!(config.port(), Some(5433));
    }

    #[test]
    fn test_database_config_port_default() {
        let config = DatabaseConfig::new("postgresql://localhost/mydb".to_string(), 10);
        assert_eq!(config.port(), Some(5432));
    }

    #[test]
    fn test_database_config_serialize() {
        let config = DatabaseConfig::default();
        let json = serde_json::to_string(&config).unwrap();
        assert!(json.contains("url"));
        assert!(json.contains("max_connections"));
    }

    #[test]
    fn test_database_config_deserialize() {
        let json = r#"{
            "url": "postgresql://localhost/test",
            "max_connections": 20
        }"#;
        let config: DatabaseConfig = serde_json::from_str(json).unwrap();
        assert_eq!(config.url, "postgresql://localhost/test");
        assert_eq!(config.max_connections, 20);
    }
}

