// Package migrate runs AgentTeam database migrations by invoking cmd/agentteam-schema.
package migrate

import (
	"bytes"
	"database/sql"
	"fmt"
	"io"
	"log/slog"
	"os"
	"os/exec"
	"strings"
	"time"

	_ "github.com/go-sql-driver/mysql"

	"github.com/tidbcloud/AgentTeam/dev/internal/config"
	"github.com/tidbcloud/AgentTeam/dev/internal/env"
)

// Run executes `go run ./cmd/agentteam-schema init` with retries.
// It re-runs with the zero-recovery path if migrations detect a schema
// without a goose baseline.
func Run(repoRoot string, cfg *config.Config) error {
	return migrateOnce(repoRoot, cfg, false)
}

func migrateOnce(repoRoot string, cfg *config.Config, retried bool) error {
	slog.Info("running migrations...")
	for i := 1; i <= 5; i++ {
		output, err := runCapture(repoRoot, "go", "run", "./cmd/agentteam-schema", "init")
		if err == nil {
			slog.Info("migrations done")
			return nil
		}
		if strings.Contains(output, "tidb schema exists without goose baseline") {
			if cfg.IsLocalTiDB() {
				return fmt.Errorf("migrations failed: schema exists without goose baseline; choose a different RETAIN_DATA tag, or unset RETAIN_DATA and run make reset-dep to bootstrap a fresh database")
			}
			if retried {
				return fmt.Errorf("migrations failed after schema recovery")
			}
			slog.Info("TiDB Zero schema without goose baseline; dropping and recreating databases")
			DropZeroDatabases(cfg)
			return migrateOnce(repoRoot, cfg, true)
		}
		if i < 5 {
			slog.Warn("migration attempt failed, retrying", "attempt", i, "total", 5)
			time.Sleep(time.Second)
		}
	}
	return fmt.Errorf("migrations failed after 5 attempts")
}

func runCapture(repoRoot string, name string, args ...string) (string, error) {
	cmd := exec.Command(name, args...)
	cmd.Dir = repoRoot
	var output bytes.Buffer
	writer := io.MultiWriter(os.Stderr, &output)
	cmd.Stdout = writer
	cmd.Stderr = writer
	err := cmd.Run()
	return output.String(), err
}

// DropZeroDatabases drops agentteam_core/agentteam_meshx/agentteam_ags using cfg.CoreStorageDSN.
func DropZeroDatabases(cfg *config.Config) {
	if cfg == nil {
		return
	}
	adminDSN := env.ReplaceDSNDatabase(cfg.CoreStorageDSN, "test")
	for _, name := range []string{"agentteam_core", "agentteam_meshx", "agentteam_ags"} {
		slog.Info("dropping database", "database", name)
		if err := dropDatabase(adminDSN, name); err != nil {
			slog.Error("failed to drop database", "database", name, "error", err)
		}
	}
}

func dropDatabase(dsn, database string) error {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	defer db.Close()
	_, err = db.Exec("DROP DATABASE IF EXISTS `" + env.EscapeSQLIdentifier(database) + "`")
	return err
}
