package templategen

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	pb "github.com/tidbcloud/config-center/proto/config_center"
)

// TestApplyDelta exercises the core engine value: a shared base plus a small
// per-version delta written in plain Go, gated on the input version.
func TestApplyDelta(t *testing.T) {
	g := New([]Spec{{
		ConfigType: "demo",
		Name:       "demo",
		Base: MustTOML(`
[server]
grpc-concurrency = 5
simplify-metrics = true

[raftstore]
aux-worker-count = 4
`),
		Apply: func(version string, data map[string]*pb.ParamItem) {
			// tolerate an optional leading "v": matches both "26.3.x" and "v26.3.x".
			if !strings.HasPrefix(strings.TrimPrefix(version, "v"), "26.3") {
				return
			}
			data["server.grpc-concurrency"] = MustParam(8)
			delete(data, "raftstore.aux-worker-count")
		},
	}})

	old, err := g.Generate("demo", "v8.5.4")
	require.NoError(t, err)
	assert.Equal(t, "v8.5.4", old.Version)
	assert.Equal(t, float64(5), old.Data["server.grpc-concurrency"].GetValue().GetNumberValue())
	assert.Contains(t, old.Data, "raftstore.aux-worker-count")

	// Both the "26.3.x" and "v26.3.x" forms must trigger the delta.
	for _, v := range []string{"26.3.0", "v26.3.0"} {
		v26, err := g.Generate("demo", v)
		require.NoError(t, err)
		assert.Equal(t, float64(8), v26.Data["server.grpc-concurrency"].GetValue().GetNumberValue(), v)
		assert.True(t, v26.Data["server.simplify-metrics"].GetValue().GetBoolValue(), v)
		assert.NotContains(t, v26.Data, "raftstore.aux-worker-count", v)
	}

	// Apply must not mutate the shared Base: regenerating the old version still
	// has the original value.
	again, err := g.Generate("demo", "v8.5.4")
	require.NoError(t, err)
	assert.Equal(t, float64(5), again.Data["server.grpc-concurrency"].GetValue().GetNumberValue())
}

func TestMustJSON(t *testing.T) {
	data := MustJSON(`{"a": {"b": 1}, "c": "x"}`)
	assert.Equal(t, float64(1), data["a.b"].GetValue().GetNumberValue())
	assert.Equal(t, "x", data["c"].GetValue().GetStringValue())
}

func TestParamComment(t *testing.T) {
	p := MustParam("5s", "pre-backup delay")
	assert.Equal(t, "5s", p.GetValue().GetStringValue())
	assert.Equal(t, "pre-backup delay", p.GetComment())
}

func TestGenerateUnknownConfigType(t *testing.T) {
	g := New([]Spec{{ConfigType: "demo", Name: "demo"}})
	_, err := g.Generate("does-not-exist", "v8.5.4")
	assert.Error(t, err)
}

// TestDefaultBinding verifies that a Spec with no explicit Bindings is bound as
// the SHARED_POOL default for its own config type, at the requested version.
func TestDefaultBinding(t *testing.T) {
	g := New([]Spec{{ConfigType: "demo", Name: "demo", Base: MustTOML("x = 1")}})
	tmpl, err := g.Generate("demo", "v8.5.4")
	require.NoError(t, err)
	assert.Equal(t, "v8.5.4", tmpl.Version)
	require.Len(t, tmpl.Bindings, 1)
	assert.Equal(t, "demo", tmpl.Bindings[0].ConfigType)
}

// TestEmptyTemplate verifies a Spec with no Base yields a non-nil, empty template
// that is still created and bound like any other.
func TestEmptyTemplate(t *testing.T) {
	g := New([]Spec{{ConfigType: "demo", Name: "demo"}})
	tmpl, err := g.Generate("demo", "v8.5.4")
	require.NoError(t, err)
	assert.NotNil(t, tmpl.Data)
	assert.Empty(t, tmpl.Data)
	assert.Equal(t, "demo v8.5.4", tmpl.Name)
	require.Len(t, tmpl.Bindings, 1)
}

func TestMissingVersionErrors(t *testing.T) {
	g := New([]Spec{{ConfigType: "demo", Name: "demo", Base: MustTOML("x = 1")}})
	_, err := g.Generate("demo", "")
	assert.Error(t, err)
}
