pub use common::error::ErrorSeverity;
use thiserror::Error;

/// Database error types covering all database operations
#[derive(Error, Debug)]
pub enum DatabaseError {
    /// Connection pool related errors
    #[error("Connection pool error: {message}")]
    ConnectionPool { message: String },

    /// Connection establishment errors
    #[error("Connection failed: {message}")]
    Connection { message: String },

    /// Query execution errors
    #[error("Query execution failed: {query} - {message}")]
    Query { query: String, message: String },

    /// Transaction related errors
    #[error("Transaction error: {message}")]
    Transaction { message: String },

    /// Configuration errors
    #[error("Configuration error: {message}")]
    Configuration { message: String },

    /// Migration errors
    #[error("Migration error: {message}")]
    Migration { message: String },

    /// Serialization/Deserialization errors
    #[error("Serialization error: {message}")]
    Serialization { message: String },

    /// Timeout errors
    #[error("Operation timeout: {operation} exceeded {timeout_secs}s")]
    Timeout {
        operation: String,
        timeout_secs: u64,
    },

    /// Validation errors
    #[error("Validation error: {field} - {message}")]
    Validation { field: String, message: String },

    /// Resource not found errors
    #[error("Resource not found: {resource_type} with {identifier}")]
    NotFound {
        resource_type: String,
        identifier: String,
    },

    /// Constraint violation errors
    #[error("Constraint violation: {constraint} - {message}")]
    ConstraintViolation { constraint: String, message: String },

    /// Unknown/Generic errors
    #[error("Database error: {message}")]
    Unknown { message: String },
}

impl DatabaseError {
    /// Check if this error is retryable (transient)
    pub fn is_retryable(&self) -> bool {
        match self {
            DatabaseError::ConnectionPool { .. } => true,
            DatabaseError::Connection { .. } => true,
            DatabaseError::Timeout { .. } => true,
            DatabaseError::Query { message, .. } => {
                // Check for specific PostgreSQL error codes that are retryable
                message.contains("connection")
                    || message.contains("timeout")
                    || message.contains("deadlock")
                    || message.contains("serialization_failure")
            },
            _ => false,
        }
    }

    /// Get error category for metrics/logging
    pub fn category(&self) -> &'static str {
        match self {
            DatabaseError::ConnectionPool { .. } => "connection_pool",
            DatabaseError::Connection { .. } => "connection",
            DatabaseError::Query { .. } => "query",
            DatabaseError::Transaction { .. } => "transaction",
            DatabaseError::Configuration { .. } => "configuration",
            DatabaseError::Migration { .. } => "migration",
            DatabaseError::Serialization { .. } => "serialization",
            DatabaseError::Timeout { .. } => "timeout",
            DatabaseError::Validation { .. } => "validation",
            DatabaseError::NotFound { .. } => "not_found",
            DatabaseError::ConstraintViolation { .. } => "constraint_violation",
            DatabaseError::Unknown { .. } => "unknown",
        }
    }

    /// Get error severity level
    pub fn severity(&self) -> ErrorSeverity {
        match self {
            DatabaseError::Configuration { .. } => ErrorSeverity::Critical,
            DatabaseError::Migration { .. } => ErrorSeverity::Critical,
            DatabaseError::ConnectionPool { .. } => ErrorSeverity::High,
            DatabaseError::Connection { .. } => ErrorSeverity::High,
            DatabaseError::Transaction { .. } => ErrorSeverity::High,
            DatabaseError::ConstraintViolation { .. } => ErrorSeverity::Medium,
            DatabaseError::Query { .. } => ErrorSeverity::Medium,
            DatabaseError::Timeout { .. } => ErrorSeverity::Medium,
            DatabaseError::Validation { .. } => ErrorSeverity::Low,
            DatabaseError::NotFound { .. } => ErrorSeverity::Low,
            DatabaseError::Serialization { .. } => ErrorSeverity::Low,
            DatabaseError::Unknown { .. } => ErrorSeverity::Medium,
        }
    }
}

// ErrorSeverity is imported from common::error

/// Convert SQLx errors to our domain errors
impl From<sqlx::Error> for DatabaseError {
    fn from(err: sqlx::Error) -> Self {
        match err {
            sqlx::Error::Configuration(msg) => DatabaseError::Configuration {
                message: msg.to_string(),
            },
            sqlx::Error::Database(db_err) => {
                let code = db_err.code().unwrap_or_default();
                let message = db_err.message().to_string();

                // PostgreSQL error codes
                match code.as_ref() {
                    "23505" | "23503" | "23502" | "23514" => DatabaseError::ConstraintViolation {
                        constraint: code.to_string(),
                        message,
                    },
                    "40001" | "40P01" => DatabaseError::Transaction {
                        message: format!("Serialization failure: {}", message),
                    },
                    _ => DatabaseError::Query {
                        query: "unknown".to_string(),
                        message,
                    },
                }
            },
            sqlx::Error::Io(io_err) => DatabaseError::Connection {
                message: io_err.to_string(),
            },
            sqlx::Error::Tls(tls_err) => DatabaseError::Connection {
                message: format!("TLS error: {}", tls_err),
            },
            sqlx::Error::Protocol(msg) => DatabaseError::Connection {
                message: format!("Protocol error: {}", msg),
            },
            sqlx::Error::RowNotFound => DatabaseError::NotFound {
                resource_type: "row".to_string(),
                identifier: "unknown".to_string(),
            },
            sqlx::Error::TypeNotFound { type_name } => DatabaseError::Serialization {
                message: format!("Type not found: {}", type_name),
            },
            sqlx::Error::ColumnIndexOutOfBounds { index, len } => DatabaseError::Query {
                query: "unknown".to_string(),
                message: format!("Column index {} out of bounds (len: {})", index, len),
            },
            sqlx::Error::ColumnNotFound(col) => DatabaseError::Query {
                query: "unknown".to_string(),
                message: format!("Column not found: {}", col),
            },
            sqlx::Error::ColumnDecode { index, source } => DatabaseError::Serialization {
                message: format!("Column decode error at index {}: {}", index, source),
            },
            sqlx::Error::Decode(decode_err) => DatabaseError::Serialization {
                message: decode_err.to_string(),
            },
            sqlx::Error::PoolTimedOut => DatabaseError::Timeout {
                operation: "pool_acquire".to_string(),
                timeout_secs: 30_u64, // Default timeout
            },
            sqlx::Error::PoolClosed => DatabaseError::ConnectionPool {
                message: "Connection pool is closed".to_string(),
            },
            sqlx::Error::WorkerCrashed => DatabaseError::ConnectionPool {
                message: "Database worker crashed".to_string(),
            },
            _ => DatabaseError::Unknown {
                message: err.to_string(),
            },
        }
    }
}

/// Convert from serde_json errors
impl From<serde_json::Error> for DatabaseError {
    fn from(err: serde_json::Error) -> Self {
        DatabaseError::Serialization {
            message: err.to_string(),
        }
    }
}

/// Convert from config errors
impl From<config::error::ConfigError> for DatabaseError {
    fn from(err: config::error::ConfigError) -> Self {
        DatabaseError::Configuration {
            message: err.to_string(),
        }
    }
}

/// Result type alias for database operations
pub type DatabaseResult<T> = Result<T, DatabaseError>;

/// Error context helper for adding additional information
pub trait ErrorContext<T> {
    fn with_context(self, context: &str) -> DatabaseResult<T>;
    fn with_query_context(self, query: &str) -> DatabaseResult<T>;
}

impl<T> ErrorContext<T> for DatabaseResult<T> {
    fn with_context(self, context: &str) -> DatabaseResult<T> {
        self.map_err(|err| match err {
            DatabaseError::Unknown { message } => DatabaseError::Unknown {
                message: format!("{}: {}", context, message),
            },
            other => other,
        })
    }

    fn with_query_context(self, query: &str) -> DatabaseResult<T> {
        self.map_err(|err| match err {
            DatabaseError::Query { message, .. } => DatabaseError::Query {
                query: query.to_string(),
                message,
            },
            DatabaseError::Unknown { message } => DatabaseError::Query {
                query: query.to_string(),
                message,
            },
            other => other,
        })
    }
}

/// Implement ErrorContext for Result<T, sqlx::Error>
impl<T> ErrorContext<T> for Result<T, sqlx::Error> {
    fn with_context(self, context: &str) -> DatabaseResult<T> {
        self.map_err(|err| {
            let db_err = DatabaseError::from(err);
            match db_err {
                DatabaseError::Unknown { message } => DatabaseError::Unknown {
                    message: format!("{}: {}", context, message),
                },
                other => other,
            }
        })
    }

    fn with_query_context(self, query: &str) -> DatabaseResult<T> {
        self.map_err(|err| {
            let db_err = DatabaseError::from(err);
            match db_err {
                DatabaseError::Query { message, .. } => DatabaseError::Query {
                    query: query.to_string(),
                    message,
                },
                DatabaseError::Unknown { message } => DatabaseError::Query {
                    query: query.to_string(),
                    message,
                },
                other => other,
            }
        })
    }
}

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

    #[test]
    fn test_error_retryable() {
        let retryable = DatabaseError::Connection {
            message: "Connection refused".to_string(),
        };
        assert!(retryable.is_retryable());

        let not_retryable = DatabaseError::Validation {
            field: "email".to_string(),
            message: "Invalid format".to_string(),
        };
        assert!(!not_retryable.is_retryable());
    }

    #[test]
    fn test_error_severity() {
        let critical = DatabaseError::Configuration {
            message: "Invalid config".to_string(),
        };
        assert_eq!(critical.severity(), ErrorSeverity::Critical);

        let low = DatabaseError::NotFound {
            resource_type: "user".to_string(),
            identifier: "123".to_string(),
        };
        assert_eq!(low.severity(), ErrorSeverity::Low);
    }

    #[test]
    fn test_error_context() {
        let result: DatabaseResult<i32> = Err(DatabaseError::Unknown {
            message: "Something went wrong".to_string(),
        });

        let with_context = result.with_context("User operation failed");
        match with_context {
            Err(DatabaseError::Unknown { message }) => {
                assert!(message.contains("User operation failed"));
            },
            _ => panic!("Expected Unknown error with context"),
        }
    }
}
