#![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,
)]
//! Comprehensive Migration Tests for Database Schema Evolution
//!
//! Tests cover:
//! - Migration metadata verification
//! - Sequential migration ordering
//! - Schema evolution (columns, indexes, constraints)
//! - Foreign key and check constraints
//! - Index creation and verification
//! - Extension and custom type verification
//! - Complete schema validation
//! - Data preservation verification
//!
//! Test Database Setup:
//! ```bash
//! docker-compose up -d postgres
//! cargo sqlx migrate run
//! ```
//!
//! Note: Tests use the existing foxhunt database with applied migrations.

use sqlx::postgres::PgPoolOptions;
use sqlx::{PgPool, Row};
use std::time::Instant;
use uuid::Uuid;

const DATABASE_URL: &str = "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt";

/// Helper to get database pool
async fn get_pool() -> Result<PgPool, sqlx::Error> {
    PgPoolOptions::new()
        .max_connections(5)
        .connect(DATABASE_URL)
        .await
}

/// Helper to count applied migrations
async fn count_migrations(pool: &PgPool) -> Result<i64, sqlx::Error> {
    let row = sqlx::query("SELECT COUNT(*) as count FROM _sqlx_migrations")
        .fetch_one(pool)
        .await?;
    Ok(row.get("count"))
}

/// Helper to get migration versions
async fn get_migration_versions(pool: &PgPool) -> Result<Vec<i64>, sqlx::Error> {
    let rows = sqlx::query("SELECT version FROM _sqlx_migrations ORDER BY version")
        .fetch_all(pool)
        .await?;
    Ok(rows.iter().map(|r| r.get("version")).collect())
}

/// Helper to check if table exists
async fn table_exists(pool: &PgPool, table_name: &str) -> Result<bool, sqlx::Error> {
    let row = sqlx::query(
        "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1) as exists",
    )
    .bind(table_name)
    .fetch_one(pool)
    .await?;
    Ok(row.get("exists"))
}

/// Helper to get table columns
async fn get_columns(pool: &PgPool, table_name: &str) -> Result<Vec<String>, sqlx::Error> {
    let rows = sqlx::query(
        "SELECT column_name FROM information_schema.columns
         WHERE table_name = $1 ORDER BY ordinal_position",
    )
    .bind(table_name)
    .fetch_all(pool)
    .await?;
    Ok(rows.iter().map(|r| r.get("column_name")).collect())
}

/// Helper to check if index exists
async fn index_exists(pool: &PgPool, index_name: &str) -> Result<bool, sqlx::Error> {
    let row =
        sqlx::query("SELECT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = $1) as exists")
            .bind(index_name)
            .fetch_one(pool)
            .await?;
    Ok(row.get("exists"))
}

// ============================================================================
// Migration Metadata Tests
// ============================================================================

#[tokio::test]
async fn test_migrations_table_exists() {
    let pool = get_pool().await.expect("Failed to connect");
    assert!(table_exists(&pool, "_sqlx_migrations").await.unwrap());
    println!("✅ Migration tracking table exists");
    pool.close().await;
}

#[tokio::test]
async fn test_migrations_applied() {
    let pool = get_pool().await.expect("Failed to connect");
    let count = count_migrations(&pool).await.unwrap();
    assert!(count > 0, "Expected migrations, got {}", count);
    println!("✅ {} migrations applied", count);
    pool.close().await;
}

#[tokio::test]
async fn test_migration_sequential_ordering() {
    let pool = get_pool().await.expect("Failed to connect");
    let versions = get_migration_versions(&pool).await.unwrap();

    for i in 1..versions.len() {
        assert!(
            versions[i] > versions[i - 1],
            "Migrations not sequential: {} > {}",
            versions[i],
            versions[i - 1]
        );
    }
    println!("✅ {} migrations in sequential order", versions.len());
    pool.close().await;
}

#[tokio::test]
async fn test_all_migrations_succeeded() {
    let pool = get_pool().await.expect("Failed to connect");
    let failed: i64 =
        sqlx::query_scalar("SELECT COUNT(*) FROM _sqlx_migrations WHERE success = false")
            .fetch_one(&pool)
            .await
            .unwrap();

    assert_eq!(failed, 0, "Found {} failed migrations", failed);
    println!("✅ All migrations succeeded");
    pool.close().await;
}

#[tokio::test]
async fn test_migration_timestamps() {
    let pool = get_pool().await.expect("Failed to connect");
    let rows = sqlx::query("SELECT version, installed_on FROM _sqlx_migrations ORDER BY version")
        .fetch_all(&pool)
        .await
        .unwrap();

    assert!(!rows.is_empty(), "No migrations found");
    println!("✅ {} migrations have timestamps", rows.len());
    pool.close().await;
}

// ============================================================================
// Schema Verification Tests
// ============================================================================

#[tokio::test]
async fn test_trading_events_table() {
    let pool = get_pool().await.expect("Failed to connect");
    assert!(table_exists(&pool, "trading_events").await.unwrap());

    let cols = get_columns(&pool, "trading_events").await.unwrap();
    assert!(cols.contains(&"id".to_string()));
    assert!(cols.contains(&"event_type".to_string()));
    assert!(cols.contains(&"event_timestamp".to_string()));

    println!("✅ trading_events table verified: {} columns", cols.len());
    pool.close().await;
}

#[tokio::test]
async fn test_risk_events_table() {
    let pool = get_pool().await.expect("Failed to connect");
    assert!(table_exists(&pool, "risk_events").await.unwrap());

    let cols = get_columns(&pool, "risk_events").await.unwrap();
    assert!(cols.contains(&"id".to_string()));
    assert!(cols.contains(&"event_type".to_string()));

    println!("✅ risk_events table verified: {} columns", cols.len());
    pool.close().await;
}

#[tokio::test]
async fn test_audit_log_table() {
    let pool = get_pool().await.expect("Failed to connect");
    assert!(table_exists(&pool, "audit_log").await.unwrap());
    println!("✅ audit_log table verified");
    pool.close().await;
}

#[tokio::test]
async fn test_users_table() {
    let pool = get_pool().await.expect("Failed to connect");
    assert!(table_exists(&pool, "users").await.unwrap());

    let cols = get_columns(&pool, "users").await.unwrap();
    assert!(cols.contains(&"id".to_string()));
    assert!(cols.contains(&"username".to_string()));
    assert!(cols.contains(&"email".to_string()));
    assert!(cols.contains(&"password_hash".to_string()));

    println!("✅ users table verified: {} columns", cols.len());
    pool.close().await;
}

#[tokio::test]
async fn test_executions_table() {
    let pool = get_pool().await.expect("Failed to connect");
    assert!(table_exists(&pool, "executions").await.unwrap());

    let cols = get_columns(&pool, "executions").await.unwrap();
    assert!(cols.contains(&"id".to_string()));
    assert!(cols.contains(&"order_id".to_string()));
    assert!(cols.contains(&"account_id".to_string()));
    assert!(cols.contains(&"symbol".to_string()));

    println!("✅ executions table verified: {} columns", cols.len());
    pool.close().await;
}

// ============================================================================
// Index Verification Tests
// ============================================================================

#[tokio::test]
async fn test_executions_indexes() {
    let pool = get_pool().await.expect("Failed to connect");

    assert!(index_exists(&pool, "idx_executions_account_id")
        .await
        .unwrap());
    assert!(index_exists(&pool, "idx_executions_order_id")
        .await
        .unwrap());

    println!("✅ Executions table indexes verified");
    pool.close().await;
}

#[tokio::test]
async fn test_index_count() {
    let pool = get_pool().await.expect("Failed to connect");

    let count: i64 =
        sqlx::query_scalar("SELECT COUNT(*) FROM pg_indexes WHERE schemaname = 'public'")
            .fetch_one(&pool)
            .await
            .unwrap();

    assert!(count >= 5, "Expected at least 5 indexes, got {}", count);
    println!("✅ {} indexes found", count);
    pool.close().await;
}

// ============================================================================
// Constraint Tests
// ============================================================================

#[tokio::test]
async fn test_check_constraint_negative_quantity() {
    let pool = get_pool().await.expect("Failed to connect");

    if !table_exists(&pool, "executions").await.unwrap() {
        println!("⚠️  Skipping: executions table doesn't exist");
        return;
    }

    let result = sqlx::query(
        "INSERT INTO executions (order_id, account_id, symbol, side, quantity, price)
         VALUES ($1, $2, $3, $4, $5, $6)",
    )
    .bind(Uuid::new_v4())
    .bind("test")
    .bind("TEST")
    .bind("buy")
    .bind(-100_i64)
    .bind(1000_i64)
    .execute(&pool)
    .await;

    assert!(
        result.is_err(),
        "Check constraint should reject negative quantity"
    );
    println!("✅ Check constraint verified");
    pool.close().await;
}

#[tokio::test]
async fn test_unique_constraint_username() {
    let pool = get_pool().await.expect("Failed to connect");

    let username = format!("test_user_{}", Uuid::new_v4().simple());

    // First insert should succeed
    let result1 = sqlx::query(
        "INSERT INTO users (username, email, password_hash, salt)
         VALUES ($1, $2, $3, $4)",
    )
    .bind(&username)
    .bind(format!("{}@test.com", username))
    .bind("hash")
    .bind("salt")
    .execute(&pool)
    .await;

    if result1.is_ok() {
        // Second insert should fail
        let result2 = sqlx::query(
            "INSERT INTO users (username, email, password_hash, salt)
             VALUES ($1, $2, $3, $4)",
        )
        .bind(&username)
        .bind(format!("{}2@test.com", username))
        .bind("hash")
        .bind("salt")
        .execute(&pool)
        .await;

        assert!(
            result2.is_err(),
            "Unique constraint should reject duplicate"
        );
        println!("✅ Unique constraint verified");

        // Cleanup
        sqlx::query("DELETE FROM users WHERE username = $1")
            .bind(&username)
            .execute(&pool)
            .await
            .ok();
    } else {
        println!("⚠️  Skipping: users table not writable");
    }

    pool.close().await;
}

// ============================================================================
// Extension Tests
// ============================================================================

#[tokio::test]
async fn test_uuid_extension() {
    let pool = get_pool().await.expect("Failed to connect");

    let extensions: Vec<String> =
        sqlx::query_scalar("SELECT extname FROM pg_extension WHERE extname = 'uuid-ossp'")
            .fetch_all(&pool)
            .await
            .unwrap();

    assert!(
        !extensions.is_empty(),
        "uuid-ossp extension should be installed"
    );
    println!("✅ uuid-ossp extension installed");
    pool.close().await;
}

#[tokio::test]
async fn test_custom_types() {
    let pool = get_pool().await.expect("Failed to connect");

    let types: Vec<String> = sqlx::query_scalar(
        "SELECT typname FROM pg_type
         WHERE typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
         AND typtype = 'e'",
    )
    .fetch_all(&pool)
    .await
    .unwrap();

    assert!(!types.is_empty(), "Should have custom enum types");
    println!("✅ {} custom types found", types.len());
    pool.close().await;
}

// ============================================================================
// Data Tests
// ============================================================================

#[tokio::test]
async fn test_insert_and_query_execution() {
    let pool = get_pool().await.expect("Failed to connect");

    if !table_exists(&pool, "executions").await.unwrap() {
        println!("⚠️  Skipping: executions table doesn't exist");
        return;
    }

    let test_id = Uuid::new_v4();

    // Insert test record
    let result = sqlx::query(
        "INSERT INTO executions (id, order_id, account_id, symbol, side, quantity, price)
         VALUES ($1, $2, $3, $4, $5, $6, $7)",
    )
    .bind(test_id)
    .bind(Uuid::new_v4())
    .bind("test_account")
    .bind("TESTBTC")
    .bind("buy")
    .bind(100_i64)
    .bind(50000_i64)
    .execute(&pool)
    .await;

    if result.is_ok() {
        // Query it back
        let found: Option<(Uuid,)> = sqlx::query_as("SELECT id FROM executions WHERE id = $1")
            .bind(test_id)
            .fetch_optional(&pool)
            .await
            .unwrap();

        assert!(found.is_some(), "Should find inserted record");
        println!("✅ Data insertion and retrieval verified");

        // Cleanup
        sqlx::query("DELETE FROM executions WHERE id = $1")
            .bind(test_id)
            .execute(&pool)
            .await
            .ok();
    } else {
        println!("⚠️  Skipping: executions table not writable");
    }

    pool.close().await;
}

#[tokio::test]
async fn test_bulk_insert_performance() {
    let pool = get_pool().await.expect("Failed to connect");

    if !table_exists(&pool, "executions").await.unwrap() {
        println!("⚠️  Skipping: executions table doesn't exist");
        return;
    }

    let start = Instant::now();
    let batch_size = 100;
    let test_symbol = format!(
        "TEST{}",
        Uuid::new_v4()
            .simple()
            .to_string()
            .chars()
            .take(6)
            .collect::<String>()
    );

    for _ in 0..batch_size {
        let result = sqlx::query(
            "INSERT INTO executions (order_id, account_id, symbol, side, quantity, price)
             VALUES ($1, $2, $3, $4, $5, $6)",
        )
        .bind(Uuid::new_v4())
        .bind("test_account")
        .bind(&test_symbol)
        .bind("buy")
        .bind(100_i64)
        .bind(50000_i64)
        .execute(&pool)
        .await;

        if result.is_err() {
            println!("⚠️  Skipping: executions table not writable");
            pool.close().await;
            return;
        }
    }

    let duration = start.elapsed();
    let throughput = batch_size as f64 / duration.as_secs_f64();

    println!(
        "✅ Bulk insert: {} rows in {:?} ({:.0} rows/sec)",
        batch_size, duration, throughput
    );

    // Cleanup
    sqlx::query("DELETE FROM executions WHERE symbol = $1")
        .bind(&test_symbol)
        .execute(&pool)
        .await
        .ok();

    pool.close().await;
}

// ============================================================================
// Schema Coverage Tests
// ============================================================================

#[tokio::test]
async fn test_all_tables() {
    let pool = get_pool().await.expect("Failed to connect");

    let tables: Vec<String> = sqlx::query_scalar(
        "SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename",
    )
    .fetch_all(&pool)
    .await
    .unwrap();

    assert!(!tables.is_empty(), "Should have tables");
    println!("✅ {} tables found:", tables.len());
    for table in tables.iter().take(20) {
        println!("   - {}", table);
    }

    pool.close().await;
}

#[tokio::test]
async fn test_all_views() {
    let pool = get_pool().await.expect("Failed to connect");

    let views: Vec<String> = sqlx::query_scalar(
        "SELECT viewname FROM pg_views WHERE schemaname = 'public' ORDER BY viewname",
    )
    .fetch_all(&pool)
    .await
    .unwrap();

    println!("✅ {} views found", views.len());
    pool.close().await;
}

#[tokio::test]
async fn test_all_functions() {
    let pool = get_pool().await.expect("Failed to connect");

    let functions: Vec<String> = sqlx::query_scalar(
        "SELECT proname FROM pg_proc
         WHERE pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
         ORDER BY proname",
    )
    .fetch_all(&pool)
    .await
    .unwrap();

    println!("✅ {} functions found", functions.len());
    pool.close().await;
}

#[tokio::test]
async fn test_migration_file_count() {
    let migration_files = std::fs::read_dir("./migrations")
        .expect("Failed to read migrations directory")
        .filter_map(|e| e.ok())
        .filter(|e| {
            let name = e.file_name();
            let name_str = name.to_str().unwrap();
            e.path().extension().and_then(|s| s.to_str()) == Some("sql")
                && !name_str.contains("backup")
                && !name_str.contains("broken")
        })
        .count();

    assert!(migration_files > 0, "Should have migration files");
    println!("✅ {} migration files found", migration_files);
}
