package bootstrapimpl

import (
	"context"
	"database/sql"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
)

// TestMigrationApplied_NotPresent — fresh DB returns false, no error.
func TestMigrationApplied_NotPresent(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs("any-id").
		WillReturnRows(sqlmock.NewRows([]string{"_"}))

	applied, err := MigrationApplied(context.Background(), db, "any-id")
	if err != nil {
		t.Fatalf("MigrationApplied = error %v; want nil", err)
	}
	if applied {
		t.Fatal("MigrationApplied = true on empty result; want false")
	}
}

// TestMigrationApplied_Present — stamped DB returns true, no error.
func TestMigrationApplied_Present(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs("foo").
		WillReturnRows(sqlmock.NewRows([]string{"_"}).AddRow(1))

	applied, err := MigrationApplied(context.Background(), db, "foo")
	if err != nil {
		t.Fatalf("MigrationApplied = error %v; want nil", err)
	}
	if !applied {
		t.Fatal("MigrationApplied = false on present row; want true")
	}
}

// TestMigrationApplied_QueryError surfaces non-ErrNoRows errors so callers
// can fail closed instead of silently treating an unreachable DB as "not
// applied" and re-running a one-shot migration.
func TestMigrationApplied_QueryError(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs("err-id").
		WillReturnError(sql.ErrConnDone)

	_, err = MigrationApplied(context.Background(), db, "err-id")
	if err == nil {
		t.Fatal("MigrationApplied = nil error on connection failure; want error")
	}
}

// TestRecordMigrationApplied_InsertShape locks the INSERT shape — Id +
// Version + Description plus an ON DUPLICATE KEY UPDATE clause that keeps
// the original AppliedAt while bumping LastModifiedDate.
func TestRecordMigrationApplied_InsertShape(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	expected := regexp.MustCompile(
		`(?s)INSERT INTO _System_Migration.*Version.*Description.*` +
			`AppliedAt.*ON DUPLICATE KEY UPDATE`,
	)
	mock.ExpectExec(expected.String()).
		WithArgs("mig-1", 1, "first").
		WillReturnResult(sqlmock.NewResult(1, 1))

	if err := RecordMigrationApplied(context.Background(), db, "mig-1", 1, "first"); err != nil {
		t.Fatalf("RecordMigrationApplied = error %v; want nil", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}
