package migration

import (
	"strings"
	"testing"

	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
)

type existingSchema struct {
	ID   uint `gorm:"primaryKey"`
	Name string
}

func (existingSchema) TableName() string { return "existing_schema" }

func TestCreateMissingTablesDoesNotAlterExistingTables(t *testing.T) {
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
	if err != nil {
		t.Fatal(err)
	}
	if err = db.Exec("CREATE TABLE existing_schema (id integer primary key)").Error; err != nil {
		t.Fatal(err)
	}
	if err = CreateMissingTables(db, &existingSchema{}); err != nil {
		t.Fatal(err)
	}
	if db.Migrator().HasColumn(&existingSchema{}, "name") {
		t.Fatal("existing schema was altered")
	}
}

func TestCreateMissingTablesCreatesAbsentTables(t *testing.T) {
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
	if err != nil {
		t.Fatal(err)
	}
	if err = CreateMissingTables(db, &existingSchema{}); err != nil {
		t.Fatal(err)
	}
	if !db.Migrator().HasTable(&existingSchema{}) || !db.Migrator().HasColumn(&existingSchema{}, "name") {
		t.Fatal("missing schema was not created")
	}
}

func TestRunRepairsInvalidMigrationMetadataTable(t *testing.T) {
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
	if err != nil {
		t.Fatal(err)
	}
	if err = db.Exec("CREATE TABLE sys_schema_migrations (id integer primary key, title text)").Error; err != nil {
		t.Fatal(err)
	}
	called := false
	if err = Run(db, []Step{{ID: "202608200001_test", Migrate: func(*gorm.DB) error {
		called = true
		return nil
	}}}); err != nil {
		t.Fatal(err)
	}
	if !called {
		t.Fatal("migration was not rerun after metadata repair")
	}
	var dataType string
	if err = db.Raw("SELECT type FROM pragma_table_info(?) WHERE name = 'id'", TableName).Scan(&dataType).Error; err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(strings.ToLower(dataType), "text") {
		t.Fatalf("migration id type = %q", dataType)
	}
}

func TestRunDoesNotLeakStatementState(t *testing.T) {
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
	if err != nil {
		t.Fatal(err)
	}
	if err = Run(db, []Step{{ID: "202608200001_test", Migrate: func(tx *gorm.DB) error {
		return CreateMissingTables(tx, &existingSchema{})
	}}}); err != nil {
		t.Fatal(err)
	}
	var count int64
	if err = db.Model(&existingSchema{}).Count(&count).Error; err != nil {
		t.Fatalf("shared database retained migration statement state: %v", err)
	}
}

func TestRunRejectsUnknownAppliedMigration(t *testing.T) {
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
	if err != nil {
		t.Fatal(err)
	}
	if err = db.Migrator().CreateTable(&record{}); err != nil {
		t.Fatal(err)
	}
	if err = db.Create(&record{ID: "unknown"}).Error; err != nil {
		t.Fatal(err)
	}
	if err = Run(db, []Step{{ID: "known", Migrate: func(*gorm.DB) error { return nil }}}); err == nil {
		t.Fatal("unknown migration was accepted")
	}
}
