package sqldb

import (
	"fmt"
	"io/fs"
	"strings"

	"github.com/featurebasedb/featurebase/v3/errors"
	"github.com/featurebasedb/featurebase/v3/logger"
	"github.com/gobuffalo/pop/v6"
)

// EmbedMigrator is a migrator for SQL and Fizz files which are
// embedded in any fs.FS. This is lifted directly from pop's
// FileMigrator and tweaked slightly to take an fs.FS instead of a
// file path.
type EmbedMigrator struct {
	pop.Migrator
	FS  fs.FS
	log logger.Logger
}

// NewEmbedMigrator for a path and a Connection
func NewEmbedMigrator(fs fs.FS, c *pop.Connection, log logger.Logger) (*EmbedMigrator, error) {
	fm := &EmbedMigrator{
		Migrator: pop.NewMigrator(c),
		FS:       fs,
		log:      log,
	}
	fm.SchemaPath = ""

	runner := func(mf pop.Migration, tx *pop.Connection) error {
		f, err := fm.FS.Open(mf.Path)
		if err != nil {
			return err
		}
		defer f.Close()
		content, err := pop.MigrationContent(mf, tx, f, true)
		if err != nil {
			return fmt.Errorf("error processing %s: %w", mf.Path, err)
		}
		if content == "" {
			return nil
		}
		err = tx.RawQuery(content).Exec()
		if err != nil {
			return fmt.Errorf("error executing %s, sql: %s: %w", mf.Path, content, err)
		}
		return nil
	}

	err := fm.findMigrations(runner)
	if err != nil {
		return fm, err
	}

	return fm, nil
}

func (fm *EmbedMigrator) findMigrations(runner func(mf pop.Migration, tx *pop.Connection) error) error {
	return fs.WalkDir(fm.FS, "migrations", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return errors.Wrap(err, "walking dir")
		}

		if d.IsDir() {
			return nil
		}

		match, err := pop.ParseMigrationFilename(d.Name())
		if err != nil {
			if strings.HasPrefix(err.Error(), "unsupported dialect") {
				fm.log.Warnf("ignoring migration file with %s", err.Error())
				return nil
			}
			return err
		}
		if match == nil {
			fm.log.Warnf("ignoring file %s because it does not match the migration file pattern", d.Name())
			return nil
		}
		mf := pop.Migration{
			Path:      path,
			Version:   match.Version,
			Name:      match.Name,
			DBType:    match.DBType,
			Direction: match.Direction,
			Type:      match.Type,
			Runner:    runner,
		}
		switch mf.Direction {
		case "up":
			fm.UpMigrations.Migrations = append(fm.UpMigrations.Migrations, mf)
		case "down":
			fm.DownMigrations.Migrations = append(fm.DownMigrations.Migrations, mf)
		default:
			// the regex only matches `(up|down)` for direction, so a panic here is appropriate
			panic("got unknown migration direction " + mf.Direction)
		}

		return nil
	})
}
