#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::indexing_slicing,
    clippy::absurd_extreme_comparisons,
    clippy::field_reassign_with_default,
    clippy::manual_flatten,
    clippy::tests_outside_test_module,
    unused_imports,
    unused_variables,
    unused_comparisons,
    dead_code,
)]
//! Integration tests for the database package
//!
//! These tests require a running PostgreSQL database.
//! Use docker-compose to start the test database:
//! ```bash
//! docker-compose up -d postgres
//! ```

use config::database::{DatabaseConfig, PoolConfig, TransactionConfig};
use database::{Database, DatabaseError, DatabasePool, OrderDirection, QueryBuilder};
use sqlx::FromRow;
use std::time::Duration;

/// Helper to get test database configuration
fn test_db_config() -> DatabaseConfig {
    let mut config = DatabaseConfig::new();
    // Use a known good connection string
    let db_url = "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string();
    config.url = db_url.clone();
    config.pool.database_url = db_url;
    config.application_name = Some("database_integration_tests".to_string());
    config.enable_query_logging = true;
    config
}

/// Test row structure for fetch operations
#[derive(Debug, Clone, FromRow)]
struct TestRow {
    id: i32,
    name: String,
}

mod database_basic_operations {
    use super::*;

    #[tokio::test]
    async fn test_database_new() {
        let config = test_db_config();
        let db = Database::new(config).await;

        assert!(db.is_ok(), "Database creation should succeed");
    }

    #[tokio::test]
    async fn test_database_from_env() {
        std::env::set_var(
            "DATABASE_URL",
            "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt",
        );

        let db = Database::from_env().await;
        assert!(db.is_ok(), "Database creation from env should succeed");
    }

    #[tokio::test]
    async fn test_database_ping() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let result = db.ping().await;
        assert!(result.is_ok(), "Database ping should succeed");
    }

    #[tokio::test]
    async fn test_database_health_check() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let result = db.health_check().await;
        assert!(result.is_ok(), "Health check should succeed");
        assert!(result.unwrap(), "Database should be healthy");
    }

    #[tokio::test]
    async fn test_database_health_info() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let health_info = db.health_info().await;
        assert!(health_info.is_ok(), "Health info should be available");

        let info = health_info.unwrap();
        assert!(info.healthy, "Database should be healthy");
        assert!(!info.version.is_empty(), "Version should be populated");
        assert!(
            info.database_size >= 0,
            "Database size should be non-negative"
        );
    }

    #[tokio::test]
    async fn test_database_version() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let version = db.version().await;
        assert!(version.is_ok(), "Version query should succeed");
        assert!(
            version.unwrap().contains("PostgreSQL"),
            "Version should contain PostgreSQL"
        );
    }

    #[tokio::test]
    async fn test_database_size() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let size = db.database_size().await;
        assert!(size.is_ok(), "Database size query should succeed");
        assert!(size.unwrap() > 0, "Database size should be positive");
    }

    #[tokio::test]
    async fn test_database_table_exists() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Test with a table that likely exists
        let exists = db.table_exists("migrations").await;
        assert!(exists.is_ok(), "Table exists check should succeed");

        // Test with a table that doesn't exist
        let not_exists = db.table_exists("nonexistent_table_xyz").await;
        assert!(not_exists.is_ok(), "Table exists check should succeed");
        assert!(
            !not_exists.unwrap(),
            "Nonexistent table should return false"
        );
    }
}

mod database_query_operations {
    use super::*;

    #[tokio::test]
    async fn test_database_execute() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Create a temp table
        let result = db
            .execute("CREATE TEMP TABLE test_execute (id SERIAL PRIMARY KEY, name TEXT)")
            .await;

        assert!(result.is_ok(), "Execute should succeed");
    }

    #[tokio::test]
    async fn test_database_execute_with_param() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Create temp table
        db.execute("CREATE TEMP TABLE test_param (id SERIAL PRIMARY KEY, name TEXT)")
            .await
            .expect("Table creation failed");

        // Insert with parameter
        let result = db
            .execute_with_param("INSERT INTO test_param (name) VALUES ($1)", "test_name")
            .await;

        assert!(result.is_ok(), "Execute with param should succeed");
        assert_eq!(result.unwrap(), 1, "Should insert 1 row");
    }

    #[tokio::test]
    async fn test_database_execute_raw() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Create temp table
        db.execute("CREATE TEMP TABLE test_raw (id SERIAL PRIMARY KEY, value INT)")
            .await
            .expect("Table creation failed");

        // Insert with raw SQL
        let result = db
            .execute_raw("INSERT INTO test_raw (value) VALUES ($1)", 42)
            .await;

        assert!(result.is_ok(), "Execute raw should succeed");
        assert_eq!(result.unwrap(), 1, "Should insert 1 row");
    }

    #[tokio::test]
    async fn test_database_query_builder_integration() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Test query builder factory methods
        let select_builder = Database::select(&["*"]);
        assert!(format!("{:?}", select_builder).contains("SelectBuilder"));

        let insert_builder = Database::insert("test_table");
        assert!(format!("{:?}", insert_builder).contains("InsertBuilder"));

        let update_builder = Database::update("test_table");
        assert!(format!("{:?}", update_builder).contains("UpdateBuilder"));

        let delete_builder = Database::delete("test_table");
        assert!(format!("{:?}", delete_builder).contains("DeleteBuilder"));
    }
}

mod database_pool_tests {
    use super::*;

    #[tokio::test]
    async fn test_pool_acquire() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let conn = db.acquire().await;
        assert!(conn.is_ok(), "Pool acquire should succeed");
    }

    #[tokio::test]
    async fn test_pool_statistics() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let stats = db.pool_stats().await;
        assert!(
            stats.active_connections >= 0,
            "Active connections should be valid"
        );
        assert!(
            stats.idle_connections >= 0,
            "Idle connections should be valid"
        );
    }

    #[tokio::test]
    async fn test_pool_size_metrics() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let size = db.pool_size();
        assert!(size > 0, "Pool size should be positive");

        let idle = db.idle_connections();
        assert!(idle >= 0, "Idle connections should be non-negative");
    }

    #[tokio::test]
    async fn test_pool_close() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        assert!(!db.is_closed(), "Pool should not be closed initially");

        db.close().await;

        assert!(db.is_closed(), "Pool should be closed after close()");
    }

    #[tokio::test]
    async fn test_pool_reset_stats() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Generate some activity
        let _ = db.ping().await;

        db.reset_stats().await;

        let stats = db.pool_stats().await;
        // Note: total_acquisitions might not be 0 due to health checks
        assert!(
            stats.failed_acquisitions >= 0,
            "Failed acquisitions should be reset"
        );
    }
}

mod database_transaction_tests {
    use super::*;

    #[tokio::test]
    async fn test_begin_transaction() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let tx = db.begin_transaction().await;
        assert!(tx.is_ok(), "Begin transaction should succeed");

        let tx = tx.unwrap();
        assert!(
            tx.elapsed().as_secs() < 1,
            "Transaction should start immediately"
        );
    }

    #[tokio::test]
    async fn test_begin_transaction_with_timeout() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let tx = db
            .begin_transaction_with_timeout(Duration::from_secs(60))
            .await;
        assert!(tx.is_ok(), "Begin transaction with timeout should succeed");
    }

    #[tokio::test]
    async fn test_transaction_commit() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Create temp table
        db.execute("CREATE TEMP TABLE test_tx_commit (id SERIAL PRIMARY KEY, value INT)")
            .await
            .expect("Table creation failed");

        let mut tx = db
            .begin_transaction()
            .await
            .expect("Begin transaction failed");

        // Execute within transaction
        let result = tx
            .execute("INSERT INTO test_tx_commit (value) VALUES (100)")
            .await;
        assert!(result.is_ok(), "Transaction execute should succeed");

        // Commit
        let commit_result = tx.commit().await;
        assert!(commit_result.is_ok(), "Transaction commit should succeed");
    }

    #[tokio::test]
    async fn test_transaction_rollback() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Create temp table
        db.execute("CREATE TEMP TABLE test_tx_rollback (id SERIAL PRIMARY KEY, value INT)")
            .await
            .expect("Table creation failed");

        let mut tx = db
            .begin_transaction()
            .await
            .expect("Begin transaction failed");

        // Execute within transaction
        let result = tx
            .execute("INSERT INTO test_tx_rollback (value) VALUES (200)")
            .await;
        assert!(result.is_ok(), "Transaction execute should succeed");

        // Rollback
        let rollback_result = tx.rollback().await;
        assert!(
            rollback_result.is_ok(),
            "Transaction rollback should succeed"
        );
    }

    #[tokio::test]
    async fn test_with_transaction() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Create temp table
        db.execute("CREATE TEMP TABLE test_with_tx (id SERIAL PRIMARY KEY, value INT)")
            .await
            .expect("Table creation failed");

        let result = db
            .with_transaction(|mut tx| async move {
                tx.execute("INSERT INTO test_with_tx (value) VALUES (300)")
                    .await?;
                Ok((42, tx))
            })
            .await;

        assert!(result.is_ok(), "with_transaction should succeed");
        assert_eq!(result.unwrap(), 42, "Should return the closure result");
    }

    #[tokio::test]
    async fn test_with_transaction_timeout() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let result = db
            .with_transaction_timeout(
                |mut tx| async move {
                    tx.execute("SELECT 1").await?;
                    Ok(("success", tx))
                },
                Duration::from_secs(10),
            )
            .await;

        assert!(result.is_ok(), "with_transaction_timeout should succeed");
        assert_eq!(result.unwrap(), "success");
    }

    #[tokio::test]
    async fn test_transaction_statistics() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Perform some transactions
        let _ = db.with_transaction(|tx| async move { Ok(((), tx)) }).await;

        let stats = db.transaction_stats();
        assert!(
            stats.total_transactions > 0,
            "Should have transaction count"
        );
    }

    #[tokio::test]
    async fn test_transaction_savepoint() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Create temp table
        db.execute("CREATE TEMP TABLE test_savepoint (id SERIAL PRIMARY KEY, value INT)")
            .await
            .expect("Table creation failed");

        let mut tx = db
            .begin_transaction()
            .await
            .expect("Begin transaction failed");

        // Create savepoint
        let sp_result = tx.savepoint("sp1").await;
        assert!(sp_result.is_ok(), "Savepoint creation should succeed");

        // Execute after savepoint
        tx.execute("INSERT INTO test_savepoint (value) VALUES (400)")
            .await
            .unwrap();

        // Rollback to savepoint
        let rollback_result = tx.rollback_to_savepoint("sp1").await;
        assert!(
            rollback_result.is_ok(),
            "Rollback to savepoint should succeed"
        );

        tx.commit().await.unwrap();
    }

    #[tokio::test]
    async fn test_transaction_release_savepoint() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        let mut tx = db
            .begin_transaction()
            .await
            .expect("Begin transaction failed");

        // Create savepoint
        tx.savepoint("sp2")
            .await
            .expect("Savepoint creation failed");

        // Release savepoint
        let release_result = tx.release_savepoint("sp2").await;
        assert!(release_result.is_ok(), "Release savepoint should succeed");

        tx.commit().await.unwrap();
    }
}

mod database_config_tests {
    use super::*;

    #[test]
    fn test_database_config_getter() {
        let config = test_db_config();
        let runtime = tokio::runtime::Runtime::new().unwrap();

        runtime.block_on(async {
            let db = Database::new(config.clone())
                .await
                .expect("Database creation failed");

            let retrieved_config = db.config();
            assert_eq!(retrieved_config.application_name, config.application_name);
            assert_eq!(
                retrieved_config.enable_query_logging,
                config.enable_query_logging
            );
        });
    }

    #[tokio::test]
    async fn test_invalid_database_url() {
        let mut config = DatabaseConfig::new();
        config.url = "invalid://not-a-database".to_string();

        let result = Database::new(config).await;
        assert!(result.is_err(), "Invalid URL should fail");

        if let Err(e) = result {
            assert!(matches!(e, DatabaseError::Configuration { .. }));
        }
    }

    #[tokio::test]
    async fn test_database_config_validation() {
        let mut config = DatabaseConfig::new();
        config.url = String::new(); // Invalid empty URL

        let result = Database::new(config).await;
        assert!(result.is_err(), "Empty URL should fail validation");
    }
}

mod error_handling_tests {
    use super::*;

    #[tokio::test]
    async fn test_query_error() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Execute invalid SQL
        let result = db.execute("INVALID SQL STATEMENT").await;
        assert!(result.is_err(), "Invalid SQL should fail");

        if let Err(e) = result {
            assert!(matches!(e, DatabaseError::Query { .. }));
        }
    }

    #[tokio::test]
    async fn test_table_not_found_error() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Query non-existent table
        let result = db.execute("SELECT * FROM nonexistent_table_xyz").await;
        assert!(result.is_err(), "Querying non-existent table should fail");
    }

    #[tokio::test]
    async fn test_transaction_timeout() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Begin transaction with very short timeout
        let tx = db
            .begin_transaction_with_timeout(Duration::from_nanos(1))
            .await;

        if let Ok(tx) = tx {
            // Should timeout immediately
            assert!(tx.is_timed_out(), "Transaction should be timed out");

            // Attempting to commit should fail with timeout error
            let commit_result = tx.commit().await;
            assert!(commit_result.is_err(), "Commit should fail due to timeout");
        }
    }

    #[tokio::test]
    async fn test_constraint_violation_handling() {
        let config = test_db_config();
        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Create temp table with unique constraint
        db.execute("CREATE TEMP TABLE test_constraint (id SERIAL PRIMARY KEY, email TEXT UNIQUE)")
            .await
            .expect("Table creation failed");

        // Insert first row
        db.execute("INSERT INTO test_constraint (email) VALUES ('test@example.com')")
            .await
            .expect("First insert should succeed");

        // Try to insert duplicate
        let result = db
            .execute("INSERT INTO test_constraint (email) VALUES ('test@example.com')")
            .await;
        assert!(result.is_err(), "Duplicate insert should fail");

        if let Err(e) = result {
            // Should be constraint violation error
            assert!(
                matches!(e, DatabaseError::ConstraintViolation { .. })
                    || matches!(e, DatabaseError::Query { .. })
            );
        }
    }
}

mod pool_configuration_tests {
    use super::*;

    #[tokio::test]
    async fn test_custom_pool_config() {
        let mut config = test_db_config();
        config.pool.max_connections = 5;
        config.pool.min_connections = 1;

        let db = Database::new(config).await;
        assert!(
            db.is_ok(),
            "Database with custom pool config should succeed"
        );
    }

    #[tokio::test]
    async fn test_pool_config_validation() {
        let mut pool_config = PoolConfig::default();
        pool_config.min_connections = 10;
        pool_config.max_connections = 5; // Invalid: min > max

        let mut config = test_db_config();
        config.pool = pool_config;

        let result = Database::new(config).await;
        assert!(result.is_err(), "Invalid pool config should fail");
    }
}

mod transaction_configuration_tests {
    use super::*;

    #[tokio::test]
    async fn test_custom_transaction_config() {
        let mut config = test_db_config();
        config.transaction.default_timeout_secs = 60;
        config.transaction.max_retries = 5;
        config.transaction.enable_retry = true;

        let db = Database::new(config).await;
        assert!(
            db.is_ok(),
            "Database with custom transaction config should succeed"
        );
    }

    #[tokio::test]
    async fn test_transaction_retry_disabled() {
        let mut config = test_db_config();
        config.transaction.enable_retry = false;

        let db = Database::new(config)
            .await
            .expect("Database creation failed");

        // Transaction should not retry on failure
        let result = db
            .with_transaction(|mut tx| async move {
                tx.execute("INVALID SQL").await?;
                Ok(((), tx))
            })
            .await;

        assert!(result.is_err(), "Transaction should fail without retry");
    }
}
