use thiserror::Error;

/// Errors that can occur when working with the connection pool.
#[derive(Debug, Error)]
pub enum PoolError {
    // Connection Errors
    /// Failed to establish a database connection
    #[error("Connection failed: {0}")]
    ConnectionFailed(String),

    /// Connection attempt timed out
    #[error("Connection timeout after {seconds}s")]
    ConnectionTimeout { seconds: u64 },

    /// Invalid database credentials provided
    #[error("Authentication failed: invalid credentials")]
    InvalidCredentials,

    /// Connection was refused by the database
    #[error("Connection refused by database at {host}")]
    ConnectionRefused { host: String },

    // Pool State Errors
    /// All connections in the pool are currently in use
    #[error("Pool is exhausted (all {max_connections} connections in use)")]
    PoolExhausted { max_connections: usize },

    /// The connection pool has been closed and cannot accept new requests
    #[error("Pool is closed")]
    PoolClosed,

    /// Connection validation failed before checkout
    #[error("Connection validation failed: {reason}")]
    ValidationFailed { reason: String },

    /// Connection has exceeded its maximum lifetime
    #[error("Connection expired (lifetime: {lifetime_secs}s)")]
    ConnectionExpired { lifetime_secs: u64 },

    // Configuration Errors
    /// Missing required configuration parameter
    #[error("Missing required configuration: {parameter}")]
    MissingConfiguration { parameter: String },

    /// Invalid configuration value
    #[error("Invalid configuration for {parameter}: {reason}")]
    InvalidConfiguration { parameter: String, reason: String },

    /// Connection string is invalid or malformed
    #[error("Invalid connection string: {reason}")]
    InvalidConnectionString { reason: String },

    // Operational Errors
    /// An internal pool error occurred
    #[error("Internal pool error: {0}")]
    InternalError(String),

    /// A database operation failed
    #[error("Database error: {0}")]
    DatabaseError(#[from] tokio_postgres::Error),

    /// Failed to acquire a lock (should be rare in properly functioning pool)
    #[error("Lock acquisition failed: {0}")]
    LockError(String),

    /// Connection leaked (not returned to pool within threshold)
    #[error("Connection leaked: held for {duration_secs}s, threshold: {threshold_secs}s")]
    ConnectionLeaked { duration_secs: u64, threshold_secs: u64 },
}

impl PoolError {
    /// Creates a connection failed error with context
    pub fn connection_failed<S: Into<String>>(message: S) -> Self {
        Self::ConnectionFailed(message.into())
    }

    /// Creates an invalid configuration error
    pub fn invalid_config<S: Into<String>>(parameter: S, reason: S) -> Self {
        Self::InvalidConfiguration { parameter: parameter.into(), reason: reason.into() }
    }

    /// Creates a missing configuration error
    pub fn missing_config<S: Into<String>>(parameter: S) -> Self {
        Self::MissingConfiguration { parameter: parameter.into() }
    }

    /// Creates a validation failed error
    pub fn validation_failed<S: Into<String>>(reason: S) -> Self {
        Self::ValidationFailed { reason: reason.into() }
    }

    /// Creates an internal error
    pub fn internal<S: Into<String>>(message: S) -> Self {
        Self::InternalError(message.into())
    }
}

/// Result type alias for pool operations
pub type Result<T> = std::result::Result<T, PoolError>;

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

    #[test]
    fn test_error_messages() {
        let err = PoolError::PoolExhausted { max_connections: 10 };
        assert_eq!(err.to_string(), "Pool is exhausted (all 10 connections in use)");

        let err = PoolError::ConnectionTimeout { seconds: 30 };
        assert_eq!(err.to_string(), "Connection timeout after 30s");

        let err = PoolError::InvalidConfiguration {
            parameter: "max_connections".to_string(),
            reason: "must be greater than 0".to_string(),
        };
        assert_eq!(
            err.to_string(),
            "Invalid configuration for max_connections: must be greater than 0"
        );
    }

    #[test]
    fn test_error_constructors() {
        let err = PoolError::connection_failed("network error");
        assert!(matches!(err, PoolError::ConnectionFailed(_)));

        let err = PoolError::invalid_config("timeout", "must be positive");
        assert!(matches!(err, PoolError::InvalidConfiguration { .. }));

        let err = PoolError::missing_config("connection_string");
        assert!(matches!(err, PoolError::MissingConfiguration { .. }));

        let err = PoolError::validation_failed("ping failed");
        assert!(matches!(err, PoolError::ValidationFailed { .. }));

        let err = PoolError::internal("unexpected state");
        assert!(matches!(err, PoolError::InternalError(_)));
    }

    #[test]
    fn test_error_from_postgres() {
        // Note: This test verifies the From trait implementation exists
        // In real scenarios, tokio_postgres::Error would be converted automatically
        // We can't easily construct a real postgres error in tests, but the From
        // implementation is verified at compile time via #[from] attribute
    }
}
