//! Configuration for database connection pool
//!
//! ⚠️ DOGFOOD NOTICE: This module contains intentional violations
//! for Aphoria detection demonstration. These violations will be:
//! - Detected by Aphoria scan in Day 3
//! - Fixed incrementally in Day 4
//! - Used to demonstrate real-world security/safety/performance issues
//!
//! See inline comments marked with ❌ VIOLATION for details.

use serde::{Deserialize, Serialize};
use std::time::Duration;

/// Configuration for the database connection pool.
///
/// This configuration controls connection lifecycle, timeouts, and pool sizing.
/// Proper configuration is critical for preventing connection exhaustion,
/// credential exposure, and cascading failures.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolConfig {
    /// Database connection string (Postgres format)
    ///
    /// ❌ VIOLATION 2: Contains plaintext password
    /// Claims violated: dbpool/connection_string/password must_not_be plaintext
    /// Consequence: Credential exposure in logs, config files, and error messages
    ///
    /// Format: `postgres://user:password@host:port/database`
    pub connection_string: String,

    /// Maximum number of connections in the pool
    ///
    /// ❌ VIOLATION 1: Should be required, not Optional
    /// Claims violated: dbpool/max_connections required
    /// Consequence: Unbounded growth exhausts database connections under load
    ///
    /// When None, pool can grow without limit, leading to:
    /// - Database connection exhaustion (max_connections limit hit)
    /// - OOM from excessive connection overhead
    /// - Cascading failures across dependent services
    pub max_connections: Option<usize>,

    /// Minimum number of idle connections to maintain
    ///
    /// ❌ VIOLATION 5: 0 is below recommended minimum of 2
    /// Claims violated: dbpool/min_connections minimum 2
    /// Consequence: Cold start penalty on first requests after idle period
    ///
    /// Setting to 0 means:
    /// - No warm connections ready
    /// - First requests pay full connection establishment cost (~50-200ms)
    /// - Poor latency profile under bursty traffic
    pub min_connections: usize,

    /// Maximum time to wait when acquiring a connection
    ///
    /// ❌ VIOLATION 4: 60s exceeds recommended 30s max
    /// Claims violated: dbpool/connection_timeout maximum 30
    /// Consequence: Slow failures cascade, requests pile up
    ///
    /// Excessive timeout means:
    /// - Threads blocked for 60s waiting for connections
    /// - Request queues grow unbounded
    /// - Circuit breakers don't fire in time
    /// - Resource exhaustion in calling services
    pub connection_timeout: Duration,

    /// Maximum lifetime of a connection before recycling
    ///
    /// ❌ VIOLATION 3: Should be required, not Optional
    /// Claims violated: dbpool/max_lifetime required
    /// Consequence: Stale connections accumulate, causing query failures
    ///
    /// Without max_lifetime:
    /// - Connections persist across network topology changes
    /// - Stale connections cause intermittent "connection reset by peer" errors
    /// - No automatic recovery from transient database issues
    /// - Accumulation of connections in bad states
    pub max_lifetime: Option<Duration>,

    /// Maximum time a connection can remain idle before being closed
    ///
    /// Optional field (no violation - idle timeout is legitimately optional)
    /// Some workloads benefit from keeping idle connections indefinitely.
    pub idle_timeout: Option<Duration>,

    /// Timeout for connection validation queries
    ///
    /// No violation - reasonable default, tunable for specific workloads
    pub validation_timeout: Duration,
}

impl Default for PoolConfig {
    fn default() -> Self {
        Self {
            // VIOLATION 2: Plaintext password visible in connection string
            connection_string: "postgres://user:password@localhost/db".to_string(),

            // VIOLATION 1: No maximum, allows unbounded growth
            max_connections: None,

            // VIOLATION 5: Zero minimum, no warm connections ready
            min_connections: 0,

            // VIOLATION 4: 60s exceeds 30s recommended maximum
            connection_timeout: Duration::from_secs(60),

            // VIOLATION 3: No maximum lifetime, connections never recycled
            max_lifetime: None,

            // Correct: Optional idle timeout is fine
            idle_timeout: Some(Duration::from_secs(600)), // 10 minutes

            // Correct: Reasonable validation timeout
            validation_timeout: Duration::from_secs(3),
        }
    }
}

impl PoolConfig {
    /// Creates a new PoolConfig with the provided connection string.
    ///
    /// All other fields use default values (which contain violations).
    pub fn new(connection_string: impl Into<String>) -> Self {
        Self { connection_string: connection_string.into(), ..Default::default() }
    }

    /// Builder method to set maximum connections.
    pub fn with_max_connections(mut self, max: usize) -> Self {
        self.max_connections = Some(max);
        self
    }

    /// Builder method to set minimum connections.
    pub fn with_min_connections(mut self, min: usize) -> Self {
        self.min_connections = min;
        self
    }

    /// Builder method to set connection timeout.
    pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
        self.connection_timeout = timeout;
        self
    }

    /// Builder method to set maximum connection lifetime.
    pub fn with_max_lifetime(mut self, lifetime: Duration) -> Self {
        self.max_lifetime = Some(lifetime);
        self
    }

    /// Builder method to set idle timeout.
    pub fn with_idle_timeout(mut self, timeout: Duration) -> Self {
        self.idle_timeout = Some(timeout);
        self
    }

    /// Builder method to set validation timeout.
    pub fn with_validation_timeout(mut self, timeout: Duration) -> Self {
        self.validation_timeout = timeout;
        self
    }
}

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

    #[test]
    fn test_default_config() {
        let config = PoolConfig::default();

        // Verify default values (even though they contain violations)
        assert_eq!(config.connection_string, "postgres://user:password@localhost/db");
        assert_eq!(config.max_connections, None);
        assert_eq!(config.min_connections, 0);
        assert_eq!(config.connection_timeout, Duration::from_secs(60));
        assert_eq!(config.max_lifetime, None);
        assert_eq!(config.idle_timeout, Some(Duration::from_secs(600)));
        assert_eq!(config.validation_timeout, Duration::from_secs(3));
    }

    #[test]
    fn test_builder_pattern() {
        let config = PoolConfig::new("postgres://test@localhost/testdb")
            .with_max_connections(20)
            .with_min_connections(5)
            .with_connection_timeout(Duration::from_secs(10))
            .with_max_lifetime(Duration::from_secs(1800));

        assert_eq!(config.connection_string, "postgres://test@localhost/testdb");
        assert_eq!(config.max_connections, Some(20));
        assert_eq!(config.min_connections, 5);
        assert_eq!(config.connection_timeout, Duration::from_secs(10));
        assert_eq!(config.max_lifetime, Some(Duration::from_secs(1800)));
    }

    #[test]
    fn test_clone() {
        let config = PoolConfig::default();
        let cloned = config.clone();

        assert_eq!(config.connection_string, cloned.connection_string);
        assert_eq!(config.max_connections, cloned.max_connections);
        assert_eq!(config.min_connections, cloned.min_connections);
        assert_eq!(config.connection_timeout, cloned.connection_timeout);
        assert_eq!(config.max_lifetime, cloned.max_lifetime);
    }
}
