package store

import (
	"database/sql"
	"errors"
	"fmt"

	"github.com/golang-migrate/migrate/v4"
	migratemysql "github.com/golang-migrate/migrate/v4/database/mysql"
	"github.com/golang-migrate/migrate/v4/source/iofs"

	"github.com/wangjia/pangolin/server/migrations"
)

// MigrateUp runs all pending up migrations.
// migrate.ErrNoChange is treated as success (idempotent).
func MigrateUp(db *sql.DB) error {
	return runMigration(db, func(m *migrate.Migrate) error {
		return m.Up()
	})
}

// MigrateDown rolls back all applied migrations.
// migrate.ErrNoChange is treated as success.
func MigrateDown(db *sql.DB) error {
	return runMigration(db, func(m *migrate.Migrate) error {
		return m.Down()
	})
}

// MigrateVersion returns the currently applied migration version and whether
// the schema is in a dirty state. Returns version 0 and no error when no
// migrations have been applied yet.
func MigrateVersion(db *sql.DB) (uint, bool, error) {
	m, cleanup, err := newMigrator(db)
	if err != nil {
		return 0, false, err
	}
	defer cleanup()

	v, dirty, err := m.Version()
	if errors.Is(err, migrate.ErrNilVersion) {
		return 0, false, nil
	}
	if err != nil {
		return 0, false, fmt.Errorf("store.migrate: version: %w", err)
	}
	return v, dirty, nil
}

// runMigration opens a migrator, calls fn, and handles ErrNoChange.
func runMigration(db *sql.DB, fn func(*migrate.Migrate) error) error {
	m, cleanup, err := newMigrator(db)
	if err != nil {
		return err
	}
	defer cleanup()

	if err := fn(m); err != nil && !errors.Is(err, migrate.ErrNoChange) {
		return fmt.Errorf("store.migrate: %w", err)
	}
	return nil
}

// newMigrator creates a golang-migrate instance backed by the embedded SQL
// files (iofs source) and the provided *sql.DB (mysql driver instance).
// The caller must invoke cleanup() to release source and driver resources.
func newMigrator(db *sql.DB) (*migrate.Migrate, func(), error) {
	src, err := iofs.New(migrations.FS, ".")
	if err != nil {
		return nil, nil, fmt.Errorf("store.migrate: iofs source: %w", err)
	}

	driver, err := migratemysql.WithInstance(db, &migratemysql.Config{})
	if err != nil {
		_ = src.Close()
		return nil, nil, fmt.Errorf("store.migrate: mysql driver: %w", err)
	}

	m, err := migrate.NewWithInstance("iofs", src, "mysql", driver)
	if err != nil {
		_ = src.Close()
		_ = driver.Close()
		return nil, nil, fmt.Errorf("store.migrate: new migrator: %w", err)
	}

	cleanup := func() {
		// Close source and driver; errors here are non-fatal cleanup.
		_, _ = m.Close()
	}
	return m, cleanup, nil
}
