// Package database implements `dev --database`: it launches a local TiDB
// playground (or Ultra runtime) and stays attached until cancelled. On
// shutdown it tears the playground down.
package database

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"strconv"

	"github.com/tidbcloud/AgentTeam/dev/internal/config"
	"github.com/tidbcloud/AgentTeam/dev/internal/features"
	"github.com/tidbcloud/AgentTeam/dev/internal/tidb"
)

var (
	newProvider        = tidb.New
	startPlayground    = tidb.StartPlayground
	loadFeatureOptions = resolveFeatureOptions
)

// Options are the inputs to `dev --database`. All optional fields fall back to
// AGENTTEAM_DEV_TIDB_* env vars (see Resolve).
type Options struct {
	RepoRoot string
	Tag      string
	DBPort   string
	PDPort   string
	KVPort   string
	Version  string
	Database string
}

// Resolve applies AGENTTEAM_DEV_TIDB_* env-var defaults to opts and returns the
// filled-in copy.
func Resolve(opts Options) Options {
	if opts.Tag == "" {
		opts.Tag = envOr("AGENTTEAM_DEV_TIDB_TAG", features.LocalTiDBTag())
	}
	if opts.DBPort == "" {
		opts.DBPort = envOr("AGENTTEAM_DEV_TIDB_DB_PORT", "4000")
	}
	if opts.PDPort == "" {
		opts.PDPort = envOr("AGENTTEAM_DEV_TIDB_PD_PORT", "2379")
	}
	if opts.KVPort == "" {
		opts.KVPort = envOr("AGENTTEAM_DEV_TIDB_KV_PORT", "20160")
	}
	if opts.Version == "" {
		opts.Version = envOr("AGENTTEAM_DEV_TIDB_VERSION", envOr("AGENTTEAM_TIDB_PLAYGROUND_VERSION", "v8.5.5"))
	}
	if opts.Database == "" {
		opts.Database = envOr("AGENTTEAM_DEV_TIDB_DATABASE", "agentteam_core")
	}
	return opts
}

// Run blocks until ctx is cancelled, owning the launched TiDB playground.
// Caller is expected to pass Resolve()ed opts.
func Run(ctx context.Context, opts Options) error {
	if _, err := strconv.Atoi(opts.DBPort); err != nil {
		return fmt.Errorf("AGENTTEAM_DEV_TIDB_DB_PORT must be numeric, got %q", opts.DBPort)
	}

	slog.Info("starting TiDB playground", "tag", opts.Tag, "repo", opts.RepoRoot, "db_port", opts.DBPort, "pd_port", opts.PDPort, "kv_port", opts.KVPort, "version", opts.Version)

	featureOpts := features.Options{RepoRoot: opts.RepoRoot}
	if os.Getenv(features.EnvName) != "" {
		parsed, err := features.Load(opts.RepoRoot)
		if err != nil {
			return err
		}
		featureOpts = parsed
	}

	if featureOpts.UltraEnabled() {
		// ULTRA manages its own lifecycle via tiup --Setsid, so we can't reuse
		// StartPlayground directly. Fall back to the provider, then idle.
		provider, err := newProvider(featureOpts)
		if err != nil {
			return err
		}
		if _, err := ensureUltraProvider(ctx, featureOpts, provider); err != nil {
			return err
		}
		slog.Info("TiDB Ultra runtime is ready; Ctrl+C to stop")
		<-ctx.Done()
		provider.Stop()
		return nil
	}

	cleanup, err := startPlayground(ctx, playgroundOptions(opts))
	if err != nil {
		return err
	}
	defer cleanup()
	slog.Info("TiDB playground is ready; Ctrl+C to stop", "addr", "127.0.0.1:"+opts.DBPort)
	<-ctx.Done()
	slog.Info("shutting down TiDB playground...")
	return nil
}

// Start launches the selected database provider, waits for readiness, and
// returns without registering shutdown cleanup. The provider's ownership
// record is the durable handle used by later dev invocations to stop it.
func Start(ctx context.Context, opts Options) error {
	if _, err := strconv.Atoi(opts.DBPort); err != nil {
		return fmt.Errorf("AGENTTEAM_DEV_TIDB_DB_PORT must be numeric, got %q", opts.DBPort)
	}
	featureOpts, err := loadFeatureOptions(opts.RepoRoot)
	if err != nil {
		return err
	}
	if featureOpts.UltraEnabled() {
		return fmt.Errorf("--database-start does not support ULTRA; use --database so the runtime remains lifecycle-owned")
	}
	_, err = startPlayground(ctx, playgroundOptions(opts))
	return err
}

func ensureUltraProvider(ctx context.Context, opts features.Options, provider tidb.Provider) (*config.Config, error) {
	cfg := config.Build()
	if err := provider.Ensure(ctx, opts, cfg); err != nil {
		return nil, err
	}
	return cfg, nil
}

func resolveFeatureOptions(repoRoot string) (features.Options, error) {
	if os.Getenv(features.EnvName) == "" {
		return features.Options{RepoRoot: repoRoot}, nil
	}
	return features.Load(repoRoot)
}

func playgroundOptions(opts Options) tidb.StartPlaygroundOptions {
	return tidb.StartPlaygroundOptions{
		RepoRoot: opts.RepoRoot,
		Tag:      opts.Tag,
		DBPort:   opts.DBPort,
		PDPort:   opts.PDPort,
		KVPort:   opts.KVPort,
		Version:  opts.Version,
		Database: opts.Database,
	}
}

func envOr(name, def string) string {
	if v := os.Getenv(name); v != "" {
		return v
	}
	return def
}
