package database

import (
	"sort"
	"time"

	"github.com/rs/zerolog/log"
	"gorm.io/gorm"
)

// DataMigration tracks one-time data migrations that have been applied.
type DataMigration struct {
	ID        uint      `gorm:"primaryKey"`
	Name      string    `gorm:"column:name;uniqueIndex;size:255;not null"`
	AppliedAt time.Time `gorm:"column:applied_at;not null"`
}

func (DataMigration) TableName() string {
	return "data_migrations"
}

// dataMigrationEntry pairs a name with its run function.
type dataMigrationEntry struct {
	Name string
	Fn   func(tx *gorm.DB) error
}

// registry holds all registered one-time data migrations, in order.
var registry []dataMigrationEntry

// RegisterDataMigration adds a one-time migration. Call from init() functions.
func RegisterDataMigration(name string, fn func(tx *gorm.DB) error) {
	registry = append(registry, dataMigrationEntry{Name: name, Fn: fn})
}

// RunDataMigrations creates the tracking table and executes any migrations
// that haven't been applied yet. Called once during server startup.
func RunDataMigrations() error {
	if db == nil {
		return nil
	}

	// Ensure tracking table exists
	if err := db.AutoMigrate(&DataMigration{}); err != nil {
		return err
	}

	if len(registry) == 0 {
		return nil
	}

	// Sort by name for deterministic order
	sort.Slice(registry, func(i, j int) bool {
		return registry[i].Name < registry[j].Name
	})

	// Load already-applied migrations
	var applied []DataMigration
	if err := db.Find(&applied).Error; err != nil {
		return err
	}
	appliedSet := make(map[string]bool, len(applied))
	for _, m := range applied {
		appliedSet[m.Name] = true
	}

	// Run pending migrations
	for _, entry := range registry {
		if appliedSet[entry.Name] {
			continue
		}

		log.Info().Str("migration", entry.Name).Msg("Running data migration")

		err := db.Transaction(func(tx *gorm.DB) error {
			if err := entry.Fn(tx); err != nil {
				return err
			}
			// Record that this migration has been applied
			return tx.Create(&DataMigration{
				Name:      entry.Name,
				AppliedAt: time.Now().UTC(),
			}).Error
		})
		if err != nil {
			log.Error().Err(err).Str("migration", entry.Name).Msg("Data migration failed")
			return err
		}

		log.Info().Str("migration", entry.Name).Msg("Data migration completed")
	}

	return nil
}
