package db_test

// migrate_test.go — TDD tests for the tracked migration system.
//
// RED phase: these tests are written before the implementation exists.
// They verify the contract of MigrateFS after it gains schema_versions tracking.
//
// Test matrix:
//   TestMigrate_SchemaVersionsTableCreated      — schema_versions exists after first run
//   TestMigrate_AllMigrationsRecorded           — every applied file is recorded
//   TestMigrate_SkipsAlreadyApplied             — second call skips files already in schema_versions
//   TestMigrate_AppliesNewMigrationsOnly        — only new files are applied on subsequent runs
//   TestMigrate_OrderIsLexicographic            — migrations execute in sorted filename order
//   TestMigrate_SeedExistingDatabase            — existing DB (no schema_versions) is seeded
//   TestMigrate_SeedDoesNotReRunMigrations      — seeded migrations are not re-executed
//   TestMigrate_SchemaVersionsAppliedAtRecorded — applied_at column is populated
//   TestMigrate_EmptyFSSucceeds                 — empty FS is fine, no error
//   TestMigrate_InvalidSQLReturnsError          — bad SQL still surfaces as an error
//   TestMigrate_ReadFileErrorReturnsError       — FS read failure surfaces as an error
//   TestMigrate_PartialRunRecordsOnlyApplied    — failure mid-run leaves earlier files recorded
//   TestMigrate_AppliedAtIsISO8601              — applied_at timestamp format is valid

import (
	"context"
	"database/sql"
	"fmt"
	"io/fs"
	"strings"
	"testing"
	"testing/fstest"
	"time"

	"github.com/owncord/server/db"
)

// failReadDirFS is an fs.FS whose root Open succeeds but ReadDir always errors.
// This exercises the sqlFilenames ReadDir error path.
type failReadDirFS struct{}

func (failReadDirFS) Open(name string) (fs.File, error) {
	if name == "." {
		return &badDirFile{}, nil
	}
	return nil, fmt.Errorf("no files")
}

type badDirFile struct{}

func (badDirFile) Read([]byte) (int, error)   { return 0, fmt.Errorf("not a file") }
func (badDirFile) Close() error               { return nil }
func (badDirFile) Stat() (fs.FileInfo, error) { return fakeDirInfo{}, nil }
func (badDirFile) ReadDir(int) ([]fs.DirEntry, error) {
	return nil, fmt.Errorf("readdir always fails")
}

// ---- helpers ----------------------------------------------------------------

// countVersions returns the number of rows in schema_versions.
func countVersions(t *testing.T, database *db.DB) int {
	t.Helper()
	var n int
	err := database.QueryRowContext(context.Background(), "SELECT COUNT(*) FROM schema_versions").Scan(&n)
	if err != nil {
		t.Fatalf("counting schema_versions: %v", err)
	}
	return n
}

// hasVersion reports whether a specific filename is recorded in schema_versions.
func hasVersion(t *testing.T, database *db.DB, filename string) bool {
	t.Helper()
	var v string
	err := database.QueryRowContext(context.Background(),
		"SELECT version FROM schema_versions WHERE version = ?", filename,
	).Scan(&v)
	if err == sql.ErrNoRows {
		return false
	}
	if err != nil {
		t.Fatalf("querying schema_versions for %q: %v", filename, err)
	}
	return true
}

// tableExists reports whether a table (or virtual table) exists in sqlite_master.
func tableExists(t *testing.T, database *db.DB, name string) bool {
	t.Helper()
	var n string
	err := database.QueryRowContext(context.Background(),
		"SELECT name FROM sqlite_master WHERE type='table' AND name=?", name,
	).Scan(&n)
	if err == sql.ErrNoRows {
		return false
	}
	if err != nil {
		t.Fatalf("checking table %q: %v", name, err)
	}
	return true
}

// simpleFS builds an fstest.MapFS with the provided filename→SQL pairs.
func simpleFS(pairs ...string) fstest.MapFS {
	if len(pairs)%2 != 0 {
		panic("simpleFS requires an even number of arguments (name, sql, ...)")
	}
	m := fstest.MapFS{}
	for i := 0; i < len(pairs); i += 2 {
		m[pairs[i]] = &fstest.MapFile{Data: []byte(pairs[i+1])}
	}
	return m
}

// ---- tests ------------------------------------------------------------------

// TestMigrate_SchemaVersionsTableCreated verifies that MigrateFS creates the
// schema_versions tracking table on first run.
func TestMigrate_SchemaVersionsTableCreated(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"001_create_foo.sql", "CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	if !tableExists(t, database, "schema_versions") {
		t.Error("schema_versions table was not created by MigrateFS")
	}
}

// TestMigrate_AllMigrationsRecorded verifies that every applied .sql file
// gets a row inserted into schema_versions.
func TestMigrate_AllMigrationsRecorded(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"001_alpha.sql", "CREATE TABLE IF NOT EXISTS alpha (id INTEGER PRIMARY KEY);",
		"002_beta.sql", "CREATE TABLE IF NOT EXISTS beta  (id INTEGER PRIMARY KEY);",
		"003_gamma.sql", "CREATE TABLE IF NOT EXISTS gamma (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	for _, name := range []string{"001_alpha.sql", "002_beta.sql", "003_gamma.sql"} {
		if !hasVersion(t, database, name) {
			t.Errorf("migration %q not recorded in schema_versions", name)
		}
	}
}

// TestMigrate_SkipsAlreadyApplied verifies that a second call to MigrateFS
// with the same FS does not re-execute already-applied migrations.
func TestMigrate_SkipsAlreadyApplied(t *testing.T) {
	database := openMemory(t)

	// This migration inserts a row; if re-run it would violate UNIQUE.
	fsys := simpleFS(
		"001_unique.sql", `
			CREATE TABLE IF NOT EXISTS unique_check (val TEXT UNIQUE);
			INSERT INTO unique_check (val) VALUES ('singleton');
		`,
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() first run error: %v", err)
	}

	// Second run — must not fail even though the INSERT would conflict if re-run.
	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() second run error (migration was re-executed): %v", err)
	}

	// Confirm the row exists exactly once.
	var count int
	if err := database.QueryRowContext(context.Background(), "SELECT COUNT(*) FROM unique_check WHERE val='singleton'").Scan(&count); err != nil {
		t.Fatalf("counting unique_check: %v", err)
	}
	if count != 1 {
		t.Errorf("unique_check has %d rows, want exactly 1 — migration was re-run", count)
	}
}

// TestMigrate_AppliesNewMigrationsOnly verifies that when a new file is added
// to the FS, only that file is applied on the second call.
func TestMigrate_AppliesNewMigrationsOnly(t *testing.T) {
	database := openMemory(t)

	fsFirst := simpleFS(
		"001_base.sql", "CREATE TABLE IF NOT EXISTS base_tbl (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsFirst); err != nil {
		t.Fatalf("MigrateFS() first run error: %v", err)
	}

	versionsAfterFirst := countVersions(t, database)

	// Add a second migration.
	fsSecond := simpleFS(
		"001_base.sql", "CREATE TABLE IF NOT EXISTS base_tbl (id INTEGER PRIMARY KEY);",
		"002_extra.sql", "CREATE TABLE IF NOT EXISTS extra_tbl (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsSecond); err != nil {
		t.Fatalf("MigrateFS() second run error: %v", err)
	}

	versionsAfterSecond := countVersions(t, database)

	if versionsAfterSecond != versionsAfterFirst+1 {
		t.Errorf(
			"expected %d version rows after second run, got %d",
			versionsAfterFirst+1, versionsAfterSecond,
		)
	}

	if !hasVersion(t, database, "002_extra.sql") {
		t.Error("002_extra.sql not recorded after second run")
	}

	if !tableExists(t, database, "extra_tbl") {
		t.Error("extra_tbl not created by second run")
	}
}

// TestMigrate_OrderIsLexicographic verifies that migrations are applied in
// sorted filename order, not insertion or readdir order.
func TestMigrate_OrderIsLexicographic(t *testing.T) {
	database := openMemory(t)

	// 002 creates the table; 001 tries to insert into it.
	// If run out of order (002 before 001) the INSERT would fail with "no such table".
	// With lexicographic ordering 001 runs first and creates the table,
	// then 002 inserts into it — so we verify the correct order by checking
	// the table was created before the insert was attempted.
	fsys := simpleFS(
		"002_insert.sql", "INSERT INTO order_check (label) VALUES ('second');",
		"001_create.sql", "CREATE TABLE IF NOT EXISTS order_check (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	var label string
	if err := database.QueryRowContext(context.Background(), "SELECT label FROM order_check LIMIT 1").Scan(&label); err != nil {
		t.Fatalf("selecting from order_check: %v", err)
	}
	if label != "second" {
		t.Errorf("label = %q, want 'second'", label)
	}
}

// TestMigrate_SeedExistingDatabase verifies that when schema_versions does not
// exist but other known tables do (simulating an existing DB from before
// tracking was added), all current migration filenames are seeded so they are
// not re-executed.
func TestMigrate_SeedExistingDatabase(t *testing.T) {
	database := openMemory(t)

	// Manually create a table to simulate a previously-migrated database
	// that does not yet have schema_versions.
	if _, err := database.ExecContext(context.Background(),
		"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY);",
	); err != nil {
		t.Fatalf("setup: creating users table: %v", err)
	}

	// This migration would drop and recreate users; if it runs it will wipe data.
	// The seeding logic must prevent it from running.
	fsys := simpleFS(
		"001_initial.sql", `
			DROP TABLE IF EXISTS users;
			CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT);
		`,
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	// The migration must be recorded (seeded).
	if !hasVersion(t, database, "001_initial.sql") {
		t.Error("001_initial.sql should be seeded into schema_versions for existing DB")
	}

	// The users table must still have its original schema (no 'name' column),
	// proving the DROP/CREATE did not run.
	_, err := database.ExecContext(context.Background(), "INSERT INTO users (id) VALUES (42)")
	if err != nil {
		t.Errorf("users table appears to have been recreated (DROP ran): %v", err)
	}
}

// TestMigrate_SeedDoesNotReRunMigrations is a companion to the seeding test:
// after seeding, a subsequent MigrateFS call with the same FS must be a no-op.
// The seeding heuristic triggers on the presence of the "users" sentinel table,
// so we create that table to simulate a pre-tracking database.
func TestMigrate_SeedDoesNotReRunMigrations(t *testing.T) {
	database := openMemory(t)

	// Simulate an existing DB: create the "users" sentinel table so the seeding
	// heuristic fires, plus the table that the migration would modify.
	if _, err := database.ExecContext(context.Background(),
		"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY);",
	); err != nil {
		t.Fatalf("setup users: %v", err)
	}
	if _, err := database.ExecContext(context.Background(),
		"CREATE TABLE IF NOT EXISTS existing (id INTEGER PRIMARY KEY);",
	); err != nil {
		t.Fatalf("setup existing: %v", err)
	}

	// This migration would INSERT into existing; if it runs, count becomes 1.
	fsys := simpleFS(
		"001_existing.sql", `
			CREATE TABLE IF NOT EXISTS existing (id INTEGER PRIMARY KEY);
			INSERT INTO existing (id) VALUES (1);
		`,
	)

	// First call — seeds because schema_versions is absent AND users table exists.
	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() first run error: %v", err)
	}

	// Second call — must be a no-op (migration is already recorded).
	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() second run error: %v", err)
	}

	// existing table should be empty — the INSERT was never executed (seeded only).
	var count int
	if err := database.QueryRowContext(context.Background(), "SELECT COUNT(*) FROM existing").Scan(&count); err != nil {
		t.Fatalf("counting existing: %v", err)
	}
	if count != 0 {
		t.Errorf("existing has %d rows, want 0 — seeded migration was re-executed", count)
	}
}

// TestMigrate_SchemaVersionsAppliedAtRecorded verifies that applied_at is
// populated for every recorded migration.
func TestMigrate_SchemaVersionsAppliedAtRecorded(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"001_ts.sql", "CREATE TABLE IF NOT EXISTS ts_test (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	var appliedAt string
	err := database.QueryRowContext(context.Background(),
		"SELECT applied_at FROM schema_versions WHERE version = '001_ts.sql'",
	).Scan(&appliedAt)
	if err != nil {
		t.Fatalf("querying applied_at: %v", err)
	}
	if appliedAt == "" {
		t.Error("applied_at should not be empty")
	}
}

// TestMigrate_AppliedAtIsISO8601 verifies applied_at is a parseable datetime.
func TestMigrate_AppliedAtIsISO8601(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"001_dt.sql", "CREATE TABLE IF NOT EXISTS dt_test (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	var appliedAt string
	if err := database.QueryRowContext(context.Background(),
		"SELECT applied_at FROM schema_versions WHERE version = '001_dt.sql'",
	).Scan(&appliedAt); err != nil {
		t.Fatalf("querying applied_at: %v", err)
	}

	// SQLite datetime('now') produces "YYYY-MM-DD HH:MM:SS".
	formats := []string{
		"2006-01-02 15:04:05",
		time.RFC3339,
	}
	var parsed bool
	for _, f := range formats {
		if _, err := time.Parse(f, appliedAt); err == nil {
			parsed = true
			break
		}
	}
	if !parsed {
		t.Errorf("applied_at %q is not a recognised datetime format", appliedAt)
	}
}

// TestMigrate_EmptyFSSucceeds verifies that an empty FS returns no error and
// still creates the schema_versions table.
func TestMigrate_EmptyFSSucceeds(t *testing.T) {
	database := openMemory(t)

	fsys := fstest.MapFS{}

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() with empty FS error: %v", err)
	}

	if !tableExists(t, database, "schema_versions") {
		t.Error("schema_versions should be created even for empty FS")
	}

	if countVersions(t, database) != 0 {
		t.Error("schema_versions should be empty for empty FS")
	}
}

// TestMigrate_InvalidSQLReturnsError verifies that a migration with invalid
// SQL causes MigrateFS to return a non-nil error.
func TestMigrate_InvalidSQLReturnsError(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"001_bad.sql", "THIS IS NOT VALID SQL !!!@@@###",
	)

	err := db.MigrateFS(database, fsys)
	if err == nil {
		t.Error("MigrateFS() should return error for invalid SQL, got nil")
	}
}

// TestMigrate_ReadFileErrorReturnsError verifies that an FS read failure
// surfaces as an error from MigrateFS.
func TestMigrate_ReadFileErrorReturnsError(t *testing.T) {
	database := openMemory(t)

	err := db.MigrateFS(database, failReadFS{})
	if err == nil {
		t.Error("MigrateFS() should return error when ReadFile fails")
	}
}

// TestMigrate_PartialRunRecordsOnlyApplied verifies that if the second
// migration in a set fails, only the first is recorded in schema_versions.
func TestMigrate_PartialRunRecordsOnlyApplied(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"001_good.sql", "CREATE TABLE IF NOT EXISTS partial_good (id INTEGER PRIMARY KEY);",
		"002_bad.sql", "THIS IS DEFINITELY NOT SQL;",
	)

	_ = db.MigrateFS(database, fsys) // we expect an error; ignore it here

	if !hasVersion(t, database, "001_good.sql") {
		t.Error("001_good.sql should be recorded even though 002 failed")
	}
	if hasVersion(t, database, "002_bad.sql") {
		t.Error("002_bad.sql should NOT be recorded because it failed")
	}
}

// TestMigrate_NonSQLFilesSkipped verifies that files without a .sql extension
// are skipped and not recorded in schema_versions.
func TestMigrate_NonSQLFilesSkipped(t *testing.T) {
	database := openMemory(t)

	fsys := fstest.MapFS{
		"README.md":  {Data: []byte("not sql")},
		"001_ok.sql": {Data: []byte("CREATE TABLE IF NOT EXISTS ns_test (id INTEGER PRIMARY KEY);")},
		"002_ok.go":  {Data: []byte("package migrations")},
	}

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	if hasVersion(t, database, "README.md") {
		t.Error("README.md should not be recorded in schema_versions")
	}
	if hasVersion(t, database, "002_ok.go") {
		t.Error("002_ok.go should not be recorded in schema_versions")
	}
	if !hasVersion(t, database, "001_ok.sql") {
		t.Error("001_ok.sql should be recorded in schema_versions")
	}
}

// TestMigrate_WithRealMigrations is an integration smoke test: run the
// production migration set through the tracked MigrateFS and verify the
// schema_versions table contains exactly one row per .sql file in the FS.
func TestMigrate_WithRealMigrations(t *testing.T) {
	database := openMemory(t)

	if err := db.Migrate(database); err != nil {
		t.Fatalf("Migrate() error: %v", err)
	}

	if !tableExists(t, database, "schema_versions") {
		t.Fatal("schema_versions not created by Migrate()")
	}

	// Count .sql files in the embedded FS by running Migrate again (no-op) and
	// inspecting the version count.  We just verify the count is > 0.
	n := countVersions(t, database)
	if n == 0 {
		t.Error("schema_versions is empty after running production migrations")
	}
}

// TestMigrate_WithRealMigrationsIdempotent verifies the production migration
// set can be run twice without error via the tracked path.
func TestMigrate_WithRealMigrationsIdempotent(t *testing.T) {
	database := openMemory(t)

	if err := db.Migrate(database); err != nil {
		t.Fatalf("Migrate() first run error: %v", err)
	}
	if err := db.Migrate(database); err != nil {
		t.Fatalf("Migrate() second run error: %v", err)
	}
}

// TestMigrate_SeedDetectionUsesKnownTable verifies the seeding heuristic: it
// must detect an existing DB by the presence of a known table (e.g. "users"),
// not by an arbitrary table name.
func TestMigrate_SeedDetectionUsesKnownTable(t *testing.T) {
	database := openMemory(t)

	// Create only an unrelated table — not one of the known sentinel tables.
	if _, err := database.ExecContext(context.Background(),
		"CREATE TABLE IF NOT EXISTS unrelated (id INTEGER PRIMARY KEY);",
	); err != nil {
		t.Fatalf("setup: %v", err)
	}

	fsys := simpleFS(
		"001_new.sql", "CREATE TABLE IF NOT EXISTS new_table (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	// Since "users" table was absent, seeding must NOT have occurred and the
	// migration must have actually been applied.
	if !tableExists(t, database, "new_table") {
		t.Error("new_table should exist — migration was not seeded, so it must have run")
	}
}

// TestMigrate_ErrorMessageContainsFilename verifies that when a migration
// fails, the returned error message includes the filename for easier debugging.
func TestMigrate_ErrorMessageContainsFilename(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"042_broken.sql", "INVALID SQL STATEMENT;",
	)

	err := db.MigrateFS(database, fsys)
	if err == nil {
		t.Fatal("expected error, got nil")
	}

	if !strings.Contains(err.Error(), "042_broken.sql") {
		t.Errorf("error %q does not mention the failing filename", err.Error())
	}
}

// TestMigrate_ReadDirErrorReturnsError verifies that when the FS returns an
// error from ReadDir, MigrateFS propagates it as a non-nil error.
func TestMigrate_ReadDirErrorReturnsError(t *testing.T) {
	database := openMemory(t)

	err := db.MigrateFS(database, failReadDirFS{})
	if err == nil {
		t.Error("MigrateFS() should return error when ReadDir fails")
	}
}

// TestMigrate_SchemaVersionsHasPrimaryKey verifies the schema_versions table
// uses version as PRIMARY KEY, preventing duplicate rows for the same file.
func TestMigrate_SchemaVersionsHasPrimaryKey(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"001_pk.sql", "CREATE TABLE IF NOT EXISTS pk_test (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	// Attempting a duplicate insert must fail.
	_, err := database.ExecContext(context.Background(),
		"INSERT INTO schema_versions (version, applied_at) VALUES ('001_pk.sql', datetime('now'))",
	)
	if err == nil {
		t.Error("duplicate insert into schema_versions should fail — PRIMARY KEY not enforced")
	}
}

// TestMigrate_SeedRecordsAllFilesFromFS verifies that seeding writes a row for
// every .sql file in the FS, including when there are multiple files.
func TestMigrate_SeedRecordsAllFilesFromFS(t *testing.T) {
	database := openMemory(t)

	// Create the users sentinel to trigger seeding on first call.
	if _, err := database.ExecContext(context.Background(), "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY);"); err != nil {
		t.Fatalf("setup: %v", err)
	}

	fsys := simpleFS(
		"001_a.sql", "CREATE TABLE IF NOT EXISTS seed_a (id INTEGER PRIMARY KEY);",
		"002_b.sql", "CREATE TABLE IF NOT EXISTS seed_b (id INTEGER PRIMARY KEY);",
		"003_c.sql", "CREATE TABLE IF NOT EXISTS seed_c (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	for _, name := range []string{"001_a.sql", "002_b.sql", "003_c.sql"} {
		if !hasVersion(t, database, name) {
			t.Errorf("%q not seeded into schema_versions", name)
		}
	}

	// Tables from the seeded migrations must NOT have been created
	// (seeding records without executing).
	for _, tbl := range []string{"seed_a", "seed_b", "seed_c"} {
		if tableExists(t, database, tbl) {
			t.Errorf("table %q should not exist — migration was seeded, not executed", tbl)
		}
	}
}

// TestMigrate_FreshDatabaseRunsAllMigrations verifies that a completely fresh
// database (no tables at all) runs every migration without seeding.
func TestMigrate_FreshDatabaseRunsAllMigrations(t *testing.T) {
	database := openMemory(t)

	fsys := simpleFS(
		"001_fresh.sql", "CREATE TABLE IF NOT EXISTS fresh_a (id INTEGER PRIMARY KEY);",
		"002_fresh.sql", "CREATE TABLE IF NOT EXISTS fresh_b (id INTEGER PRIMARY KEY);",
	)

	if err := db.MigrateFS(database, fsys); err != nil {
		t.Fatalf("MigrateFS() error: %v", err)
	}

	for _, tbl := range []string{"fresh_a", "fresh_b"} {
		if !tableExists(t, database, tbl) {
			t.Errorf("table %q should exist on fresh database run", tbl)
		}
	}

	if countVersions(t, database) != 2 {
		t.Errorf("expected 2 version rows, got %d", countVersions(t, database))
	}
}

// TestMigrate_LargeNumberOfMigrations verifies that MigrateFS can handle
// a large set of migrations without degrading or skipping any.
func TestMigrate_LargeNumberOfMigrations(t *testing.T) {
	database := openMemory(t)

	const n = 50
	pairs := make([]string, 0, n*2)
	for i := 1; i <= n; i++ {
		name := fmt.Sprintf("%03d_large.sql", i)
		sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS large_%03d (id INTEGER PRIMARY KEY);", i)
		pairs = append(pairs, name, sql)
	}

	if err := db.MigrateFS(database, simpleFS(pairs...)); err != nil {
		t.Fatalf("MigrateFS() error with %d migrations: %v", n, err)
	}

	got := countVersions(t, database)
	if got != n {
		t.Errorf("schema_versions has %d rows, want %d", got, n)
	}
}
