package v1

import (
	"context"
	"testing"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/validation/field"
)

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

	providerName := "TestValidateRegion"
	provider := &Provider{
		ObjectMeta: metav1.ObjectMeta{
			Name: providerName,
		},
		Spec: ProviderSpec{
			Regions: []Region{
				{
					Name: "r1",
				},
				{
					Name: "r2",
				},
			},
		},
	}

	n := &Network{
		Spec: NetworkSpec{
			Provider: providerName,
			CIDR:     "10.240.0.0/12",
		},
	}

	n.Spec.Region = "r1"
	err := n.validateRegion(provider)
	g.Expect(err).Should(Succeed())
	n.Spec.Region = "r2"
	err = n.validateRegion(provider)
	g.Expect(err).Should(Succeed())
	n.Spec.Region = "wrong-region"
	err = n.validateRegion(provider)
	g.Expect(err).ShouldNot(Succeed())
}

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

	n := &Network{
		Spec: NetworkSpec{
			CIDR: "10.240.0.0/12",
		},
	}
	fld := field.NewPath("spec")

	// test valid CIDR
	g.Expect(n.validateCIDR(fld)).Should(BeNil())

	// test too small host bits
	n.Spec.CIDR = "10.240.0.0/31"
	g.Expect(n.validateCIDR(fld)).ShouldNot(BeNil())

	// test invalid CIDR
	n.Spec.CIDR = "dfdfdfsf"
	g.Expect(n.validateCIDR(fld)).ShouldNot(BeNil())
}

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

	n := &Network{
		Spec: NetworkSpec{
			CIDR: "10.240.0.0/12",
		},
	}

	old := n.DeepCopy()

	// test valid update
	g.Expect(n.validateUpdate(old)).Should(BeNil())
	// test invalid mute CIDR
	old.Spec.CIDR = "10.240.0.1/12"
	g.Expect(n.validateUpdate(old)).ShouldNot(BeNil())
}

func TestValidateUpdateAccountRef(t *testing.T) {
	networkWithAccountFoo := &Network{
		Spec: NetworkSpec{
			AccountRef: &AccountReference{
				Name: "account-foo",
			},
		},
	}

	networkWithAccountBar := &Network{
		Spec: NetworkSpec{
			AccountRef: &AccountReference{
				Name: "account-bar",
			},
		},
	}

	networkWithoutAccount := &Network{
		Spec: NetworkSpec{},
	}

	tests := []struct {
		name      string
		oldNet    *Network
		newNet    *Network
		wantError bool
	}{
		{
			name:      "update nothing 1",
			oldNet:    networkWithAccountFoo,
			newNet:    networkWithAccountFoo.DeepCopy(),
			wantError: false,
		},
		{
			name:      "update nothing 2",
			oldNet:    networkWithAccountBar,
			newNet:    networkWithAccountBar.DeepCopy(),
			wantError: false,
		},
		{
			name:      "update nothing 3",
			oldNet:    networkWithoutAccount,
			newNet:    networkWithoutAccount.DeepCopy(),
			wantError: false,
		},
		{
			name:      "add accountRef",
			oldNet:    networkWithoutAccount,
			newNet:    networkWithAccountFoo,
			wantError: false,
		},
		{
			name:      "update accountRef to another account",
			oldNet:    networkWithAccountFoo,
			newNet:    networkWithAccountBar,
			wantError: true,
		},
		{
			name:      "update accountRef to nil",
			oldNet:    networkWithAccountFoo,
			newNet:    networkWithoutAccount,
			wantError: true,
		},
	}

	g := NewGomegaWithT(t)
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.wantError {
				g.Expect(tt.newNet.validateUpdate(tt.oldNet)).ShouldNot(BeNil())
			} else {
				g.Expect(tt.newNet.validateUpdate(tt.oldNet)).Should(BeNil())
			}
		})
	}
}

var _ = Describe("Network webhook", func() {
	Context("Create Network", func() {
		It("should reject when region、provider and projectID same with another network", func() {
			By("prepare provider and network")
			pt := &ProviderType{
				ObjectMeta: metav1.ObjectMeta{
					Name: ProviderTypeAWS,
				},
			}
			Expect(k8sClient.Create(ctx, pt)).To(Succeed())
			p := &Provider{
				ObjectMeta: metav1.ObjectMeta{
					Name: "provider-" + RandomString(5),
				},
				Spec: ProviderSpec{
					Type: pt.Name,
					Regions: []Region{
						{
							Name:  "us-west-2",
							Zones: []string{"us-west-2a", "us-west-2b", "us-west-2c"},
						},
					},
				},
			}

			Expect(k8sClient.Create(ctx, p)).To(Succeed())

			n1 := newSampleNetwork(p.Name)
			Expect(k8sClient.Create(context.TODO(), n1)).To(Succeed())
			Expect(n1.Labels[RegionLabel]).To(Equal("us-west-2"))

			n2 := newSampleNetwork(p.Name)
			err := k8sClient.Create(context.TODO(), n2)
			Expect(err).ToNot(Succeed())
			Expect(err.Error()).To(ContainSubstring("shoot name will conflict"))
		})
	})
})

func newSampleNetwork(provider string) *Network {
	return &Network{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "network-" + RandomString(5),
			Namespace: "default",
			Labels: map[string]string{
				ProjectLabel: "1",
			},
		},
		Spec: NetworkSpec{
			Provider: provider,
			Region:   "us-west-2",
			CIDR:     "10.0.0.0/16",
		},
	}
}
