package v1

import (
	"testing"

	"github.com/onsi/gomega"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/util/validation/field"
	"k8s.io/utils/pointer"
)

func TestClusterWebhookValidateSqlEndpoints(t *testing.T) {
	reader := &fakeReader{}
	webhookClient.direct = reader
	webhookClient.cached = reader

	cases := map[string]struct {
		setup    func(c *SqlEndpoints)
		expectFn func(g *gomega.GomegaWithT, err error)
	}{
		"Reserved public endpoint port": {
			setup: func(ep *SqlEndpoints) {
				ep.Public = Service{Enabled: pointer.BoolPtr(true), Port: pointer.Int32Ptr(10080)}
			},
			expectFn: func(g *gomega.GomegaWithT, err error) {
				g.Expect(err).To(gomega.HaveOccurred())
				g.Expect(err.Error()).Should(gomega.ContainSubstring("port is reserved"))
			},
		},
		"Reserved private endpoint port": {
			setup: func(ep *SqlEndpoints) {
				ep.Private = &Service{Enabled: pointer.BoolPtr(true), Port: pointer.Int32Ptr(10080)}
			},
			expectFn: func(g *gomega.GomegaWithT, err error) {
				g.Expect(err).To(gomega.HaveOccurred())
				g.Expect(err.Error()).Should(gomega.ContainSubstring("port is reserved"))
			},
		},
	}

	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := gomega.NewGomegaWithT(t)
			c := &Cluster{}
			ep := &SqlEndpoints{}

			tc.setup(ep)
			err := c.validateSqlEndpoints(field.NewPath("spec.endpoints"), *ep)
			tc.expectFn(g, err.ToAggregate())
		})
	}

}

func TestClusterWebhookValidateUpdate(t *testing.T) {
	expectSucceed := func(g *gomega.GomegaWithT, err error) {
		g.Expect(err).Should(gomega.Succeed())
	}
	reader := &fakeReader{}
	webhookClient.direct = reader
	webhookClient.cached = reader

	truePtr := pointer.BoolPtr(true)
	flasePtr := pointer.BoolPtr(false)
	port4000Ptr := pointer.Int32Ptr(4000)
	port5000Ptr := pointer.Int32Ptr(5000)

	cases := map[string]struct {
		setCluster func(old *Cluster, cur *Cluster)
		expectFn   func(g *gomega.GomegaWithT, err error)
	}{
		"Change network": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.NetworkName = "old-network"
				cur.Spec.NetworkName = "new-network"
			},
			expectFn: func(g *gomega.GomegaWithT, err error) {
				g.Expect(err).Should(gomega.HaveOccurred())
				g.Expect(err.Error()).Should(gomega.ContainSubstring("spec.network"))
				g.Expect(err.Error()).Should(gomega.ContainSubstring("field is immutable"))
			},
		},
		"Disable public endpoint": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.SqlEndpoints.Public = Service{Enabled: truePtr, Port: port4000Ptr}
				cur.Spec.SqlEndpoints.Public = Service{Enabled: flasePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Disable private endpoint": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port4000Ptr}
				cur.Spec.SqlEndpoints.Private = &Service{Enabled: flasePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Enable public endpoint": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.SqlEndpoints.Public = Service{Enabled: flasePtr, Port: port4000Ptr}
				cur.Spec.SqlEndpoints.Public = Service{Enabled: truePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Enable private endpoint": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.SqlEndpoints.Private = &Service{Enabled: flasePtr, Port: port4000Ptr}
				cur.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Not change public endpoint port": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.SqlEndpoints.Public = Service{Enabled: truePtr, Port: port5000Ptr}
				cur.Spec.SqlEndpoints.Public = Service{Enabled: truePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Not change private endpoint port": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port5000Ptr}
				cur.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Change public endpoint port": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.SqlEndpoints.Public = Service{Enabled: truePtr, Port: port4000Ptr}
				cur.Spec.SqlEndpoints.Public = Service{Enabled: truePtr, Port: port5000Ptr}
			},
			expectFn: func(g *gomega.GomegaWithT, err error) {
				g.Expect(err).Should(gomega.HaveOccurred())
				g.Expect(err.Error()).Should(gomega.ContainSubstring("spec.sqlEndpoints.public.port"))
				g.Expect(err.Error()).Should(gomega.ContainSubstring("field is immutable"))
			},
		},
		"Change private endpoint port": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port4000Ptr}
				cur.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port5000Ptr}
			},
			expectFn: func(g *gomega.GomegaWithT, err error) {
				g.Expect(err).Should(gomega.HaveOccurred())
				g.Expect(err.Error()).Should(gomega.ContainSubstring("spec.sqlEndpoints.private.port"))
				g.Expect(err.Error()).Should(gomega.ContainSubstring("field is immutable"))
			},
		},
		"Scale local storage of TiKV": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.Cluster.TiKV = TiKV{
					UseLocalStorage: truePtr,
					StorageSize:     resource.MustParse("1Gi"),
				}
				cur.Spec.Cluster.TiKV = TiKV{
					UseLocalStorage: truePtr,
					StorageSize:     resource.MustParse("2Gi"),
				}
			},
			expectFn: func(g *gomega.GomegaWithT, err error) {
				g.Expect(err).Should(gomega.HaveOccurred())
				g.Expect(err.Error()).Should(gomega.ContainSubstring("cluster.tikv.storageSize"))
				g.Expect(err.Error()).Should(gomega.ContainSubstring("size of local storage is immutable"))
			},
		},
		"Scale local storage of TiFlash": {
			setCluster: func(old, cur *Cluster) {
				old.Spec.Cluster.TiFlash = TiFlash{
					UseLocalStorage: truePtr,
					StorageSize:     resource.MustParse("1Gi"),
				}
				cur.Spec.Cluster.TiFlash = TiFlash{
					UseLocalStorage: truePtr,
					StorageSize:     resource.MustParse("2Gi"),
				}
			},
			expectFn: func(g *gomega.GomegaWithT, err error) {
				g.Expect(err).Should(gomega.HaveOccurred())
				g.Expect(err.Error()).Should(gomega.ContainSubstring("cluster.tiflash.storageSize"))
				g.Expect(err.Error()).Should(gomega.ContainSubstring("size of local storage is immutable"))
			},
		},
	}

	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := gomega.NewGomegaWithT(t)
			old := &Cluster{}
			old.Name = "webhook-ut"
			old.Namespace = "default"
			old.Spec.NetworkName = "aws-us-west-2"

			cur := old.DeepCopy()
			if tc.setCluster != nil {
				tc.setCluster(old, cur)
			}
			err := cur.ValidateUpdate(old)
			tc.expectFn(g, err)
		})
	}
}
