package plan

import (
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/tidbcloud/observability/vm-migration/internal/schema"
)

func TestBuildMigrationLoop(t *testing.T) {
	cfg, _ := loadGoldenCompiled(t)
	build := func(ids ...string) (*schema.MigrationLoopPlan, error) {
		cfg.Tenants = ids
		return BuildMigrationLoop(cfg)
	}

	t.Run("SingleTenant", func(t *testing.T) {
		p, err := build("1001:0")
		assert.NoError(t, err)
		assert.Len(t, p.Tenants, 1)
	})

	t.Run("NameIsRealTenant", func(t *testing.T) {
		p, err := build("1001:0", "1002:0")
		assert.NoError(t, err)
		assert.Equal(t, "1001:0", p.Tenants[0].Name)
		assert.Equal(t, "1002:0", p.Tenants[1].Name)
	})

	t.Run("FilesAreSanitized", func(t *testing.T) {
		p, err := build("1001:0")
		assert.NoError(t, err)
		stem := "tenants-1001-0"
		assert.Equal(t, stem+".json", p.Tenants[0].TenantsFile)
		assert.Equal(t, stem+"-status.json", p.Tenants[0].StatusFile)
		assert.Equal(t, stem+".log", p.Tenants[0].LogFile)
	})

	t.Run("CmdsRendered", func(t *testing.T) {
		p, err := build("1001:0")
		assert.NoError(t, err)
		assert.NotEmpty(t, p.Tenants[0].Cmds, "no cmds rendered")
		manifest := p.Tenants[0].Cmds[0]
		assert.NotNil(t, manifest.Action)
		assert.Equal(t, "tenants-1001-0.json", manifest.Action.TenantsFile)
	})

	t.Run("CommandsSelectTheExactTypedAction", func(t *testing.T) {
		p, err := build("1001:0")
		assert.NoError(t, err)
		assert.NotNil(t, p.Runtime)
		wantKinds := []schema.MigrationLoopActionKind{
			schema.ActionManifest,
			schema.ActionMigrate,
			schema.ActionVerify,
		}
		for i, want := range wantKinds {
			step := p.Tenants[0].Cmds[i]
			assert.NotNil(t, step.Action)
			assert.Equal(t, want, step.Action.Kind, "step %d", i)
		}
	})

	t.Run("DuplicateRejected", func(t *testing.T) {
		_, err := build("1001:0", "1001:0")
		assert.Error(t, err)
		assert.ErrorContains(t, err, "duplicate tenant: 1001:0")
	})

	t.Run("SanitizeCollisionRejected", func(t *testing.T) {
		_, err := build("1001:0", "1001/0")
		assert.Error(t, err)
		assert.ErrorContains(t, err, "collision after sanitize")
	})

	t.Run("SortsInPlace", func(t *testing.T) {
		p, err := build("1002:0", "1001:0")
		assert.NoError(t, err)
		assert.Equal(t, "1001:0", p.Tenants[0].Name)
		assert.Equal(t, "1002:0", p.Tenants[1].Name)
	})
}

func TestSanitizeTenantName(t *testing.T) {
	f := func(in, want string) {
		t.Helper()
		assert.Equal(t, want, sanitizeTenantName(in), "in=%q", in)
	}
	f("1001:0", "1001-0")
	f("a/b", "a-b")
	f(`a\b`, "a-b")
	f("a b", "a-b")
	f("plain", "plain")
	f("", "")

	t.Run("Idempotent", func(t *testing.T) {
		once := sanitizeTenantName("1001:0")
		twice := sanitizeTenantName(once)
		assert.Equal(t, once, twice, "not idempotent")
	})
}

func TestBuildMigrationLoopRejectsMissingTenantPlaceholder(t *testing.T) {
	f := func(t *testing.T, cfg *Config) {
		t.Helper()
		_, err := BuildMigrationLoop(cfg)
		assert.ErrorContains(t, err, "must contain {tenant}")
	}
	t.Run("Vminsert", func(t *testing.T) {
		f(t, &Config{VminsertURLPattern: "http://i/insert/0/prometheus"})
	})
	t.Run("VmselectCandidate", func(t *testing.T) {
		f(t, &Config{VmselectCandidateURLPattern: "http://c/select/prometheus"})
	})
	t.Run("ValidPasses", func(t *testing.T) {
		cfg := &Config{
			VminsertURLPattern:          "http://i/insert/{tenant}/prometheus",
			VmselectURLPattern:          "http://o/{tenant}/prometheus",
			VmselectMigrationURLPattern: "http://m/{tenant}/prometheus",
			VmselectCandidateURLPattern: "http://c/{tenant}/prometheus",
		}
		_, err := BuildMigrationLoop(cfg)
		assert.NoError(t, err)
	})
}
