// Copyright (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"

	"bytes"
	"net/http"
	"net/http/httptest"

	"k8s.io/api/admission/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/intel/multus-cni/types"
)

var _ = Describe("Webhook", func() {

	Describe("Preparing Admission Review Response", func() {
		Context("Admission Review Request is nil", func() {
			It("should return error", func() {
				ar := &v1beta1.AdmissionReview{}
				ar.Request = nil
				Expect(prepareAdmissionReviewResponse(false, "", ar)).To(HaveOccurred())
			})
		})
		Context("Message is not empty", func() {
			It("should set message in the response", func() {
				ar := &v1beta1.AdmissionReview{}
				ar.Request = &v1beta1.AdmissionRequest{
					UID: "fake-uid",
				}
				err := prepareAdmissionReviewResponse(false, "some message", ar)
				Expect(err).NotTo(HaveOccurred())
				Expect(ar.Response.Result.Message).To(Equal("some message"))
			})
		})
	})

	Describe("Deserializing Admission Review", func() {
		Context("It's not an Admission Review", func() {
			It("should return an error", func() {
				body := []byte("some-invalid-body")
				_, err := deserializeAdmissionReview(body)
				Expect(err).To(HaveOccurred())
			})
		})
	})

	Describe("Deserializing Network Attachment Definition", func() {
		Context("It's not an Network Attachment Definition", func() {
			It("should return an error", func() {
				ar := &v1beta1.AdmissionReview{}
				ar.Request = &v1beta1.AdmissionRequest{}
				_, err := deserializeNetworkAttachmentDefinition(*ar)
				Expect(err).To(HaveOccurred())
			})
		})
	})

	Describe("Handling validation request", func() {
		Context("Request body is empty", func() {
			It("should return an error", func() {
				req := httptest.NewRequest("POST", "https://fakewebhook/validate", nil)
				w := httptest.NewRecorder()
				validateHandler(w, req)
				resp := w.Result()
				Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))
			})
		})

		Context("Content type is not application/json", func() {
			It("should return an error", func() {
				req := httptest.NewRequest("POST", "https://fakewebhook/validate", bytes.NewBufferString("fake-body"))
				req.Header.Set("Content-Type", "invalid-type")
				w := httptest.NewRecorder()
				validateHandler(w, req)
				resp := w.Result()
				Expect(resp.StatusCode).To(Equal(http.StatusUnsupportedMediaType))
			})
		})
	})

	DescribeTable("Network Attachment Definition validation",
		func(in types.NetworkAttachmentDefinition, out bool, shouldFail bool) {
			actualOut, err := validateNetworkAttachmentDefinition(in)
			Expect(actualOut).To(Equal(out))
			if shouldFail {
				Expect(err).To(HaveOccurred())
			}
		},
		Entry(
			"empty config",
			types.NetworkAttachmentDefinition{
				Metadata: metav1.ObjectMeta{
					Name: "some-valid-name",
				},
			},
			false, true,
		),
		Entry(
			"invalid name",
			types.NetworkAttachmentDefinition{
				Metadata: metav1.ObjectMeta{
					Name: "some_invalid_name",
				},
			},
			false, true,
		),
		Entry(
			"invalid network config",
			types.NetworkAttachmentDefinition{
				Metadata: metav1.ObjectMeta{
					Name: "some-valid-name",
				},
				Spec: types.NetworkAttachmentDefinitionSpec{
					Config: `{"some-invalid": "config"}`,
				},
			},
			false, true,
		),
		Entry(
			"valid network config",
			types.NetworkAttachmentDefinition{
				Metadata: metav1.ObjectMeta{
					Name: "some-valid-name",
				},
				Spec: types.NetworkAttachmentDefinitionSpec{
					Config: `{
            "cniVersion": "0.3.0",
            "type": "some-plugin"
          }`,
				},
			},
			true, false,
		),
		Entry(
			"valid network config list",
			types.NetworkAttachmentDefinition{
				Metadata: metav1.ObjectMeta{
					Name: "some-valid-name",
				},
				Spec: types.NetworkAttachmentDefinitionSpec{
					Config: `{
            "cniVersion": "0.3.0",
            "name": "some-bridge-network",
            "plugins": [
              {
                "type": "bridge",
                "bridge": "br0",
                "ipam": {
                  "type": "host-local",
                  "subnet": "192.168.1.0/24"
                }
              },
              {
                "type": "some-plugin"
              },
              {
                "type": "another-plugin",
                "sysctl": {
                  "net.ipv4.conf.all.log_martians": "1"
                }
              }
            ]
          }`,
				},
			},
			true, false,
		),
	)
})
