package config

import (
	"os"
	"testing"

	"github.com/imdario/mergo"
	. "github.com/onsi/gomega"
	"github.com/spf13/viper"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
)

func TestAPIConfig(t *testing.T) {
	g := NewGomegaWithT(t)
	g.Expect(InitConfig("a.yaml", true)).To(HaveOccurred())

	testCases := []struct {
		str         string
		expectedErr bool
	}{
		{
			str:         ``,
			expectedErr: false,
		}, {
			str:         `-api`,
			expectedErr: true,
		}, {
			str: `
server:
  grpcPort: port1
`,
			expectedErr: true,
		}, {
			str: `
server:
  logLevel: 100
`,
			expectedErr: false,
		}, {
			str: `
server:
  regionSvc: 100
`,
			expectedErr: false,
		},
	}

	for _, tc := range testCases {
		f, err := os.CreateTemp(t.TempDir(), "config-test*.yaml")
		g.Expect(err).To(Succeed())

		_, err = f.WriteString(tc.str)
		g.Expect(err).To(Succeed())

		if tc.expectedErr {
			g.Expect(InitConfig(f.Name(), false)).To(HaveOccurred())
		} else {
			g.Expect(InitConfig(f.Name(), false)).To(Succeed())
		}
	}
}

type TestMapIn struct {
	A string
	B string
	C string
}

type TestMap struct {
	Test TestMapIn
}

func TestMergoConfig(t *testing.T) {
	g := NewGomegaWithT(t)
	a := TestMap{
		TestMapIn{
			A: "a",
			C: "c1",
		},
	}
	b := TestMap{
		TestMapIn{
			B: "b",
			C: "c2",
		},
	}

	err := mergo.Merge(&a, b, mergo.WithOverride)
	g.Expect(err).To(Succeed())
	g.Expect(a.Test.A).To(Equal("a"))
	g.Expect(a.Test.B).To(Equal("b"))
	g.Expect(a.Test.C).To(Equal("c2"))
}

func TestNormalizeConfigDefaultsSetsNewArchScheduling(t *testing.T) {
	g := NewGomegaWithT(t)
	cfg := &CentralConfig{}

	g.Expect(NormalizeConfigDefaults(cfg)).To(Succeed())

	g.Expect(cfg.CDCConfig.NewArch.NodeSelector).To(Equal(DefaultNewArchNodeSelector))
	g.Expect(cfg.CDCConfig.NewArch.Tolerations).To(Equal(DefaultNewArchTolerations))
	g.Expect(cfg.CDCConfig.NewArch.ShareScope).To(Equal("global"))
}

func TestMaxSpendLimitForOrg(t *testing.T) {
	g := NewGomegaWithT(t)
	cfg := BizConfig{}
	g.Expect(cfg.MaxSpendLimitForOrg(1)).To(Equal(constant.MaxSpendLimitValue))

	cfg.SpendLimitMaxOverrideTenants = map[uint64]uint32{1: 50000000, 2: 0}
	g.Expect(cfg.MaxSpendLimitForOrg(1)).To(Equal(uint32(50000000)))
	g.Expect(cfg.MaxSpendLimitForOrg(2)).To(Equal(constant.MaxSpendLimitValue))
	g.Expect(cfg.MaxSpendLimitForOrg(3)).To(Equal(constant.MaxSpendLimitValue))
}

func TestNormalizeConfigDefaultsValidatesNewArchShareRules(t *testing.T) {
	g := NewGomegaWithT(t)
	cfg := &CentralConfig{
		CDCConfig: CDCConfig{NewArch: CDCNewArchConfig{
			ShareScope: "cluster",
			ShareRules: []CDCNewArchShareRule{{
				ShareScope: "changefeed",
				ClusterIDs: []string{"cluster-1"},
			}},
		}},
	}

	g.Expect(NormalizeConfigDefaults(cfg)).To(Succeed())
	g.Expect(cfg.CDCConfig.NewArch.ShareScope).To(Equal("cluster"))
	g.Expect(cfg.CDCConfig.NewArch.ShareRules[0].ShareScope).To(Equal("changefeed"))
}

func TestNormalizeConfigDefaultsRejectsInvalidNewArchShareRule(t *testing.T) {
	g := NewGomegaWithT(t)
	cfg := &CentralConfig{
		CDCConfig: CDCConfig{NewArch: CDCNewArchConfig{
			ShareRules: []CDCNewArchShareRule{{ShareScope: "project"}},
		}},
	}

	g.Expect(NormalizeConfigDefaults(cfg)).To(HaveOccurred())
}

func TestNormalizeConfigDefaultsValidatesNewArchEnabledClusters(t *testing.T) {
	g := NewGomegaWithT(t)

	valid := &CentralConfig{
		CDCConfig: CDCConfig{NewArch: CDCNewArchConfig{
			EnabledClusters: []string{"123", "456"},
		}},
	}
	g.Expect(NormalizeConfigDefaults(valid)).To(Succeed())

	invalid := &CentralConfig{
		CDCConfig: CDCConfig{NewArch: CDCNewArchConfig{
			EnabledClusters: []string{"123", "not-a-cluster"},
		}},
	}
	wantErr := "invalid cdc.newArch.enabledClusters[1] \"not-a-cluster\": " +
		"strconv.ParseUint: parsing \"not-a-cluster\": invalid syntax"
	g.Expect(NormalizeConfigDefaults(invalid)).To(MatchError(wantErr))
}

func TestNormalizeConfigDefaultsKeepsExplicitNewArchScheduling(t *testing.T) {
	g := NewGomegaWithT(t)
	cfg := &CentralConfig{
		CDCConfig: CDCConfig{NewArch: CDCNewArchConfig{
			NodeSelector: map[string]string{"node": "custom"},
			Tolerations: []NewArchToleration{{
				Key:      "node",
				Operator: "Equal",
				Value:    "custom",
			}},
		}},
	}

	g.Expect(NormalizeConfigDefaults(cfg)).To(Succeed())

	g.Expect(cfg.CDCConfig.NewArch.NodeSelector).To(Equal(map[string]string{"node": "custom"}))
	g.Expect(cfg.CDCConfig.NewArch.Tolerations).To(Equal([]NewArchToleration{{
		Key:      "node",
		Operator: "Equal",
		Value:    "custom",
		Effect:   "NoSchedule",
	}}))
}

func TestNormalizeConfigSetupSupportsDottedNodeSelectorKeys(t *testing.T) {
	g := NewGomegaWithT(t)
	viper.Reset()
	t.Cleanup(viper.Reset)

	f, err := os.CreateTemp(t.TempDir(), "config-test*.yaml")
	g.Expect(err).To(Succeed())

	_, err = f.WriteString(`
cdc:
  newArch:
    nodeSelector:
      serverless.tidbcloud.com/node: adhoc
      adhoc-cpu: "16"
      adhoc-memory: "64"
      serverless.tidbcloud.com/capacity-type: ON_DEMAND
`)
	g.Expect(err).To(Succeed())

	viper.SetConfigFile(f.Name())
	g.Expect(viper.ReadInConfig()).To(Succeed())

	cfg := &CentralConfig{}
	g.Expect(NormalizeConfigSetup(cfg)).To(Succeed())
	g.Expect(cfg.CDCConfig.NewArch.NodeSelector).To(Equal(map[string]string{
		"serverless.tidbcloud.com/node":          "adhoc",
		"adhoc-cpu":                              "16",
		"adhoc-memory":                           "64",
		"serverless.tidbcloud.com/capacity-type": "ON_DEMAND",
	}))
}

func TestHasS3CompatibleStashing(t *testing.T) {
	g := NewGomegaWithT(t)
	g.Expect(HasS3CompatibleStashing(ProviderAWS)).To(BeTrue())
	g.Expect(HasS3CompatibleStashing(ProviderAliCloud)).To(BeTrue())
	g.Expect(HasS3CompatibleStashing(ProviderGCP)).To(BeFalse())
	g.Expect(HasS3CompatibleStashing(ProviderAzure)).To(BeFalse())
}
