/*
Copyright 2022 The KubeVela Authors.

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 addon

import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"k8s.io/helm/pkg/tlsutil"
)

var _ = Describe("Addon push command", func() {
	var (
		testTarballPath    = "testdata/charts/sample-1.0.1.tgz"
		testServerCertPath = "testdata/tls/server.crt"
		testServerKeyPath  = "testdata/tls/server.key"
		testServerCAPath   = "testdata/tls/server_ca.crt"
		testClientCAPath   = "testdata/tls/client_ca.crt"
		testClientCertPath = "testdata/tls/client.crt"
		testClientKeyPath  = "testdata/tls/client.key"
	)
	var (
		statusCode int
		body       string
		tmp        string
	)
	var p *PushCmd
	var ts *httptest.Server
	var err error
	var ds RegistryDataStore

	setArgsAndRun := func(args []string) error {
		p = &PushCmd{}
		p.Client = k8sClient
		p.Out = os.Stdout
		p.ChartName = args[0]
		p.RepoName = args[1]
		p.SetFieldsFromEnv()
		return p.Push(context.TODO())
	}

	AfterEach(func() {
		ts.Close()
		_ = os.RemoveAll(tmp)
		err = ds.DeleteRegistry(context.TODO(), "helm-push-test")
		Expect(err).To(Succeed())
	})

	Context("plain old HTTP Server", func() {
		BeforeEach(func() {
			statusCode = 201
			body = "{\"success\": true}"
			ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(statusCode)
				w.Write([]byte(body))
			}))

			// Create new Helm home w/ test repo
			tmp, err = ioutil.TempDir("", "helm-push-test")
			Expect(err).To(Succeed())

			// Add our helm repo to addon registry
			ds = NewRegistryDataStore(k8sClient)
			err = ds.AddRegistry(context.TODO(), Registry{
				Name: "helm-push-test",
				Helm: &HelmSource{
					URL: ts.URL,
				},
			})
			Expect(err).To(Succeed())

			_ = os.Setenv("HELM_REPO_USERNAME", "myuser")
			_ = os.Setenv("HELM_REPO_PASSWORD", "mypass")
			_ = os.Setenv("HELM_REPO_CONTEXT_PATH", "/x/y/z")
		})

		It("Not enough args", func() {
			err = setArgsAndRun([]string{"", ""})
			Expect(err).ShouldNot(Succeed(), "expecting error with missing args, instead got nil")
		})

		It("Bad chart path", func() {
			args := []string{"/this/this/not/a/chart", "helm-push-test"}
			err = setArgsAndRun(args)
			Expect(err).ShouldNot(Succeed(), "expecting error with bad chart path, instead got nil")
		})

		It("Bad repo name", func() {
			args := []string{testTarballPath, "this-is-not-a-valid-repo"}
			err = setArgsAndRun(args)
			Expect(err).ShouldNot(Succeed(), "expecting error with bad repo name, instead got nil")
		})

		It("Valid tar, repo name", func() {
			args := []string{testTarballPath, "helm-push-test"}
			err = setArgsAndRun(args)
			Expect(err).Should(Succeed())
		})

		It("Valid tar, repo URL", func() {
			args := []string{testTarballPath, ts.URL}
			err = setArgsAndRun(args)
			Expect(err).Should(Succeed())
		})

		It("Trigger 409, already exists", func() {
			statusCode = 409
			body = "{\"error\": \"package already exists\"}"
			args := []string{testTarballPath, "helm-push-test"}
			err = setArgsAndRun(args)
			Expect(err).ShouldNot(Succeed(), "expecting error with 409, instead got nil")
		})

		It("Unable to parse JSON response body", func() {
			statusCode = 500
			body = "duiasnhioasd"
			args := []string{testTarballPath, "helm-push-test"}
			err = setArgsAndRun(args)
			Expect(err).ShouldNot(Succeed(), "expecting error with bad response body, instead got nil")
		})
	})

	Context("TLS Enabled Server", func() {
		BeforeEach(func() {
			statusCode = 201
			body = "{\"success\": true}"
			ts = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(statusCode)
				_, _ = w.Write([]byte(body))
			}))
			serverCert, err := tls.LoadX509KeyPair(testServerCertPath, testServerKeyPath)
			Expect(err).To(Succeed(), "failed to load certificate and key")

			clientCaCertPool, err := tlsutil.CertPoolFromFile(testClientCAPath)
			Expect(err).To(Succeed(), "load server CA file failed")

			ts.TLS = &tls.Config{
				ClientCAs:    clientCaCertPool,
				ClientAuth:   tls.RequireAndVerifyClientCert,
				Certificates: []tls.Certificate{serverCert},
				Rand:         rand.Reader,
			}
			ts.StartTLS()

			// Create new Helm home w/ test repo
			tmp, err = ioutil.TempDir("", "helm-push-test")
			Expect(err).To(Succeed())

			// Add our helm repo to addon registry
			ds = NewRegistryDataStore(k8sClient)
			err = ds.AddRegistry(context.TODO(), Registry{
				Name: "helm-push-test",
				Helm: &HelmSource{
					URL: ts.URL,
				},
			})
			Expect(err).To(Succeed())

			_ = os.Setenv("HELM_REPO_USERNAME", "myuser")
			_ = os.Setenv("HELM_REPO_PASSWORD", "mypass")
			_ = os.Setenv("HELM_REPO_CONTEXT_PATH", "/x/y/z")
		})

		It("no cert provided", func() {
			_ = os.Unsetenv("HELM_REPO_CA_FILE")
			_ = os.Unsetenv("HELM_REPO_CERT_FILE")
			_ = os.Unsetenv("HELM_REPO_KEY_FILE")
			args := []string{testTarballPath, "helm-push-test"}
			err = setArgsAndRun(args)
			Expect(err).ShouldNot(Succeed(), "expected non nil error but got nil when run cmd without certificate option")
		})

		It("with cert", func() {
			_ = os.Setenv("HELM_REPO_CA_FILE", testServerCAPath)
			_ = os.Setenv("HELM_REPO_CERT_FILE", testClientCertPath)
			_ = os.Setenv("HELM_REPO_KEY_FILE", testClientKeyPath)
			args := []string{testTarballPath, "helm-push-test"}
			err = setArgsAndRun(args)
			Expect(err).Should(Succeed())
		})
	})
})
