package premium

import (
	"strings"
	"testing"

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

const testVersion = "v8.5.4"

func templatesByConfigType(t *testing.T) map[string]templategen.Template {
	t.Helper()
	all, err := Generator.GenerateAll(VersionFor(testVersion))
	require.NoError(t, err)
	out := make(map[string]templategen.Template, len(all))
	for _, tmpl := range all {
		out[tmpl.ConfigType] = tmpl
	}
	require.Len(t, out, len(Generator.ConfigTypes()), "duplicate config type in premium specs")
	return out
}

func TestNamesVersionAndCreateClusterType(t *testing.T) {
	all, err := Generator.GenerateAll(VersionFor(testVersion))
	require.NoError(t, err)
	for _, tmpl := range all {
		assert.Equal(t, pb.ClusterType_SHARED_POOL, tmpl.CreateClusterType, tmpl.ConfigType)
		assert.NotEmpty(t, tmpl.Version, "template %s has no version", tmpl.ConfigType)
		assert.NotEmpty(t, tmpl.Bindings, "template %s has no bindings", tmpl.ConfigType)
		assert.True(t, strings.HasSuffix(tmpl.Name, " "+tmpl.Version), "name must end with its version: %s", tmpl.Name)
		assert.NotNil(t, tmpl.Data, "template %s has nil data", tmpl.ConfigType)
	}
}

// TestGoldenValues spot-checks flattened values across the value kinds (bool,
// int, float, string) to catch transcription errors in the TOML literals.
func TestGoldenValues(t *testing.T) {
	tmpls := templatesByConfigType(t)

	pd := tmpls[pb.SHARED_POOL_CONFIG_PD].Data
	assert.NotContains(t, pd, "replication.enable-placement-rules")
	assert.Equal(t, float64(100), pd["schedule.max-merge-region-size"].GetValue().GetNumberValue())
	assert.InDelta(t, 0.00048828, pd["controller.request-unit.write-cost-per-byte"].GetValue().GetNumberValue(), 1e-12)

	tikv := tmpls[pb.SHARED_POOL_CONFIG_TIKV].Data
	assert.Equal(t, float64(2), tikv["storage.api-version"].GetValue().GetNumberValue())
	assert.Equal(t, "30MB", tikv["backup.s3-multi-part-size"].GetValue().GetStringValue())

	worker := tmpls[pb.SHARED_POOL_CONFIG_TIDB_WORKER].Data
	assert.Equal(t, "dxf_service", worker["instance.tidb_service_scope"].GetValue().GetStringValue())
	assert.True(t, worker["security.enable-sem"].GetValue().GetBoolValue())
}

// TestBindingsAndVersions verifies the non-trivial cases: templates shared across
// cluster types, and tiproxy carrying its own (non-cluster) version.
func TestBindingsAndVersions(t *testing.T) {
	tmpls := templatesByConfigType(t)

	// tidb resource-pool template drives both SHARED_POOL and LOGICAL_CLUSTER.
	assert.ElementsMatch(t, []templategen.Binding{
		{ClusterType: pb.ClusterType_SHARED_POOL, ConfigType: pb.SHARED_POOL_CONFIG_TIDB},
		{ClusterType: pb.ClusterType_LOGICAL_CLUSTER, ConfigType: pb.LOGICAL_CLUSTER_CONFIG_TIDB},
	}, tmpls[pb.SHARED_POOL_CONFIG_TIDB].Bindings)

	// dumpling: created under the SHARED_POOL dumpling config type, bound only
	// for LOGICAL_CLUSTER.
	dumpling := tmpls[pb.LOGICAL_CLUSTER_COMPONENT_DUMPLING]
	assert.Equal(t, pb.LOGICAL_CLUSTER_COMPONENT_DUMPLING, dumpling.ConfigType)
	assert.Equal(t, []templategen.Binding{
		{ClusterType: pb.ClusterType_LOGICAL_CLUSTER, ConfigType: pb.LOGICAL_CLUSTER_CONFIG_DUMPLING},
	}, dumpling.Bindings)

	// tiproxy carries its own component version, independent of the cluster version.
	assert.Equal(t, TiProxyVersion, tmpls[pb.SHARED_POOL_CONFIG_TIPROXY].Version)
}

func TestTiDBStatementSummaryPersistentDefaults(t *testing.T) {
	tidb, err := Generator.Generate(pb.SHARED_POOL_CONFIG_TIDB, testVersion)
	require.NoError(t, err)
	assert.NotContains(t, tidb.Data, "instance.tidb_stmt_summary_enable_persistent")
	assert.NotContains(t, tidb.Data, "instance.tidb_stmt_summary_filename")

	tidb263, err := Generator.Generate(pb.SHARED_POOL_CONFIG_TIDB, "v26.3.2")
	require.NoError(t, err)
	assert.True(t, tidb263.Data["instance.tidb_stmt_summary_enable_persistent"].GetValue().GetBoolValue())
	assert.Equal(t, "/var/log/auditlog/tidb-statements.log", tidb263.Data["instance.tidb_stmt_summary_filename"].GetValue().GetStringValue())
	assert.NotContains(t, tidb263.Data, "instance.tidb_stmt_summary_group_by_user")
}

func TestIsV263(t *testing.T) {
	assert.True(t, IsV263("26.3.2"))
	assert.True(t, IsV263("v26.3.2"))
	assert.False(t, IsV263("v8.5.4"))
}

func TestSEMAndBR(t *testing.T) {
	tmpls := templatesByConfigType(t)

	// SEM (JSON source) flattens into params.
	sem := tmpls[pb.SHARED_POOL_CONFIG_TIDB_SEM]
	assert.NotEmpty(t, sem.Data)

	// BR autobackup carries values and their comments.
	ab := tmpls[pb.SHARED_POOL_CONFIG_TIBR_AUTOBACKUP].Data
	assert.True(t, ab["native-br.backup-tolerate-err"].GetValue().GetBoolValue())
	assert.Equal(t, "5s", ab["native-br.backup-delay"].GetValue().GetStringValue())
	assert.NotEmpty(t, ab["native-br.backup-delay"].GetComment())

	// t3_archive has no init params: an empty (but real, bound) template.
	t3 := tmpls[pb.SHARED_POOL_CONFIG_TIBRGC_T3_ARCHIVE]
	assert.NotNil(t, t3.Data)
	assert.Empty(t, t3.Data)

	cr := tmpls[pb.SHARED_POOL_CONFIG_CLASSIC_RESTORE].Data
	assert.True(t, cr["with-sys-table"].GetValue().GetBoolValue())
	assert.False(t, cr["check-requirements"].GetValue().GetBoolValue())
	assert.False(t, cr["checksum"].GetValue().GetBoolValue())
	assert.False(t, cr["fast-load-sys-tables"].GetValue().GetBoolValue())
	assert.True(t, cr["sys-check-collation"].GetValue().GetBoolValue())
	assert.Equal(t, float64(4), cr["tikv-max-restore-concurrency"].GetValue().GetNumberValue())
	assert.Equal(t, float64(50), cr["ratelimit"].GetValue().GetNumberValue())
	assert.Equal(t, float64(2359296000), cr["merge-region-size-bytes"].GetValue().GetNumberValue())
	assert.Equal(t, float64(225000000), cr["merge-region-key-count"].GetValue().GetNumberValue())
	assert.NotContains(t, cr, "send-credentials-to-tikv")
}

// TestV263Deltas verifies the tikv / coprocessor_worker 26.3 additions are
// present on the 26.3 line and absent on the 8.5.x line.
func TestV263Deltas(t *testing.T) {
	tikv263, err := Generator.Generate(pb.SHARED_POOL_CONFIG_TIKV, "v26.3.2")
	require.NoError(t, err)
	assert.True(t, tikv263.Data["pd.report-store-size-with-keyspace-name"].GetValue().GetBoolValue())
	assert.True(t, tikv263.Data["pd.load-all-keyspaces-on-start"].GetValue().GetBoolValue())
	assert.False(t, tikv263.Data["storage.flow-control.enable"].GetValue().GetBoolValue())
	assert.True(t, tikv263.Data["rfengine.enable-compact-rate-limiter"].GetValue().GetBoolValue())

	tikv85, err := Generator.Generate(pb.SHARED_POOL_CONFIG_TIKV, "v8.5.4")
	require.NoError(t, err)
	for _, k := range []string{
		"pd.report-store-size-with-keyspace-name",
		"pd.load-all-keyspaces-on-start",
		"storage.flow-control.enable",
		"rfengine.enable-compact-rate-limiter",
	} {
		assert.NotContains(t, tikv85.Data, k)
	}

	copr263, err := Generator.Generate(pb.SHARED_POOL_CONFIG_COPROCESSOR_WORKER, "26.3.2")
	require.NoError(t, err)
	assert.Equal(t, "80%", copr263.Data["ia.disk-cap"].GetValue().GetStringValue())
	assert.Equal(t, "10%", copr263.Data["ia.mem-cap"].GetValue().GetStringValue())
	assert.Equal(t, float64(4194304), copr263.Data["ia.segment-size"].GetValue().GetNumberValue())

	copr85, err := Generator.Generate(pb.SHARED_POOL_CONFIG_COPROCESSOR_WORKER, "v8.5.4")
	require.NoError(t, err)
	for _, k := range []string{"ia.disk-cap", "ia.mem-cap", "ia.segment-size"} {
		assert.NotContains(t, copr85.Data, k)
	}
}

func TestTiKVTemplateOmitsKernelManagedDefaults(t *testing.T) {
	for _, version := range []string{"v8.5.4", "v26.3.2"} {
		tikv, err := Generator.Generate(pb.SHARED_POOL_CONFIG_TIKV, version)
		require.NoError(t, err)
		for _, k := range []string{
			"backup.num-threads",
			"raftstore.aux-worker-count",
			"server.grpc-concurrency",
			"rfengine.compact-wal-sync-concurrency",
			"rfengine.compaction-concurrency",
		} {
			assert.NotContains(t, tikv.Data, k, version)
		}
	}
}
