package migrate

import (
	"fmt"
	"sort"

	"github.com/go-gormigrate/gormigrate/v2"
	"gorm.io/gorm"
)

// RunCoreMigrate runs migration queue against the given gorm db.
// One must call `migrate.AddCoreMigrateRecord` first to init migration queue.
func RunCoreMigrate(db *gorm.DB) error {
	// we don't depend on foreign key constraint, so just disable it.
	db.DisableForeignKeyConstraintWhenMigrating = true

	return doMigrate(db, initCoreDB, coreDBMigrationRecords)
}

// AddCoreMigrateRecord add a migration functions to migration queue.
// @param - m - the migration function.
func AddCoreMigrateRecord(version string, m func(*gorm.DB) error) bool {
	checkDuplicateMigrationVersion(version, coreDBMigrationRecords)
	coreDBMigrationRecords = append(coreDBMigrationRecords, &migration{
		version: version,
		migrate: m,
	})
	return true
}

var coreDBMigrationRecords []*migration

type migration struct {
	migrate func(*gorm.DB) error
	version string
}

// byVersion implements sort.Interface based on the version field.
type byVersion []*migration

func (a byVersion) Len() int           { return len(a) }
func (a byVersion) Less(i, j int) bool { return a[i].version < a[j].version }
func (a byVersion) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

// todo: backup and restore database
func doMigrate(db *gorm.DB, initSchema gormigrate.InitSchemaFunc, migrations []*migration) error {
	sort.Sort(byVersion(migrations))
	var gormMigrations []*gormigrate.Migration
	for _, item := range migrations {
		gormMigrations = append(gormMigrations, &gormigrate.Migration{
			ID:      item.version,
			Migrate: item.migrate,
		})
	}
	options := &gormigrate.Options{
		TableName:                 "core_migrations",
		IDColumnName:              "id",
		IDColumnSize:              255,
		UseTransaction:            false,
		ValidateUnknownMigrations: false,
	}
	goMigration := gormigrate.New(db, options, gormMigrations)
	goMigration.InitSchema(initSchema)
	return goMigration.Migrate()
}

func checkDuplicateMigrationVersion(version string, records []*migration) {
	for _, item := range records {
		if item.version == version {
			panic(fmt.Sprintf("migration version is already added: %s", version))
		}
	}
}
