package grpc_service

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/config-center/internal/db"
	"github.com/tidbcloud/config-center/internal/service"
	pb "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/structpb"
	"k8s.io/utils/ptr"
)

func TestCreateGetClusterConfig(t *testing.T) {
	assert := assert.New(t)
	mockDB := db.NewMockDB()
	c := &ConfigCenterService{
		DB: mockDB,
		UnifiedService: &service.ConfigCenterService{
			DB: mockDB,
		},
	}

	f, err := c.CreateClusterConfigurationFile(context.TODO(), &pb.CreateClusterConfigurationFileRequest{
		ClusterType: pb.ClusterType_DEDICATED_TIDB,
		ClusterId:   "tidb123123",
		ConfigurationFile: &pb.ConfigurationFile{
			ConfigurationFilename: "tidb",
			Format:                pb.Format_TOML,
			TemplateId:            0,
			CustomParams: map[string]*pb.ParamItem{
				"log.level": {
					Value:   structpb.NewStringValue("info"),
					Comment: "test",
				},
				"int.value": {
					Value: structpb.NewNumberValue(123),
				},
			},
		},
	})

	assert.Nil(err)
	assert.NotNil(f)
	assert.Equal("tidb", f.ConfigurationFilename)
	assert.Equal(2, len(f.FullParams))
	assert.Equal(`
[int]
value = 123

[log]
level = "info"
`, f.Text)

	f2, err := c.GetClusterConfigurationFile(context.TODO(), &pb.GetClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
	})

	assert.Nil(err)
	assert.NotNil(f2)
	assert.Equal(f.ConfigurationFilename, f2.ConfigurationFilename)
	assert.Equal(f.Text, f2.Text)
}

func TestUpdateClusterConfig(t *testing.T) {
	assert := assert.New(t)
	mockDB := db.NewMockDB()
	c := &ConfigCenterService{
		DB: mockDB,
		UnifiedService: &service.ConfigCenterService{
			DB: mockDB,
		},
	}

	template, err := c.CreateConfigurationTemplate(context.TODO(), &pb.CreateConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: "tidb",
		TemplateName:      "template name",
		Data: map[string]*pb.ParamItem{
			"test": {
				Value:   &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "test"}},
				Comment: "test",
			},
		},
	})

	assert.Nil(err)
	// Check if the template is created
	assert.NotNil(t, template)
	assert.Equal("template name", template.TemplateName)
	assert.Equal("tidb", template.ConfigurationType)

	// test cannot update a non-existing file by default
	format := pb.Format_TOML
	f, err := c.UpdateClusterConfigurationFile(context.TODO(), &pb.UpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		Format:                &format,
		TemplateId:            ptr.To(int64(0)),
		CustomParams: map[string]*pb.ParamItem{
			"log.level": {
				Value:   structpb.NewStringValue("info"),
				Comment: "test",
			},
			"int.value": {
				Value: structpb.NewNumberValue(123),
			},
		},
		// AllowMissing: false, // default is false
	})
	assert.NotNil(err)

	// test allowing_missing=true validate_only=true to preview file
	f, err = c.UpdateClusterConfigurationFile(context.TODO(), &pb.UpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		Format:                &format,
		TemplateId:            ptr.To(int64(0)),
		CustomParams: map[string]*pb.ParamItem{
			"log.level": {
				Value:   structpb.NewStringValue("info"),
				Comment: "test",
			},
			"int.value": {
				Value: structpb.NewNumberValue(123),
			},
		},
		AllowMissing: true,
		ValidateOnly: true,
	})
	assert.Nil(err)
	assert.NotNil(f)
	assert.Equal("tidb", f.ConfigurationFilename)
	assert.Equal(`
[int]
value = 123

[log]
level = "info"
`, f.Text)
	_, err = c.GetClusterConfigurationFile(context.TODO(), &pb.GetClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
	})
	assert.Equal(codes.NotFound, status.Code(err))

	// test allowing_missing=true to create a new file
	f, err = c.UpdateClusterConfigurationFile(context.TODO(), &pb.UpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		Format:                &format,
		TemplateId:            ptr.To(int64(0)),
		CustomParams: map[string]*pb.ParamItem{
			"log.level": {
				Value:   structpb.NewStringValue("info"),
				Comment: "test",
			},
			"int.value": {
				Value: structpb.NewNumberValue(123),
			},
		},
		AllowMissing: true,
	})

	assert.Nil(err)
	assert.NotNil(f)
	assert.Equal("tidb", f.ConfigurationFilename)
	assert.Equal(`
[int]
value = 123

[log]
level = "info"
`, f.Text)

	f2, err := c.UpdateClusterConfigurationFile(context.TODO(), &pb.UpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		CustomParams: map[string]*pb.ParamItem{
			"log.level": {
				Value:   structpb.NewStringValue("debug"),
				Comment: "test",
			},
			"int.v": {
				Value: structpb.NewNumberValue(123),
			},
		},
	})

	assert.Nil(err)
	assert.NotNil(f2)
	assert.Equal("tidb", f2.ConfigurationFilename)
	assert.Equal(`
[int]
v = 123

[log]
level = "debug"
`, f2.Text)

	// test file is unchange after preview change
	f3, err := c.UpdateClusterConfigurationFile(context.TODO(), &pb.UpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		TemplateId:            &template.TemplateId,
		CustomParams: map[string]*pb.ParamItem{
			"log.level": {
				Value:   structpb.NewStringValue("debug"),
				Comment: "test",
			},
			"int.v": {
				Value: structpb.NewNumberValue(123),
			},
			// do not know why, it just work
			"profiles.default.no-value-for-migrate": {
				Value: nil,
			},
		},
		ValidateOnly: true,
	})
	assert.Nil(err)
	assert.NotNil(f3)
	assert.Equal(`test = "test"

[int]
v = 123

[log]
level = "debug"

[profiles]

[profiles.default]
`, f3.Text)
	f4, err := c.GetClusterConfigurationFile(context.TODO(), &pb.GetClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
	})
	assert.Nil(err)
	assert.Equal(f2.Text, f4.Text)

	f5, err := c.UpdateClusterConfigurationFile(context.TODO(), &pb.UpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		TemplateId:            &template.TemplateId,
		CustomParams: map[string]*pb.ParamItem{
			"log.level": {
				Value:   structpb.NewStringValue("debug"),
				Comment: "test",
			},
			"int.v": {
				Value: structpb.NewNumberValue(123),
			},
		},
	})

	assert.Nil(err)
	assert.NotNil(f5)
	assert.Equal(`test = "test"

[int]
v = 123

[log]
level = "debug"
`, f5.Text)

}

func TestAutoUpdateClusterConfig(t *testing.T) {
	assert := assert.New(t)
	mockDB := db.NewMockDB()
	c := &ConfigCenterService{
		DB: mockDB,
		UnifiedService: &service.ConfigCenterService{
			DB: mockDB,
		},
	}

	template1, err := c.CreateConfigurationTemplate(context.TODO(), &pb.CreateConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: "tidb",
		TemplateName:      "template name",
		Data: map[string]*pb.ParamItem{
			"test": {
				Value:   &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "test1"}},
				Comment: "test1",
			},
		},
	})
	assert.Nil(err)
	assert.NotNil(t, template1)

	template2, err := c.CreateConfigurationTemplate(context.TODO(), &pb.CreateConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: "tidb",
		TemplateName:      "template name",
		Data: map[string]*pb.ParamItem{
			"test": {
				Value:   &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "test2"}},
				Comment: "test2",
			},
		},
	})
	assert.Nil(err)
	assert.NotNil(t, template2)

	_, err = c.UpdateDefaultConfigurationTemplate(context.TODO(), &pb.UpdateDefaultConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: "tidb",
		Version:           "v7.5.0",
		TemplateId:        template1.TemplateId,
	})
	assert.Nil(err)

	_, err = c.UpdateDefaultConfigurationTemplate(context.TODO(), &pb.UpdateDefaultConfigurationTemplateRequest{
		ClusterType:       pb.ClusterType_DEDICATED_TIDB,
		ConfigurationType: "tidb",
		Version:           "v7.5.1",
		TemplateId:        template2.TemplateId,
	})
	assert.Nil(err)

	// test allowing_missing=true to create a new file
	format := pb.Format_TOML
	f, err := c.AutoUpdateClusterConfigurationFile(context.TODO(), &pb.AutoUpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		Format:                &format,
		Version:               "v7.5.0",
		CloudProvider:         "aws",
		NodeInstance:          "non-exist",
	})

	assert.Nil(err)
	assert.NotNil(f)
	assert.Equal("tidb", f.ConfigurationFilename)
	assert.Equal(`test = "test1"
`, f.Text)

	f2, err := c.UpdateClusterConfigurationFile(context.TODO(), &pb.UpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		CustomParams: map[string]*pb.ParamItem{
			"log.level": {
				Value:   structpb.NewStringValue("debug"),
				Comment: "test",
			},
			"int.v": {
				Value: structpb.NewNumberValue(123),
			},
		},
	})

	assert.Nil(err)
	assert.NotNil(f2)
	assert.Equal("tidb", f2.ConfigurationFilename)
	assert.Equal(`test = "test1"

[int]
v = 123

[log]
level = "debug"
`, f2.Text)

	_, err = c.UpdateComponentImage(context.TODO(), &pb.UpdateComponentImageRequest{
		ClusterType:      pb.ClusterType_DEDICATED_TIDB,
		ComponentName:    "tidb",
		ComponentVersion: "v7.5.0",
		BaseImage:        "docker.io/pingcap/tidb",
		Tag:              "v7.5.0",
	})
	assert.Nil(err)

	_, err = c.UpdateComponentImage(context.TODO(), &pb.UpdateComponentImageRequest{
		ClusterType:      pb.ClusterType_DEDICATED_TIDB,
		ComponentName:    "tidb",
		ComponentVersion: "v7.5.1",
		BaseImage:        "docker.io/pingcap/tidb",
		Tag:              "v7.5.1",
	})
	assert.Nil(err)

	component, err := c.UpdateClusterComponent(context.TODO(), &pb.UpdateClusterComponentRequest{
		ClusterType:   pb.ClusterType_DEDICATED_TIDB,
		ClusterId:     "tidb123123",
		ComponentName: "tidb",
		CloudProvider: ptr.To("aws"),
		Version:       ptr.To("v7.5.0"),
		AllowMissing:  true,
	})
	assert.Nil(err)
	assert.NotNil(component)

	f3, err := c.AutoUpdateClusterConfigurationFile(context.TODO(), &pb.AutoUpdateClusterConfigurationFileRequest{
		ClusterType:           pb.ClusterType_DEDICATED_TIDB,
		ClusterId:             "tidb123123",
		ConfigurationFilename: "tidb",
		Version:               "v7.5.1",
		CloudProvider:         "aws",
	})

	assert.Nil(err)
	assert.NotNil(f3)
	assert.Equal(`test = "test2"

[int]
v = 123

[log]
level = "debug"
`, f3.Text)

}
