//! Sub-phase 4d integration tests for advanced-mode SQL
//! `DROP INDEX [IF EXISTS]` (ADR-0035 §4d).
//!
//! `SqlDropIndex` executes through the same `do_drop_index` machinery as
//! the simple `drop index <name>`; the only new behaviour is `IF EXISTS`
//! as a no-op-with-note (`DropIndexOutcome::Skipped`). These drive the
//! worker directly; parsing (text → `Command::SqlDropIndex`) is covered
//! by the `sql_drop_index_tests` in `src/dsl/grammar/ddl.rs`.

use rdbms_playground::db::{Database, DropIndexOutcome};
use rdbms_playground::dsl::{ColumnSpec, Type};
use rdbms_playground::persistence::Persistence;
use rdbms_playground::project;

fn rt() -> tokio::runtime::Runtime {
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .expect("tokio rt")
}

fn open(undo: bool) -> (project::Project, Database, tempfile::TempDir) {
    let dir = tempfile::tempdir().expect("create tempdir");
    let project = project::open_or_create(None, Some(dir.path())).expect("open or create project");
    let persistence = Persistence::new(project.path().to_path_buf());
    let db = Database::open_with_persistence_and_undo(project.db_path(), persistence, undo)
        .expect("open db with persistence");
    (project, db, dir)
}

/// Create `T (id int primary key, email text)` and an index on `email`.
fn make_t_with_index(db: &Database, r: &tokio::runtime::Runtime) -> String {
    r.block_on(db.sql_create_table(
        "T".to_string(),
        vec![
            ColumnSpec::new("id", Type::Int),
            ColumnSpec::new("email", Type::Text),
        ],
        vec!["id".to_string()],
        vec![],
        vec![],
        vec![],
        false,
        Some("create table T (id int primary key, email text)".to_string()),
    ))
    .expect("create T");
    let desc = r
        .block_on(db.add_index(
            Some("T_email_idx".to_string()),
            "T".to_string(),
            vec!["email".to_string()],
            Some("add index as T_email_idx on T (email)".to_string()),
        ))
        .expect("add index");
    assert_eq!(desc.indexes.len(), 1, "index created");
    "T_email_idx".to_string()
}

fn index_names(db: &Database, r: &tokio::runtime::Runtime) -> Vec<String> {
    r.block_on(db.describe_table("T".to_string()))
        .expect("describe")
        .indexes
        .into_iter()
        .map(|i| i.name)
        .collect()
}

#[test]
fn drop_index_removes_an_existing_index_and_shows_the_table() {
    let (_p, db, _d) = open(false);
    let r = rt();
    let name = make_t_with_index(&db, &r);
    let out = r
        .block_on(db.sql_drop_index(name, false, Some("drop index T_email_idx".to_string())))
        .expect("drop index");
    // Dropped carries the de-indexed table's structure (auto-show).
    match out {
        DropIndexOutcome::Dropped(desc) => {
            assert_eq!(desc.name, "T");
            assert!(
                desc.indexes.is_empty(),
                "the index is gone from the structure"
            );
        }
        DropIndexOutcome::Skipped => panic!("expected Dropped, got Skipped"),
    }
    assert!(index_names(&db, &r).is_empty());
}

#[test]
fn if_exists_on_an_absent_index_is_a_noop_and_journalled() {
    let (_p, db, _d) = open(false);
    let r = rt();
    let line = "drop index if exists ghost_idx";
    let out = r
        .block_on(db.sql_drop_index("ghost_idx".to_string(), true, Some(line.to_string())))
        .expect("IF EXISTS on an absent index succeeds as a no-op");
    assert!(matches!(out, DropIndexOutcome::Skipped));
    // The no-op is still journalled (ADR-0034), like the create-skip.
    // ADR-0052: journaling moved to the dispatch layer; this test now
    // asserts only the no-op `Skipped` outcome.
}

#[test]
fn plain_drop_of_an_absent_index_errors() {
    let (_p, db, _d) = open(false);
    let r = rt();
    let res = r.block_on(db.sql_drop_index(
        "ghost_idx".to_string(),
        false,
        Some("drop index ghost_idx".to_string()),
    ));
    assert!(
        res.is_err(),
        "plain DROP INDEX on an absent index errors (no IF EXISTS)"
    );
}

#[test]
fn drop_index_is_one_undo_step_and_restores_the_index() {
    let (_p, db, _d) = open(true); // undo enabled
    let r = rt();
    let name = make_t_with_index(&db, &r);
    r.block_on(db.sql_drop_index(
        name.clone(),
        false,
        Some("drop index T_email_idx".to_string()),
    ))
    .expect("drop index");
    assert!(index_names(&db, &r).is_empty());

    // One undo brings the index back.
    assert!(
        r.block_on(db.undo()).expect("undo").is_some(),
        "the drop was one undo step"
    );
    assert_eq!(index_names(&db, &r), vec![name], "undo restored the index");
}
