#![deny(clippy::unwrap_used, clippy::expect_used)]
//! High-performance PostgreSQL database abstraction library
//!
//! This library provides a clean, type-safe abstraction over PostgreSQL with:
//! - Connection pooling with health monitoring
//! - Type-safe query building
//! - Transaction management with savepoints
//! - Comprehensive error handling
//! - Performance metrics and monitoring
//!
//! # Examples
//!
//! ## Basic Usage
//!
//! ```rust,no_run
//! use database::{Database, DatabaseConfig};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let config = DatabaseConfig::default();
//!     let db = Database::new(config).await?;
//!     
//!     // Execute a simple query
//!     let count: i64 = db.fetch_one("SELECT COUNT(*) FROM users").await?;
//!     println!("User count: {}", count);
//!     
//!     Ok(())
//! }
//! ```
//!
//! ## Transaction Usage
//!
//! ```rust,no_run
//! use database::{Database, DatabaseConfig};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let config = DatabaseConfig::default();
//!     let db = Database::new(config).await?;
//!     
//!     // Use transaction with retry logic
//!     let result = db.with_transaction(|mut tx| async move {
//!         tx.execute("INSERT INTO users (name) VALUES ('Alice')").await?;
//!         tx.execute("UPDATE settings SET user_count = user_count + 1").await?;
//!         Ok((42, tx)) // Return value and transaction
//!     }).await?;
//!     
//!     println!("Transaction result: {}", result);
//!     Ok(())
//! }
//! ```

pub mod error;
pub mod pool;
pub mod query;
pub mod transaction;

use crate::error::ErrorContext;
use config::database::DatabaseConfig;

// Re-export commonly used types for external crate usage
pub use crate::error::{DatabaseError, DatabaseResult};
pub use crate::pool::{DatabasePool, PoolStats};
pub use crate::query::{OrderDirection, QueryBuilder};
pub use crate::transaction::{DatabaseTransaction, TransactionManager, TransactionStats};

// serde imports removed - not needed
use sqlx::migrate::Migrator;
use sqlx::postgres::PgRow;
use sqlx::FromRow;
use std::future::Future;
use std::path::Path;
use std::time::Duration;
use tracing::{debug, info};

/// Main database interface providing high-level operations
#[derive(Debug, Clone)]
pub struct Database {
    pool: DatabasePool,
    transaction_manager: TransactionManager,
    config: DatabaseConfig,
}

impl Database {
    /// Create a new database instance
    pub async fn new(config: DatabaseConfig) -> DatabaseResult<Self> {
        // Validate configuration
        config
            .validate()
            .map_err(|e| DatabaseError::Configuration { message: e })?;

        info!(
            "Initializing database with application name: {}",
            config.application_name.as_deref().unwrap_or("foxhunt")
        );

        // Create connection pool
        let pool = DatabasePool::new(config.pool.clone()).await?;

        // Create transaction manager
        let transaction_manager = TransactionManager::new(pool.clone(), config.transaction.clone());

        let database = Self {
            pool,
            transaction_manager,
            config,
        };

        // Perform initial health check
        database.health_check().await?;

        info!("Database initialization completed successfully");
        Ok(database)
    }

    /// Create a database instance from environment variables
    pub async fn from_env() -> DatabaseResult<Self> {
        let database_url =
            std::env::var("DATABASE_URL").map_err(|_| DatabaseError::Configuration {
                message: "DATABASE_URL environment variable not set".to_string(),
            })?;

        let mut config = DatabaseConfig::new();
        config.url = database_url;
        Self::new(config).await
    }

    /// Get a connection from the pool
    pub async fn acquire(&self) -> DatabaseResult<sqlx::pool::PoolConnection<sqlx::Postgres>> {
        self.pool.acquire().await
    }

    /// Execute a query and return affected rows
    pub async fn execute(&self, query: &str) -> DatabaseResult<u64> {
        if self.config.enable_query_logging {
            debug!("Executing query: {}", query);
        }

        let result = sqlx::query(query)
            .execute(self.pool.inner())
            .await
            .with_query_context(query)?;

        Ok(result.rows_affected())
    }

    /// Execute a parameterized query with a single parameter
    pub async fn execute_with_param<T>(&self, query: &str, param: T) -> DatabaseResult<u64>
    where
        T: for<'q> sqlx::Encode<'q, sqlx::Postgres> + sqlx::Type<sqlx::Postgres> + Send,
    {
        if self.config.enable_query_logging {
            debug!("Executing parameterized query: {}", query);
        }

        let result = sqlx::query(query)
            .bind(param)
            .execute(self.pool.inner())
            .await
            .with_query_context(query)?;

        Ok(result.rows_affected())
    }

    /// Fetch all rows from a query
    pub async fn fetch_all<T>(&self, query: &str) -> DatabaseResult<Vec<T>>
    where
        T: for<'r> FromRow<'r, PgRow> + Send + Unpin,
    {
        if self.config.enable_query_logging {
            debug!("Fetching all rows: {}", query);
        }

        let rows = sqlx::query_as(query)
            .fetch_all(self.pool.inner())
            .await
            .with_query_context(query)?;

        Ok(rows)
    }

    /// Fetch one row from a query
    pub async fn fetch_one<T>(&self, query: &str) -> DatabaseResult<T>
    where
        T: for<'r> FromRow<'r, PgRow> + Send + Unpin,
    {
        if self.config.enable_query_logging {
            debug!("Fetching one row: {}", query);
        }

        let row = sqlx::query_as(query)
            .fetch_one(self.pool.inner())
            .await
            .with_query_context(query)?;

        Ok(row)
    }

    /// Fetch optional row from a query
    pub async fn fetch_optional<T>(&self, query: &str) -> DatabaseResult<Option<T>>
    where
        T: for<'r> FromRow<'r, PgRow> + Send + Unpin,
    {
        if self.config.enable_query_logging {
            debug!("Fetching optional row: {}", query);
        }

        let row = sqlx::query_as(query)
            .fetch_optional(self.pool.inner())
            .await
            .with_query_context(query)?;

        Ok(row)
    }

    /// Begin a new transaction
    pub async fn begin_transaction(&self) -> DatabaseResult<DatabaseTransaction> {
        self.transaction_manager.begin().await
    }

    /// Begin a transaction with custom timeout
    pub async fn begin_transaction_with_timeout(
        &self,
        timeout: Duration,
    ) -> DatabaseResult<DatabaseTransaction> {
        self.transaction_manager.begin_with_timeout(timeout).await
    }

    /// Execute a closure within a transaction
    pub async fn with_transaction<F, R, Fut>(&self, f: F) -> DatabaseResult<R>
    where
        F: Fn(DatabaseTransaction) -> Fut + Send + Sync,
        Fut: Future<Output = DatabaseResult<(R, DatabaseTransaction)>> + Send,
        R: Send,
    {
        self.transaction_manager.with_transaction(f).await
    }

    /// Execute a closure within a transaction with custom timeout
    pub async fn with_transaction_timeout<F, R, Fut>(
        &self,
        f: F,
        timeout: Duration,
    ) -> DatabaseResult<R>
    where
        F: Fn(DatabaseTransaction) -> Fut + Send + Sync,
        Fut: Future<Output = DatabaseResult<(R, DatabaseTransaction)>> + Send,
        R: Send,
    {
        self.transaction_manager
            .with_transaction_timeout(f, timeout)
            .await
    }

    /// Create a new query builder
    pub fn query_builder() -> QueryBuilder {
        QueryBuilder::new()
    }

    /// Create a SELECT query builder
    pub fn select<T: AsRef<str>>(columns: &[T]) -> crate::query::SelectBuilder {
        QueryBuilder::select(columns)
    }

    /// Create an INSERT query builder
    pub fn insert(table: &str) -> crate::query::InsertBuilder {
        QueryBuilder::insert(table)
    }

    /// Create an UPDATE query builder
    pub fn update(table: &str) -> crate::query::UpdateBuilder {
        QueryBuilder::update(table)
    }

    /// Create a DELETE query builder
    pub fn delete(table: &str) -> crate::query::DeleteBuilder {
        QueryBuilder::delete(table)
    }

    /// Perform a health check on the database
    pub async fn health_check(&self) -> DatabaseResult<bool> {
        self.pool.health_check().await
    }

    /// Get connection pool statistics
    pub async fn pool_stats(&self) -> PoolStats {
        self.pool.stats().await
    }

    /// Get transaction statistics
    pub fn transaction_stats(&self) -> TransactionStats {
        self.transaction_manager.stats()
    }

    /// Get database configuration
    pub fn config(&self) -> &DatabaseConfig {
        &self.config
    }

    /// Test database connectivity
    pub async fn ping(&self) -> DatabaseResult<()> {
        self.pool.ping().await
    }

    /// Reset all statistics
    pub async fn reset_stats(&self) {
        self.pool.reset_stats().await;
        self.transaction_manager.reset_stats();
        info!("All database statistics reset");
    }

    /// Close the database connection pool
    pub async fn close(&self) {
        info!("Closing database connection pool");
        self.pool.close().await;
    }

    /// Check if the database connection pool is closed
    pub fn is_closed(&self) -> bool {
        self.pool.is_closed()
    }

    /// Get current pool size
    pub fn pool_size(&self) -> u32 {
        self.pool.size()
    }

    /// Get number of idle connections
    pub fn idle_connections(&self) -> usize {
        self.pool.num_idle()
    }

    /// Execute a database migration
    pub async fn migrate(&self) -> DatabaseResult<()> {
        info!("Running database migrations");

        let migrator = Migrator::new(Path::new("./migrations"))
            .await
            .map_err(|e| DatabaseError::Migration {
                message: format!("Migration setup failed: {}", e),
            })?;

        migrator
            .run(self.pool.inner())
            .await
            .map_err(|e| DatabaseError::Migration {
                message: format!("Migration failed: {}", e),
            })?;

        info!("Database migrations completed successfully");
        Ok(())
    }

    /// Execute raw SQL with a single parameter
    pub async fn execute_raw<T>(&self, sql: &str, arg: T) -> DatabaseResult<u64>
    where
        T: for<'q> sqlx::Encode<'q, sqlx::Postgres> + sqlx::Type<sqlx::Postgres> + Send,
    {
        if self.config.enable_query_logging {
            debug!("Executing raw SQL: {}", sql);
        }

        let result = sqlx::query(sql)
            .bind(arg)
            .execute(self.pool.inner())
            .await
            .with_query_context(sql)?;

        Ok(result.rows_affected())
    }

    /// Check if a table exists
    pub async fn table_exists(&self, table_name: &str) -> DatabaseResult<bool> {
        let exists = sqlx::query_scalar(
            "SELECT EXISTS (
                SELECT FROM information_schema.tables 
                WHERE table_schema = 'public' 
                AND table_name = $1
            )",
        )
        .bind(table_name)
        .fetch_one(self.pool.inner())
        .await
        .with_context(&format!("Failed to check if table '{}' exists", table_name))?;

        Ok(exists)
    }

    /// Get database version
    pub async fn version(&self) -> DatabaseResult<String> {
        let version: String = sqlx::query_scalar("SELECT version()")
            .fetch_one(self.pool.inner())
            .await
            .with_context("Failed to get database version")?;

        Ok(version)
    }

    /// Get database size in bytes
    pub async fn database_size(&self) -> DatabaseResult<i64> {
        let size: i64 = sqlx::query_scalar("SELECT pg_database_size(current_database())")
            .fetch_one(self.pool.inner())
            .await
            .with_context("Failed to get database size")?;

        Ok(size)
    }
}

/// Database health information
#[derive(Debug, Clone)]
pub struct HealthInfo {
    /// Database is healthy
    pub healthy: bool,
    /// Database version
    pub version: String,
    /// Connection pool status
    pub pool_stats: PoolStats,
    /// Transaction statistics
    pub transaction_stats: TransactionStats,
    /// Database size in bytes
    pub database_size: i64,
    /// Response time in milliseconds
    pub response_time_ms: u64,
}

impl Database {
    /// Get comprehensive health information
    pub async fn health_info(&self) -> DatabaseResult<HealthInfo> {
        let start = std::time::Instant::now();

        // Perform health check
        let healthy = self.health_check().await.unwrap_or(false);

        // Get version (may fail if unhealthy)
        let version = self
            .version()
            .await
            .unwrap_or_else(|_| "unknown".to_string());

        // Get statistics
        let pool_stats = self.pool_stats().await;
        let transaction_stats = self.transaction_stats();

        // Get database size (may fail if unhealthy)
        let database_size = self.database_size().await.unwrap_or(0);

        let response_time_ms = start.elapsed().as_millis() as u64;

        Ok(HealthInfo {
            healthy,
            version,
            pool_stats,
            transaction_stats,
            database_size,
            response_time_ms,
        })
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;
    use config::database::{PoolConfig, TransactionConfig};

    #[test]
    fn test_database_config_new() {
        let config = DatabaseConfig::new();
        assert_eq!(config.application_name, Some("foxhunt".to_string()));
        assert!(!config.enable_query_logging);
        assert!(!config.url.is_empty());
    }

    #[test]
    fn test_database_config_validation() {
        let mut config = DatabaseConfig::new();
        assert!(config.validate().is_ok());

        config.url = String::new();
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_pool_config_defaults() {
        let pool_config = PoolConfig::default();
        assert!(pool_config.max_connections > 0);
        assert!(pool_config.min_connections > 0);
        assert!(pool_config.health_check_enabled);
    }

    #[test]
    fn test_transaction_config_defaults() {
        let tx_config = TransactionConfig::default();
        assert!(tx_config.max_retries > 0);
        assert!(tx_config.enable_retry);
        assert!(!tx_config.isolation_level.is_empty());
    }
}
