package database

import (
	"context"
	"strings"
	"testing"

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

type recordingProvider struct {
	cfg *config.Config
}

func (p *recordingProvider) Ensure(_ context.Context, _ features.Options, cfg *config.Config) error {
	p.cfg = cfg
	return nil
}

func (p *recordingProvider) Stop() {}

func TestEnsureUltraProviderPassesConfig(t *testing.T) {
	provider := &recordingProvider{}
	cfg, err := ensureUltraProvider(context.Background(), features.Options{}, provider)
	if err != nil {
		t.Fatal(err)
	}
	if provider.cfg == nil {
		t.Fatal("Ultra provider received nil config")
	}
	if cfg != provider.cfg {
		t.Fatal("returned config is not the config passed to the provider")
	}
}

func TestStartReturnsAfterReadyWithoutRunningCleanup(t *testing.T) {
	oldStart := startPlayground
	t.Cleanup(func() { startPlayground = oldStart })

	cleanupCalls := 0
	startPlayground = func(_ context.Context, got tidb.StartPlaygroundOptions) (func(), error) {
		if got.Tag != "test-db" || got.DBPort != "14000" {
			t.Fatalf("options = %+v", got)
		}
		return func() { cleanupCalls++ }, nil
	}

	err := Start(context.Background(), Options{
		RepoRoot: t.TempDir(),
		Tag:      "test-db",
		DBPort:   "14000",
		PDPort:   "12379",
		KVPort:   "30160",
	})
	if err != nil {
		t.Fatal(err)
	}
	if cleanupCalls != 0 {
		t.Fatalf("detached start ran cleanup %d time(s)", cleanupCalls)
	}
}

func TestStartRejectsUltraDetachedLifecycle(t *testing.T) {
	t.Setenv(features.EnvName, string(features.ULTRA))
	oldLoad := loadFeatureOptions
	t.Cleanup(func() { loadFeatureOptions = oldLoad })
	loadFeatureOptions = func(repoRoot string) (features.Options, error) {
		return features.Options{
			RepoRoot: repoRoot,
			Features: features.Set{features.ULTRA: {}},
		}, nil
	}
	err := Start(context.Background(), Options{
		RepoRoot: t.TempDir(),
		Tag:      "test-db",
		DBPort:   "14000",
	})
	if err == nil || !strings.Contains(err.Error(), "does not support ULTRA") {
		t.Fatalf("Start() error = %v, want explicit ULTRA lifecycle error", err)
	}
}

func TestRunRemainsAttachedAndCleansUpOnCancellation(t *testing.T) {
	oldStart := startPlayground
	t.Cleanup(func() { startPlayground = oldStart })

	started := make(chan struct{})
	cleaned := make(chan struct{})
	startPlayground = func(_ context.Context, _ tidb.StartPlaygroundOptions) (func(), error) {
		close(started)
		return func() { close(cleaned) }, nil
	}

	ctx, cancel := context.WithCancel(context.Background())
	done := make(chan error, 1)
	go func() {
		done <- Run(ctx, Options{
			RepoRoot: t.TempDir(),
			Tag:      "attached-db",
			DBPort:   "14000",
			PDPort:   "12379",
			KVPort:   "30160",
		})
	}()

	<-started
	select {
	case err := <-done:
		t.Fatalf("attached Run returned before cancellation: %v", err)
	default:
	}
	cancel()
	if err := <-done; err != nil {
		t.Fatal(err)
	}
	select {
	case <-cleaned:
	default:
		t.Fatal("attached Run did not invoke cleanup")
	}
}

func setEnvs(t *testing.T, kv map[string]string) {
	t.Helper()
	for k, v := range kv {
		t.Setenv(k, v)
	}
}

func TestResolveDefaults(t *testing.T) {
	for _, k := range []string{
		"AGENTTEAM_DEV_TIDB_TAG", "AGENTTEAM_DEV_TIDB_DB_PORT", "AGENTTEAM_DEV_TIDB_PD_PORT",
		"AGENTTEAM_DEV_TIDB_KV_PORT", "AGENTTEAM_DEV_TIDB_VERSION", "AGENTTEAM_DEV_TIDB_DATABASE",
		"AGENTTEAM_TIDB_PLAYGROUND_VERSION", "RETAIN_DATA",
	} {
		t.Setenv(k, "")
	}
	got := Resolve(Options{})
	if got.DBPort != "4000" {
		t.Errorf("DBPort = %q, want 4000", got.DBPort)
	}
	if got.PDPort != "2379" {
		t.Errorf("PDPort = %q, want 2379", got.PDPort)
	}
	if got.KVPort != "20160" {
		t.Errorf("KVPort = %q, want 20160", got.KVPort)
	}
	if got.Version != "v8.5.5" {
		t.Errorf("Version = %q, want v8.5.5", got.Version)
	}
	if got.Database != "agentteam_core" {
		t.Errorf("Database = %q, want agentteam_core", got.Database)
	}
}

func TestResolveEnvOverrides(t *testing.T) {
	setEnvs(t, map[string]string{
		"AGENTTEAM_DEV_TIDB_TAG":      "custom-tag",
		"AGENTTEAM_DEV_TIDB_DB_PORT":  "14000",
		"AGENTTEAM_DEV_TIDB_PD_PORT":  "12379",
		"AGENTTEAM_DEV_TIDB_KV_PORT":  "30160",
		"AGENTTEAM_DEV_TIDB_VERSION":  "v8.5.0",
		"AGENTTEAM_DEV_TIDB_DATABASE": "custom_db",
	})
	got := Resolve(Options{})
	if got.Tag != "custom-tag" {
		t.Errorf("Tag = %q", got.Tag)
	}
	if got.DBPort != "14000" {
		t.Errorf("DBPort = %q", got.DBPort)
	}
	if got.PDPort != "12379" {
		t.Errorf("PDPort = %q", got.PDPort)
	}
	if got.KVPort != "30160" {
		t.Errorf("KVPort = %q", got.KVPort)
	}
	if got.Version != "v8.5.0" {
		t.Errorf("Version = %q", got.Version)
	}
	if got.Database != "custom_db" {
		t.Errorf("Database = %q", got.Database)
	}
}

func TestResolveExplicitOptsTakePrecedence(t *testing.T) {
	t.Setenv("AGENTTEAM_DEV_TIDB_DB_PORT", "14000")
	got := Resolve(Options{DBPort: "9999"})
	if got.DBPort != "9999" {
		t.Errorf("DBPort = %q, want explicit 9999", got.DBPort)
	}
}

func TestResolveLegacyVersionEnvFallback(t *testing.T) {
	for _, k := range []string{"AGENTTEAM_DEV_TIDB_VERSION", "RETAIN_DATA"} {
		t.Setenv(k, "")
	}
	t.Setenv("AGENTTEAM_TIDB_PLAYGROUND_VERSION", "v7.1.0")
	got := Resolve(Options{})
	if got.Version != "v7.1.0" {
		t.Errorf("Version = %q, want v7.1.0 from legacy env", got.Version)
	}
}

// fallback when no AGENTTEAM_DEV_TIDB_TAG is set: features.LocalTiDBTag() based on RETAIN_DATA.
func TestResolveTagFallbackUsesRetainData(t *testing.T) {
	t.Setenv("AGENTTEAM_DEV_TIDB_TAG", "")
	t.Setenv("RETAIN_DATA", "myfeature")
	got := Resolve(Options{})
	if got.Tag == "" {
		t.Error("Tag is empty, expected LocalTiDBTag-derived value")
	}
}
