//! Verification tests mapped to SRS-089's acceptance criteria:
//!   "Storage implementation uses an embedded local data store and
//!    migration mechanism."
//!
//! And to the PoC plan §2 exit criterion:
//!   "Schema, migration, and repository pattern are demonstrated."

use rusqlite::Connection;
use sqlite_storage_spike::{
    migrate::{canonical_migrations, Migration, Migrator},
    repo::NewBookmark,
    BookmarkRepository, LocalDatabaseRepository, SettingsRepository,
};

// ---------- Migration mechanism ----------

#[test]
fn migrator_brings_fresh_db_to_latest_version() {
    let mut conn = Connection::open_in_memory().unwrap();
    let migrator = Migrator::new(canonical_migrations()).unwrap();

    let final_v = migrator.run(&mut conn).unwrap();
    assert_eq!(final_v, migrator.highest_version());
}

#[test]
fn migrator_is_idempotent() {
    let mut conn = Connection::open_in_memory().unwrap();
    let migrator = Migrator::new(canonical_migrations()).unwrap();
    let v1 = migrator.run(&mut conn).unwrap();
    let v2 = migrator.run(&mut conn).unwrap();
    let v3 = migrator.run(&mut conn).unwrap();
    assert_eq!(v1, v2);
    assert_eq!(v2, v3);
}

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

    // Apply only v1 first.
    let first = Migrator::new(vec![canonical_migrations()[0]]).unwrap();
    assert_eq!(first.run(&mut conn).unwrap(), 1);

    // Now apply the full list — must catch up from v1 to v3 cleanly.
    let full = Migrator::new(canonical_migrations()).unwrap();
    assert_eq!(full.run(&mut conn).unwrap(), 3);

    // Schema reflects v2 + v3 column additions.
    let cols: Vec<String> = conn
        .prepare("SELECT name FROM pragma_table_info('bookmarks')")
        .unwrap()
        .query_map([], |r| r.get::<_, String>(0))
        .unwrap()
        .collect::<Result<_, _>>()
        .unwrap();
    assert!(cols.contains(&"nickname".to_string()));
    assert!(cols.contains(&"last_connected_at".to_string()));
}

#[test]
fn migrator_rejects_out_of_order_versions() {
    let bogus = vec![
        Migration { version: 2, name: "a", sql: "" },
        Migration { version: 1, name: "b", sql: "" },
    ];
    assert!(Migrator::new(bogus).is_err());
}

#[test]
fn migrator_rejects_db_newer_than_known_migrations() {
    let mut conn = Connection::open_in_memory().unwrap();
    conn.execute_batch("PRAGMA user_version = 99").unwrap();
    let migrator = Migrator::new(canonical_migrations()).unwrap();
    assert!(migrator.run(&mut conn).is_err());
}

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

    // v1 ok; v2 deliberately broken.
    let broken = vec![
        canonical_migrations()[0],
        Migration {
            version: 2,
            name: "broken",
            sql: "ALTER TABLE not_a_table ADD COLUMN x TEXT;",
        },
    ];
    let m = Migrator::new(broken).unwrap();
    assert!(m.run(&mut conn).is_err());

    // We should still be on v1, not partially upgraded.
    let v: i64 = conn
        .query_row("PRAGMA user_version", [], |r| r.get(0))
        .unwrap();
    assert_eq!(v, 1);
}

// ---------- Repository pattern ----------

#[test]
fn bookmark_repo_crud_round_trip() {
    let repo = LocalDatabaseRepository::open_in_memory().unwrap();
    let id = BookmarkRepository::insert(
        &repo,
        NewBookmark {
            server_name: "Vigorous",
            server_host: "cn.teamspeak.app",
            identity_ref: "identity.primary",
            nickname: Some("ChanoraPoC"),
        },
    )
    .unwrap();

    let got = BookmarkRepository::get(&repo, id).unwrap();
    assert_eq!(got.server_name, "Vigorous");
    assert_eq!(got.server_host, "cn.teamspeak.app");
    assert_eq!(got.identity_ref, "identity.primary");
    assert_eq!(got.nickname.as_deref(), Some("ChanoraPoC"));
    assert!(got.last_connected_at.is_none());

    BookmarkRepository::touch_connected(&repo, id, 1_700_000_000).unwrap();
    let again = BookmarkRepository::get(&repo, id).unwrap();
    assert_eq!(again.last_connected_at, Some(1_700_000_000));

    BookmarkRepository::delete(&repo, id).unwrap();
    assert!(matches!(
        BookmarkRepository::get(&repo, id),
        Err(sqlite_storage_spike::RepoError::NotFound)
    ));
}

#[test]
fn bookmark_repo_list_orders_recent_first() {
    let repo = LocalDatabaseRepository::open_in_memory().unwrap();
    let a = BookmarkRepository::insert(
        &repo,
        NewBookmark { server_name: "A", server_host: "a.example", identity_ref: "i.a", nickname: None },
    )
    .unwrap();
    let b = BookmarkRepository::insert(
        &repo,
        NewBookmark { server_name: "B", server_host: "b.example", identity_ref: "i.b", nickname: None },
    )
    .unwrap();
    let _c = BookmarkRepository::insert(
        &repo,
        NewBookmark { server_name: "C", server_host: "c.example", identity_ref: "i.c", nickname: None },
    )
    .unwrap();
    BookmarkRepository::touch_connected(&repo, b, 200).unwrap();
    BookmarkRepository::touch_connected(&repo, a, 100).unwrap();
    // c never connected.

    let list = BookmarkRepository::list_ordered(&repo).unwrap();
    let names: Vec<_> = list.iter().map(|x| x.server_name.as_str()).collect();
    // b (200), a (100), then c (NULL last) — c sorts after by name fallback.
    assert_eq!(names, vec!["B", "A", "C"]);
}

#[test]
fn bookmark_repo_unique_host_identity_is_enforced() {
    let repo = LocalDatabaseRepository::open_in_memory().unwrap();
    BookmarkRepository::insert(
        &repo,
        NewBookmark {
            server_name: "Vigorous",
            server_host: "cn.teamspeak.app",
            identity_ref: "identity.primary",
            nickname: None,
        },
    )
    .unwrap();
    let dup = BookmarkRepository::insert(
        &repo,
        NewBookmark {
            server_name: "Vigorous Again",
            server_host: "cn.teamspeak.app",
            identity_ref: "identity.primary",
            nickname: None,
        },
    );
    assert!(dup.is_err(), "expected UNIQUE violation");
}

#[test]
fn settings_repo_upsert_and_delete() {
    let repo = LocalDatabaseRepository::open_in_memory().unwrap();
    SettingsRepository::put(&repo, "audio.aec", "true").unwrap();
    assert_eq!(
        SettingsRepository::get(&repo, "audio.aec").unwrap().as_deref(),
        Some("true")
    );

    // Upsert (same key, new value).
    SettingsRepository::put(&repo, "audio.aec", "false").unwrap();
    assert_eq!(
        SettingsRepository::get(&repo, "audio.aec").unwrap().as_deref(),
        Some("false")
    );

    assert!(SettingsRepository::delete(&repo, "audio.aec").unwrap());
    assert!(SettingsRepository::get(&repo, "audio.aec").unwrap().is_none());
    assert!(!SettingsRepository::delete(&repo, "audio.aec").unwrap());
}

// ---------- End-to-end on disk ----------

#[test]
fn open_creates_file_and_persists_across_reopen() {
    let tmp = tempfile::tempdir().unwrap();
    let path = tmp.path().join("chanora.sqlite");

    {
        let repo = LocalDatabaseRepository::open(&path).unwrap();
        assert_eq!(repo.schema_version().unwrap(), 3);
        BookmarkRepository::insert(
            &repo,
            NewBookmark {
                server_name: "Persist",
                server_host: "persist.example",
                identity_ref: "i.persist",
                nickname: None,
            },
        )
        .unwrap();
    }

    // Reopen and verify durability + migrator no-ops.
    let repo2 = LocalDatabaseRepository::open(&path).unwrap();
    assert_eq!(repo2.schema_version().unwrap(), 3);
    let list = BookmarkRepository::list_ordered(&repo2).unwrap();
    assert_eq!(list.len(), 1);
    assert_eq!(list[0].server_name, "Persist");
}
