//! Database Migration System
//!
//! This module provides automated database migrations that support both PostgreSQL and SQLite.
//! Migrations are applied automatically when the application starts, ensuring the database
//! schema is always up to date.

use crate::utils;
use anyhow::Result;
use sqlx::{PgPool, Row, SqlitePool};
use std::collections::HashSet;
use tracing::{error, info, warn};

/// Migration struct representing a single database migration
#[derive(Debug, Clone)]
pub struct Migration {
    pub version: i32,
    #[allow(dead_code)]
    pub name: String,
    pub postgres_sql: String,
    pub sqlite_sql: String,
}

/// Migration runner that handles both PostgreSQL and SQLite
pub struct MigrationRunner {
    #[allow(dead_code)]
    migrations: Vec<Migration>,
}

#[allow(dead_code)]
impl MigrationRunner {
    /// Create a new migration runner with all available migrations
    /// Scans the migrations directory and loads SQL files in order
    pub fn new() -> Self {
        // Initialize utils to ensure project root is set
        utils::init();

        let migrations = Self::load_migrations_from_directory().unwrap_or_else(|e| {
            error!("Failed to load migrations: {}", e);
            vec![]
        });

        Self { migrations }
    }

    /// Scan migrations directory and load SQL files in order
    fn load_migrations_from_directory() -> Result<Vec<Migration>, Box<dyn std::error::Error>> {
        let migrations_dir = utils::paths::migrations_dir();

        if !migrations_dir.exists() {
            warn!(
                "Migrations directory not found: {}",
                migrations_dir.display()
            );
            return Ok(vec![]);
        }

        let _migrations: Vec<Migration> = Vec::new();
        let mut entries = std::fs::read_dir(&migrations_dir)?
            .filter_map(|entry| entry.ok())
            .filter(|entry| {
                entry.file_type().map(|ft| ft.is_file()).unwrap_or(false)
                    && entry.file_name().to_string_lossy().ends_with(".sql")
            })
            .collect::<Vec<_>>();

        // Sort by filename to ensure proper order
        entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));

        // Group postgres and sqlite files by migration number
        let mut migration_map = std::collections::HashMap::new();

        for entry in entries {
            let filename = entry.file_name().to_string_lossy().to_string();

            // Parse migration number and database type from filename
            // Expected format: 001_migration_name_postgres.sql or 001_migration_name_sqlite.sql
            if let Some((version_str, rest)) = filename.split_once('_') {
                if let Ok(version) = version_str.parse::<i32>() {
                    let is_postgres = rest.ends_with("_postgres.sql");
                    let is_sqlite = rest.ends_with("_sqlite.sql");

                    if is_postgres || is_sqlite {
                        let name = if is_postgres {
                            rest.strip_suffix("_postgres.sql").unwrap_or(rest)
                        } else {
                            rest.strip_suffix("_sqlite.sql").unwrap_or(rest)
                        };

                        let content = std::fs::read_to_string(entry.path()).unwrap_or_else(|e| {
                            warn!("Failed to read migration file {}: {}", filename, e);
                            String::new()
                        });

                        let migration = migration_map.entry(version).or_insert_with(|| Migration {
                            version,
                            name: name.to_string(),
                            postgres_sql: String::new(),
                            sqlite_sql: String::new(),
                        });

                        if is_postgres {
                            migration.postgres_sql = content;
                        } else {
                            migration.sqlite_sql = content;
                        }
                    }
                }
            }
        }

        // Convert to sorted vector
        let mut migrations: Vec<Migration> = migration_map.into_values().collect();
        migrations.sort_by_key(|m| m.version);

        info!(
            "Loaded {} migrations from {}",
            migrations.len(),
            migrations_dir.display()
        );

        Ok(migrations)
    }

    /// Get the list of available migrations
    pub fn get_migrations(&self) -> &Vec<Migration> {
        &self.migrations
    }

    /// Run migrations for PostgreSQL
    pub async fn run_postgres_migrations(&self, pool: &PgPool) -> Result<()> {
        info!("Running PostgreSQL migrations...");

        // Create migrations table if it doesn't exist
        self.create_postgres_migrations_table(pool).await?;

        // Get applied migrations
        let applied_migrations = self.get_applied_migrations_postgres(pool).await?;

        // Apply pending migrations
        for migration in &self.migrations {
            if !applied_migrations.contains(&migration.version) {
                info!(
                    "Applying migration {} - {}",
                    migration.version, migration.name
                );

                // Begin transaction
                let mut tx = pool.begin().await?;

                // Apply the migration
                sqlx::query(&migration.postgres_sql)
                    .execute(&mut *tx)
                    .await
                    .map_err(|e| {
                        error!(
                            "Failed to apply migration {} - {}: {}",
                            migration.version, migration.name, e
                        );
                        e
                    })?;

                // Record the migration as applied
                sqlx::query(
                    "INSERT INTO _migrations (version, name, applied_at) VALUES ($1, $2, NOW())",
                )
                .bind(migration.version)
                .bind(&migration.name)
                .execute(&mut *tx)
                .await?;

                // Commit transaction
                tx.commit().await?;

                info!(
                    "✅ Applied migration {} - {}",
                    migration.version, migration.name
                );
            } else {
                info!(
                    "⏭️  Skipping migration {} - {} (already applied)",
                    migration.version, migration.name
                );
            }
        }

        info!("✅ All PostgreSQL migrations completed successfully");
        Ok(())
    }

    /// Run migrations for SQLite
    pub async fn run_sqlite_migrations(&self, pool: &SqlitePool) -> Result<()> {
        info!("Running SQLite migrations...");

        // Create migrations table if it doesn't exist
        self.create_sqlite_migrations_table(pool).await?;

        // Get applied migrations
        let applied_migrations = self.get_applied_migrations_sqlite(pool).await?;

        // Apply pending migrations
        for migration in &self.migrations {
            if !applied_migrations.contains(&migration.version) {
                info!(
                    "Applying migration {} - {}",
                    migration.version, migration.name
                );

                // Begin transaction
                let mut tx = pool.begin().await?;

                // Apply the migration
                sqlx::query(&migration.sqlite_sql)
                    .execute(&mut *tx)
                    .await
                    .map_err(|e| {
                        error!(
                            "Failed to apply migration {} - {}: {}",
                            migration.version, migration.name, e
                        );
                        e
                    })?;

                // Record the migration as applied
                sqlx::query(
                    "INSERT INTO _migrations (version, name, applied_at) VALUES (?1, ?2, datetime('now'))"
                )
                .bind(migration.version)
                .bind(&migration.name)
                .execute(&mut *tx)
                .await?;

                // Commit transaction
                tx.commit().await?;

                info!(
                    "✅ Applied migration {} - {}",
                    migration.version, migration.name
                );
            } else {
                info!(
                    "⏭️  Skipping migration {} - {} (already applied)",
                    migration.version, migration.name
                );
            }
        }

        info!("✅ All SQLite migrations completed successfully");
        Ok(())
    }

    /// Create migrations tracking table for PostgreSQL
    async fn create_postgres_migrations_table(&self, pool: &PgPool) -> Result<()> {
        sqlx::query(
            r#"
            CREATE TABLE IF NOT EXISTS _migrations (
                version INTEGER PRIMARY KEY,
                name VARCHAR(255) NOT NULL,
                applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
            );
            "#,
        )
        .execute(pool)
        .await?;
        Ok(())
    }

    /// Create migrations tracking table for SQLite
    async fn create_sqlite_migrations_table(&self, pool: &SqlitePool) -> Result<()> {
        sqlx::query(
            r#"
            CREATE TABLE IF NOT EXISTS _migrations (
                version INTEGER PRIMARY KEY,
                name TEXT NOT NULL,
                applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
            );
            "#,
        )
        .execute(pool)
        .await?;
        Ok(())
    }

    /// Get list of applied migrations from PostgreSQL
    pub async fn get_applied_migrations_postgres(&self, pool: &PgPool) -> Result<HashSet<i32>> {
        let rows = sqlx::query("SELECT version FROM _migrations")
            .fetch_all(pool)
            .await?;

        let mut applied = HashSet::new();
        for row in rows {
            applied.insert(row.get("version"));
        }
        Ok(applied)
    }

    /// Get list of applied migrations from SQLite
    pub async fn get_applied_migrations_sqlite(&self, pool: &SqlitePool) -> Result<HashSet<i32>> {
        let rows = sqlx::query("SELECT version FROM _migrations")
            .fetch_all(pool)
            .await?;

        let mut applied = HashSet::new();
        for row in rows {
            applied.insert(row.get("version"));
        }
        Ok(applied)
    }

    /// Check if database schema is up to date
    pub async fn check_postgres_schema(&self, pool: &PgPool) -> Result<bool> {
        // Create migrations table if it doesn't exist
        self.create_postgres_migrations_table(pool).await?;

        let applied_migrations = self.get_applied_migrations_postgres(pool).await?;
        let total_migrations = self.migrations.len() as i32;
        let applied_count = applied_migrations.len() as i32;

        info!(
            "PostgreSQL migrations: {}/{} applied",
            applied_count, total_migrations
        );
        Ok(applied_count == total_migrations)
    }

    /// Check if database schema is up to date
    pub async fn check_sqlite_schema(&self, pool: &SqlitePool) -> Result<bool> {
        // Create migrations table if it doesn't exist
        self.create_sqlite_migrations_table(pool).await?;

        let applied_migrations = self.get_applied_migrations_sqlite(pool).await?;
        let total_migrations = self.migrations.len() as i32;
        let applied_count = applied_migrations.len() as i32;

        info!(
            "SQLite migrations: {}/{} applied",
            applied_count, total_migrations
        );
        Ok(applied_count == total_migrations)
    }

    /// Force reset migrations (WARNING: This will drop all tables!)
    pub async fn reset_postgres_database(&self, pool: &PgPool) -> Result<()> {
        warn!("⚠️  RESETTING PostgreSQL DATABASE - ALL DATA WILL BE LOST!");

        // Drop all tables
        sqlx::query("DROP SCHEMA public CASCADE; CREATE SCHEMA public;")
            .execute(pool)
            .await?;

        // Run all migrations fresh
        self.run_postgres_migrations(pool).await?;

        info!("✅ PostgreSQL database reset and migrations applied");
        Ok(())
    }

    /// Force reset migrations (WARNING: This will drop all tables!)
    pub async fn reset_sqlite_database(&self, pool: &SqlitePool) -> Result<()> {
        warn!("⚠️  RESETTING SQLite DATABASE - ALL DATA WILL BE LOST!");

        // Get all table names
        let rows = sqlx::query(
            "SELECT name FROM sqlite_master WHERE type='table' AND name != 'sqlite_sequence';",
        )
        .fetch_all(pool)
        .await?;

        // Drop all tables
        for row in rows {
            let table_name: String = row.get("name");
            sqlx::query(&format!("DROP TABLE IF EXISTS {};", table_name))
                .execute(pool)
                .await?;
        }

        // Run all migrations fresh
        self.run_sqlite_migrations(pool).await?;

        info!("✅ SQLite database reset and migrations applied");
        Ok(())
    }

    /// Get migration status for PostgreSQL
    pub async fn get_postgres_migration_status(
        &self,
        pool: &PgPool,
    ) -> Result<Vec<MigrationStatus>> {
        self.create_postgres_migrations_table(pool).await?;
        let applied_migrations = self.get_applied_migrations_postgres(pool).await?;

        let mut status = Vec::new();
        for migration in &self.migrations {
            status.push(MigrationStatus {
                version: migration.version,
                name: migration.name.clone(),
                applied: applied_migrations.contains(&migration.version),
            });
        }
        Ok(status)
    }

    /// Get migration status for SQLite
    pub async fn get_sqlite_migration_status(
        &self,
        pool: &SqlitePool,
    ) -> Result<Vec<MigrationStatus>> {
        self.create_sqlite_migrations_table(pool).await?;
        let applied_migrations = self.get_applied_migrations_sqlite(pool).await?;

        let mut status = Vec::new();
        for migration in &self.migrations {
            status.push(MigrationStatus {
                version: migration.version,
                name: migration.name.clone(),
                applied: applied_migrations.contains(&migration.version),
            });
        }
        Ok(status)
    }
}

impl Default for MigrationRunner {
    fn default() -> Self {
        Self::new()
    }
}

/// Status of a single migration
#[derive(Debug, Clone)]
pub struct MigrationStatus {
    #[allow(dead_code)]
    pub version: i32,
    #[allow(dead_code)]
    pub name: String,
    #[allow(dead_code)]
    pub applied: bool,
}

/// Convenience function to run migrations based on database URL
#[allow(dead_code)]
pub async fn run_migrations(database_url: &str, _pool: &sqlx::Pool<sqlx::Any>) -> Result<()> {
    let _runner = MigrationRunner::new();

    if database_url.starts_with("postgres://") || database_url.starts_with("postgresql://") {
        // For PostgreSQL, we need to cast the Any pool to PgPool
        // This is a limitation of the current approach - we'll handle this in the main function
        return Err(anyhow::anyhow!(
            "Use run_postgres_migrations with PgPool directly"
        ));
    } else if database_url.starts_with("sqlite:") {
        // For SQLite, we need to cast the Any pool to SqlitePool
        // This is a limitation of the current approach - we'll handle this in the main function
        return Err(anyhow::anyhow!(
            "Use run_sqlite_migrations with SqlitePool directly"
        ));
    } else {
        return Err(anyhow::anyhow!("Unsupported database URL format"));
    }
}

/// Utility function to check if migrations are needed
#[allow(dead_code)]
pub async fn migrations_needed(database_url: &str) -> Result<bool> {
    let runner = MigrationRunner::new();

    if database_url.starts_with("postgres://") || database_url.starts_with("postgresql://") {
        let pool = PgPool::connect(database_url).await?;
        let up_to_date = runner.check_postgres_schema(&pool).await?;
        pool.close().await;
        Ok(!up_to_date)
    } else if database_url.starts_with("sqlite:") {
        let pool = SqlitePool::connect(database_url).await?;
        let up_to_date = runner.check_sqlite_schema(&pool).await?;
        pool.close().await;
        Ok(!up_to_date)
    } else {
        Err(anyhow::anyhow!("Unsupported database URL format"))
    }
}

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

    #[tokio::test]
    async fn test_sqlite_migrations() {
        let temp_dir = tempdir().expect("Failed to create temp directory");
        let db_path = temp_dir.path().join("test.db");
        let database_url = format!("sqlite://{}?mode=rwc", db_path.display());

        let pool = SqlitePool::connect(&database_url)
            .await
            .expect("Failed to connect to SQLite database");
        let runner = MigrationRunner::new();

        // Initially no migrations should be applied
        let status = runner
            .get_sqlite_migration_status(&pool)
            .await
            .expect("Failed to get migration status");
        assert!(status.iter().all(|s| !s.applied));

        // Run migrations
        runner
            .run_sqlite_migrations(&pool)
            .await
            .expect("Failed to run migrations");

        // Now all migrations should be applied
        let status = runner
            .get_sqlite_migration_status(&pool)
            .await
            .expect("Failed to get migration status after running");
        assert!(status.iter().all(|s| s.applied));

        // Running again should be idempotent
        runner
            .run_sqlite_migrations(&pool)
            .await
            .expect("Failed to run migrations idempotently");
        let status = runner
            .get_sqlite_migration_status(&pool)
            .await
            .expect("Failed to get migration status for idempotency check");
        assert!(status.iter().all(|s| s.applied));

        pool.close().await;
    }

    #[tokio::test]
    async fn test_migration_runner_creation() {
        let runner = MigrationRunner::new();
        assert!(!runner.migrations.is_empty());

        // Verify migrations are sorted by version
        let versions: Vec<i32> = runner.migrations.iter().map(|m| m.version).collect();
        let mut sorted_versions = versions.clone();
        sorted_versions.sort();
        assert_eq!(versions, sorted_versions);
    }
}
