package migrations

import (
	"bufio"
	"context"
	"database/sql"
	"fmt"
	"strings"

	"github.com/pressly/goose/v3"
	goosedatabase "github.com/pressly/goose/v3/database"
)

const TiDBBootstrapBaselineVersion int64 = 362

type TiDBGooseStore struct {
	goosedatabase.Store
}

func NewTiDBGooseStore() (*TiDBGooseStore, error) {
	store, err := goosedatabase.NewStore(goosedatabase.DialectTiDB, goose.DefaultTablename)
	if err != nil {
		return nil, fmt.Errorf("create tidb goose store: %w", err)
	}
	return &TiDBGooseStore{Store: store}, nil
}

func (s *TiDBGooseStore) TableExists(ctx context.Context, db goosedatabase.DBTxConn) (bool, error) {
	var exists bool
	if err := db.QueryRowContext(ctx, `
SELECT EXISTS (
	SELECT 1
	FROM information_schema.tables
	WHERE table_schema = DATABASE()
	  AND table_name = ?
)
`, s.Tablename()).Scan(&exists); err != nil {
		return false, fmt.Errorf("check goose table exists: %w", err)
	}
	return exists, nil
}

func SeedTiDBGooseBaseline(ctx context.Context, db *sql.DB, version int64) error {
	if db == nil {
		return fmt.Errorf("seed tidb goose baseline: sql db is nil")
	}

	store, err := NewTiDBGooseStore()
	if err != nil {
		return err
	}

	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin tidb goose baseline tx: %w", err)
	}
	committed := false
	defer func() {
		if !committed {
			_ = tx.Rollback()
		}
	}()

	versionTableExists, err := store.TableExists(ctx, tx)
	if err != nil {
		return err
	}
	if versionTableExists {
		if _, err := tx.ExecContext(ctx, "DELETE FROM "+goose.DefaultTablename); err != nil {
			return fmt.Errorf("reset tidb goose version table: %w", err)
		}
	} else {
		if err := store.CreateVersionTable(ctx, tx); err != nil {
			return fmt.Errorf("create tidb goose version table: %w", err)
		}
	}
	if err := store.Insert(ctx, tx, goosedatabase.InsertRequest{Version: 0}); err != nil {
		return fmt.Errorf("insert tidb goose version 0: %w", err)
	}
	if err := store.Insert(ctx, tx, goosedatabase.InsertRequest{Version: version}); err != nil {
		return fmt.Errorf("insert tidb goose baseline version %d: %w", version, err)
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("commit tidb goose baseline tx: %w", err)
	}
	committed = true
	return nil
}

func ExecSQLStatements(ctx context.Context, db *sql.DB, script string) error {
	if db == nil {
		return fmt.Errorf("exec sql statements: sql db is nil")
	}

	scanner := bufio.NewScanner(strings.NewReader(script))
	var statement strings.Builder
	for scanner.Scan() {
		line := scanner.Text()
		statement.WriteString(line)
		statement.WriteByte('\n')
		if !strings.HasSuffix(strings.TrimSpace(line), ";") {
			continue
		}
		if err := execSQLStatement(ctx, db, statement.String()); err != nil {
			return err
		}
		statement.Reset()
	}
	if err := scanner.Err(); err != nil {
		return fmt.Errorf("scan sql statements: %w", err)
	}
	if err := execSQLStatement(ctx, db, statement.String()); err != nil {
		return err
	}
	return nil
}

func execSQLStatement(ctx context.Context, db *sql.DB, statement string) error {
	trimmed := strings.TrimSpace(statement)
	if trimmed == "" {
		return nil
	}
	if _, err := db.ExecContext(ctx, trimmed); err != nil {
		return err
	}
	return nil
}
