package store

import (
	"context"
	"database/sql"
	"embed"
	"fmt"
	"io/fs"
	"sort"
	"strings"
)

//go:embed migrations/*.sql
var migrationsFS embed.FS

// migration is one ordered SQL file from migrations/.
type migration struct {
	version int    // parsed from filename prefix (0001, 0002, …)
	name    string // full filename, for error messages
	sql     string
}

// loadMigrations reads every migrations/*.sql file in lexical order
// and returns them. Filenames must look like NNNN_name.sql; the
// numeric prefix is the version.
func loadMigrations() ([]migration, error) {
	entries, err := fs.ReadDir(migrationsFS, "migrations")
	if err != nil {
		return nil, fmt.Errorf("read migrations dir: %w", err)
	}
	out := make([]migration, 0, len(entries))
	for _, e := range entries {
		if e.IsDir() || !strings.HasSuffix(e.Name(), ".sql") {
			continue
		}
		var v int
		// Allow up to 6 digits (we will never need that many but it
		// costs nothing to be permissive).
		if _, err := fmt.Sscanf(e.Name(), "%d_", &v); err != nil {
			return nil, fmt.Errorf("migration %q: cannot parse version prefix: %w", e.Name(), err)
		}
		body, err := fs.ReadFile(migrationsFS, "migrations/"+e.Name())
		if err != nil {
			return nil, fmt.Errorf("read %s: %w", e.Name(), err)
		}
		out = append(out, migration{version: v, name: e.Name(), sql: string(body)})
	}
	sort.Slice(out, func(i, j int) bool { return out[i].version < out[j].version })
	return out, nil
}

// migrate brings the db up to the highest known version. It is
// idempotent: running it on an already-current db is a no-op. There
// is no rollback path; we move forward only.
func migrate(ctx context.Context, db *sql.DB) error {
	if _, err := db.ExecContext(ctx, `
		CREATE TABLE IF NOT EXISTS schema_version (
			version INTEGER PRIMARY KEY,
			applied_at TEXT NOT NULL
		)
	`); err != nil {
		return fmt.Errorf("create schema_version: %w", err)
	}

	migs, err := loadMigrations()
	if err != nil {
		return err
	}

	for _, m := range migs {
		var applied int
		row := db.QueryRowContext(ctx,
			`SELECT COUNT(*) FROM schema_version WHERE version = ?`, m.version)
		if err := row.Scan(&applied); err != nil {
			return fmt.Errorf("check version %d: %w", m.version, err)
		}
		if applied > 0 {
			continue
		}

		tx, err := db.BeginTx(ctx, nil)
		if err != nil {
			return fmt.Errorf("begin tx for migration %s: %w", m.name, err)
		}
		if _, err := tx.ExecContext(ctx, m.sql); err != nil {
			_ = tx.Rollback()
			return fmt.Errorf("apply %s: %w", m.name, err)
		}
		if _, err := tx.ExecContext(ctx,
			`INSERT INTO schema_version (version, applied_at) VALUES (?, datetime('now'))`,
			m.version); err != nil {
			_ = tx.Rollback()
			return fmt.Errorf("record %s: %w", m.name, err)
		}
		if err := tx.Commit(); err != nil {
			return fmt.Errorf("commit %s: %w", m.name, err)
		}
	}
	return nil
}
