//! Full-stack test for **ADR-0005 Amendment 2** (drop `blob`): a saved
//! v1 project that declares a `blob` column is migrated to v2 (`blob` →
//! `text`) on open, and a rebuild from the migrated text reconstructs the
//! column as `text` with the row data preserved — the base64 cell survives
//! as a recoverable string. This exercises the real migrator + the
//! `rebuild_from_text` path the runtime forces when a blob column was
//! converted (the runtime loop itself isn't booted in integration tests).

use std::fs;

use rdbms_playground::db::Database;
use rdbms_playground::dsl::Type;
use rdbms_playground::persistence::Persistence;
use rdbms_playground::persistence::migrations::{
    MigratorRegistry, body_declares_blob_column, ensure_project_yaml_migrated,
};

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

#[test]
fn v1_blob_project_migrates_to_text_and_rebuilds() {
    let dir = tempfile::tempdir().expect("tempdir");
    let root = dir.path();

    // A hand-written v1 project: a serial PK + a `blob` column carrying a
    // (hand-edited, base64) value — the only way bytes ever reached a blob
    // column, since no command can insert one.
    let yaml = concat!(
        "version: 1\n",
        "project:\n  created_at: 2026-01-01T00:00:00Z\n  mode: simple\n",
        "tables:\n",
        "  - name: Files\n",
        "    primary_key: [id]\n",
        "    columns:\n",
        "      - { name: id, type: serial }\n",
        "      - { name: data, type: blob }\n",
        "relationships: []\n",
        "indexes: []\n",
    );
    fs::write(root.join("project.yaml"), yaml).expect("write yaml");
    fs::create_dir(root.join("data")).expect("data dir");
    fs::write(root.join("data").join("Files.csv"), "id,data\n1,aGVsbG8=\n").expect("write csv");

    // The runtime's blob-detection signal sees the column…
    assert!(body_declares_blob_column(yaml), "blob column detected");

    // 1. Migration runs (as the runtime does on open).
    let outcome = ensure_project_yaml_migrated(root, &MigratorRegistry::production())
        .expect("migration succeeds");
    assert_eq!(outcome.migrated_from, Some(1), "v1 project was migrated");

    let migrated = fs::read_to_string(root.join("project.yaml")).expect("read migrated");
    assert!(
        migrated.contains("version: 2"),
        "version bumped: {migrated}"
    );
    assert!(
        migrated.contains("{ name: data, type: text }"),
        "blob column rewritten to text: {migrated}"
    );
    assert!(
        !migrated.contains("type: blob"),
        "no blob type remains: {migrated}"
    );
    // The pre-migration original is preserved as a .bak.
    let bak = fs::read_to_string(root.join("project.yaml.v1.bak")).expect("read bak");
    assert!(
        bak.contains("type: blob"),
        "bak keeps the original blob column"
    );

    // 2. Rebuild from the migrated text (as the runtime forces when a blob
    //    column was converted): the column is now `text` and the row data
    //    survives as a recoverable string.
    let db = Database::open_with_persistence(
        root.join("playground.db"),
        Persistence::new(root.to_path_buf()),
    )
    .expect("open db");
    rt().block_on(async {
        db.rebuild_from_text(root.to_path_buf(), None)
            .await
            .expect("rebuild");

        let desc = db
            .describe_table("Files".to_string())
            .await
            .expect("describe");
        let data_col = desc
            .columns
            .iter()
            .find(|c| c.name == "data")
            .expect("data column present");
        assert_eq!(
            data_col.user_type,
            Some(Type::Text),
            "the former blob column is now text",
        );

        let rows = db
            .query_data("Files".to_string(), None, None)
            .await
            .expect("query");
        assert_eq!(rows.rows.len(), 1, "row preserved through the migration");
        assert_eq!(
            rows.rows[0][1].as_deref(),
            Some("aGVsbG8="),
            "the base64 cell survives as recoverable text",
        );
    });
}
