package v1

import (
	"testing"

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

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

	cases := map[string]struct {
		setup    func(c *BranchSqlEndpointsSpec)
		expectFn func(g *gomega.GomegaWithT, err error)
	}{
		"Reserved public endpoint port": {
			setup: func(ep *BranchSqlEndpointsSpec) {
				ep.Public = BranchPublicSpec{
					Service: Service{
						Enabled: pointer.Bool(true),
						Port:    pointer.Int32(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 *BranchSqlEndpointsSpec) {
				ep.Private = &Service{Enabled: pointer.Bool(true), Port: pointer.Int32(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 := &Branch{}
			ep := &BranchSqlEndpointsSpec{}

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

}

func TestBranchWebhookValidateUpdate(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.Bool(true)
	falsePtr := pointer.Bool(false)
	port4000Ptr := pointer.Int32(4000)
	port5000Ptr := pointer.Int32(5000)

	cases := map[string]struct {
		setBranch func(old *Branch, cur *Branch)
		expectFn  func(g *gomega.GomegaWithT, err error)
	}{
		"Disable public endpoint": {
			setBranch: func(old, cur *Branch) {
				old.Spec.SqlEndpoints.Public = BranchPublicSpec{
					Service: Service{
						Enabled: truePtr,
						Port:    port4000Ptr,
					},
				}
				cur.Spec.SqlEndpoints.Public = BranchPublicSpec{
					Service: Service{
						Enabled: falsePtr,
						Port:    port5000Ptr,
					},
				}
			},
			expectFn: expectSucceed,
		},
		"Disable private endpoint": {
			setBranch: func(old, cur *Branch) {
				old.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port4000Ptr}
				cur.Spec.SqlEndpoints.Private = &Service{Enabled: falsePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Enable public endpoint": {
			setBranch: func(old, cur *Branch) {
				old.Spec.SqlEndpoints.Public = BranchPublicSpec{
					Service: Service{
						Enabled: falsePtr,
						Port:    port4000Ptr,
					},
				}
				cur.Spec.SqlEndpoints.Public = BranchPublicSpec{
					Service: Service{
						Enabled: truePtr,
						Port:    port5000Ptr,
					},
				}
			},
			expectFn: expectSucceed,
		},
		"Enable private endpoint": {
			setBranch: func(old, cur *Branch) {
				old.Spec.SqlEndpoints.Private = &Service{Enabled: falsePtr, Port: port4000Ptr}
				cur.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Not change public endpoint port": {
			setBranch: func(old, cur *Branch) {
				old.Spec.SqlEndpoints.Public = BranchPublicSpec{
					Service: Service{
						Enabled: truePtr,
						Port:    port5000Ptr,
					},
				}
				cur.Spec.SqlEndpoints.Public = BranchPublicSpec{
					Service: Service{
						Enabled: truePtr,
						Port:    port5000Ptr,
					},
				}
			},
			expectFn: expectSucceed,
		},
		"Not change private endpoint port": {
			setBranch: func(old, cur *Branch) {
				old.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port5000Ptr}
				cur.Spec.SqlEndpoints.Private = &Service{Enabled: truePtr, Port: port5000Ptr}
			},
			expectFn: expectSucceed,
		},
		"Change public endpoint port": {
			setBranch: func(old, cur *Branch) {
				old.Spec.SqlEndpoints.Public = BranchPublicSpec{
					Service: Service{
						Enabled: truePtr,
						Port:    port4000Ptr,
					},
				}
				cur.Spec.SqlEndpoints.Public = BranchPublicSpec{
					Service: 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": {
			setBranch: func(old, cur *Branch) {
				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"))
			},
		},
	}

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

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