package storage

import (
	"fmt"
	"strings"

	"git.mstar.dev/mstar/goutils/other"
	"git.mstar.dev/mstar/goutils/sliceutils"
	"gorm.io/gorm"

	"git.mstar.dev/mstar/linstrom/storage-new/models"
)

// Auto-migrate all tables and types used
func Migrate(db *gorm.DB) error {
	if err := createAccountAuthMethodType(db); err != nil {
		return other.Error("storage", "Failed to create Auth Method type", err)
	}
	if err := createBeingType(db); err != nil {
		return other.Error("storage", "Failed to create Being type", err)
	}
	if err := createAccountRelationType(db); err != nil {
		return other.Error("storage", "Failed to create Account Relation type", err)
	}
	if err := createRemoteServerSoftwareType(db); err != nil {
		return other.Error("storage", "Failed to create Server Software type", err)
	}
	if err := migrateTypes(db); err != nil {
		return other.Error("storage", "Failed to automigrate data structs", err)
	}
	return nil
}

// Returns the raw error created by gorm, with no wrapping
func migrateTypes(db *gorm.DB) error {
	if err := db.AutoMigrate(models.AllTypes...); err != nil {
		return err
	}
	return nil
}

// Ensure the being enum exists for the user
func createBeingType(db *gorm.DB) error {
	return migrateEnum(
		db,
		"being_type",
		sliceutils.Map(models.AllBeings, func(t models.BeingType) string { return string(t) }),
	)
}

func createAccountRelationType(db *gorm.DB) error {
	return migrateEnum(
		db,
		"relation_type",
		sliceutils.Map(
			models.AllRelations,
			func(t models.RelationType) string { return string(t) },
		),
	)
}

func createAccountAuthMethodType(db *gorm.DB) error {
	return migrateEnum(
		db,
		"auth_method_type",
		sliceutils.Map(
			models.AllAuthMethods,
			func(t models.AuthenticationMethodType) string { return string(t) },
		),
	)
}

func createRemoteServerSoftwareType(db *gorm.DB) error {
	return migrateEnum(
		db,
		"server_software_type",
		sliceutils.Map(
			models.AllServerSoftwareTypes,
			func(t models.ServerSoftwareType) string { return string(t) },
		),
	)
}

// Helper function for ensuring the existence of an enum with the given values
func migrateEnum(db *gorm.DB, name string, values []string) error {
	if err := db.Exec("DROP TYPE IF EXISTS " + name + " CASCADE;").Error; err != nil {
		return other.Error(
			"storage",
			fmt.Sprintf("Failed to remove old type %s (if it exists)", name),
			err,
		)
	}
	queryBuilder := strings.Builder{}
	queryBuilder.WriteString("CREATE TYPE ")
	queryBuilder.WriteString(name)
	queryBuilder.WriteString(" AS ENUM (")
	blen := len(values)
	for i, btype := range values {
		queryBuilder.WriteString("'" + string(btype) + "'")
		// Append comma everywhere except last entry
		if i+1 < blen {
			queryBuilder.WriteString(",")
		}
	}
	queryBuilder.WriteString(");")
	if err := db.Exec(queryBuilder.String()).Error; err != nil {
		return err
	}
	return nil
}
