//! Database migration system
//!
//! Provides versioned schema migrations for SQLite database evolution.

use rusqlite::Connection;

use crate::persistence::error::Result;

/// Migration metadata
#[derive(Debug, Clone)]
pub struct Migration {
    /// Migration version number
    pub version: i64,
    /// Migration name/description
    pub name: &'static str,
    /// SQL statements to apply
    pub up: &'static str,
}

/// All available migrations in order
pub const MIGRATIONS: &[Migration] = &[
    Migration {
        version: 1,
        name: "initial_schema",
        up: include_str!("migrations/001_initial_schema.sql"),
    },
    Migration {
        version: 4,
        name: "sessions",
        up: include_str!("migrations/004_sessions.sql"),
    },
];

/// Initialize the migrations table
fn create_migrations_table(conn: &Connection) -> Result<()> {
    conn.execute(
        "CREATE TABLE IF NOT EXISTS schema_migrations (
            version INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            applied_at INTEGER NOT NULL
        )",
        [],
    )?;
    Ok(())
}

/// Get the current schema version
pub fn get_current_version(conn: &Connection) -> Result<i64> {
    create_migrations_table(conn)?;

    let version = conn
        .query_row(
            "SELECT COALESCE(MAX(version), 0) FROM schema_migrations",
            [],
            |row| row.get(0),
        )
        .unwrap_or(0);

    Ok(version)
}

/// Check if a migration has been applied
fn is_migration_applied(conn: &Connection, version: i64) -> Result<bool> {
    let count: i64 = conn.query_row(
        "SELECT COUNT(*) FROM schema_migrations WHERE version = ?1",
        [version],
        |row| row.get(0),
    )?;
    Ok(count > 0)
}

/// Apply a single migration
fn apply_migration(conn: &mut Connection, migration: &Migration) -> Result<()> {
    tracing::info!(
        "Applying migration {} ({})",
        migration.version,
        migration.name
    );

    let tx = conn.transaction()?;

    // Execute the migration SQL
    tx.execute_batch(migration.up)?;

    // Record that we applied this migration
    tx.execute(
        "INSERT INTO schema_migrations (version, name, applied_at)
         VALUES (?1, ?2, ?3)",
        rusqlite::params![
            migration.version,
            migration.name,
            chrono::Utc::now().timestamp(),
        ],
    )?;

    tx.commit()?;

    tracing::info!(
        "Migration {} ({}) applied successfully",
        migration.version,
        migration.name
    );

    Ok(())
}

/// Run all pending migrations
pub fn run_migrations(conn: &mut Connection) -> Result<()> {
    create_migrations_table(conn)?;

    let current_version = get_current_version(conn)?;
    tracing::info!("Current schema version: {}", current_version);

    let mut applied_count = 0;

    for migration in MIGRATIONS {
        if !is_migration_applied(conn, migration.version)? {
            apply_migration(conn, migration)?;
            applied_count += 1;
        }
    }

    if applied_count > 0 {
        tracing::info!("Applied {} migration(s)", applied_count);
    } else {
        tracing::debug!("No pending migrations");
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use rusqlite::Connection;

    use super::*;

    #[test]
    fn test_migration_system() {
        let mut conn = Connection::open_in_memory().unwrap();

        // Initially at version 0
        assert_eq!(get_current_version(&conn).unwrap(), 0);

        // Run migrations
        run_migrations(&mut conn).unwrap();

        // Should be at latest version
        let latest_version = MIGRATIONS.last().unwrap().version;
        assert_eq!(get_current_version(&conn).unwrap(), latest_version);

        // Running again should be a no-op
        run_migrations(&mut conn).unwrap();
        assert_eq!(get_current_version(&conn).unwrap(), latest_version);
    }

    #[test]
    fn test_migrations_table_created() {
        let conn = Connection::open_in_memory().unwrap();
        create_migrations_table(&conn).unwrap();

        // Should be able to query the table
        let count: i64 = conn
            .query_row("SELECT COUNT(*) FROM schema_migrations", [], |row| {
                row.get(0)
            })
            .unwrap();
        assert_eq!(count, 0);
    }

    #[test]
    fn test_is_migration_applied() {
        let conn = Connection::open_in_memory().unwrap();
        create_migrations_table(&conn).unwrap();

        // Migration 1 should not be applied yet
        assert!(!is_migration_applied(&conn, 1).unwrap());

        // Apply migration 1
        conn.execute(
            "INSERT INTO schema_migrations (version, name, applied_at) VALUES (1, 'test', 0)",
            [],
        )
        .unwrap();

        // Now it should be applied
        assert!(is_migration_applied(&conn, 1).unwrap());
    }
}
