package kube_test

import (
	b64 "encoding/base64"
	"fmt"
	"path"
	"strings"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"k8s.io/apimachinery/pkg/util/wait"

	"code.cloudfoundry.org/quarks-operator/pkg/kube/util/boshdns"
	cmdHelper "code.cloudfoundry.org/quarks-utils/testing"
)

var _ = Describe("Examples Directory", func() {
	var (
		example      string
		yamlFilePath string
	)

	const pollInterval = 5 * time.Second

	podRestarted := func(podName string, startTime time.Time) {
		err := wait.PollImmediate(pollInterval, kubectl.PollTimeout, func() (bool, error) {
			status, err := kubectl.PodStatus(namespace, podName)
			if err != nil {
				return false, err
			}

			if status == nil || status.StartTime == nil {
				return false, nil
			}

			return status.StartTime.After(startTime), nil
		})

		Expect(err).ToNot(HaveOccurred())
	}

	JustBeforeEach(func() {
		yamlFilePath = example
		if !strings.HasPrefix(example, "/") {
			yamlFilePath = path.Join(examplesDir, example)
		}
		err := cmdHelper.Create(namespace, yamlFilePath)
		Expect(err).ToNot(HaveOccurred())
	})

	Context("bosh-deployment service example", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment-with-service.yaml"
		})

		It("creates the deployment and an endpoint", func() {
			By("Checking for pods")
			waitReady("pod/nats-0")
			waitReady("pod/nats-1")

			err := kubectl.WaitForService(namespace, "nats-service")
			Expect(err).ToNot(HaveOccurred())

			ip0, err := cmdHelper.GetData(namespace, "pod", "nats-0", "go-template={{.status.podIP}}")
			Expect(err).ToNot(HaveOccurred())

			ip1, err := cmdHelper.GetData(namespace, "pod", "nats-1", "go-template={{.status.podIP}}")
			Expect(err).ToNot(HaveOccurred())

			out, err := cmdHelper.GetData(namespace, "endpoints", "nats-service", "go-template=\"{{(index .subsets 0).addresses}}\"")
			Expect(err).ToNot(HaveOccurred())
			Expect(out).To(ContainSubstring(string(ip0)))
			Expect(out).To(ContainSubstring(string(ip1)))
		})
	})

	Context("bosh-deployment example", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment.yaml"
		})

		It("deploys two pods", func() {
			waitReady("pod/nats-0")
			waitReady("pod/nats-1")
		})
	})

	Context("bosh-deployment with a custom variable example", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment-with-custom-variable.yaml"
		})

		It("uses the custom variable", func() {
			By("Checking for pods")
			waitReady("pod/nats-0")
			waitReady("pod/nats-1")

			By("Checking the value in the config file")
			// password is stored in line 24
			password, err := cmdHelper.RunCommandWithOutput(namespace, "nats-1", "awk 'NR == 24 {print substr($2,2,17)}' /var/vcap/jobs/nats/config/nats.conf")
			Expect(err).ToNot(HaveOccurred())
			Expect(strings.TrimSuffix(password, "\n")).To(Equal("a-custom-password"))
		})

	})

	Context("bosh-deployment with pre-render ops files", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment-with-pre-ops.yaml"
		})

		It("applies the ops file", func() {
			By("Checking for pods")
			waitReady("pod/nats-0")

			By("Checking the value in the config file")

			envSetByOps, err := cmdHelper.RunCommandWithOutput(namespace, "nats-0", "env")
			Expect(err).ToNot(HaveOccurred())
			Expect(strings.TrimSuffix(envSetByOps, "\n")).To(ContainSubstring("deadbeef"))
		})

	})

	Context("bosh-deployment with user variables", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment-with-user-variable.yaml"
		})

		It("uses the user's variables instead of generating new ones", func() {
			By("Checking for pods")
			waitReady("pod/nats-0")

			// Check that we didn't create new secrets
			sd, err := cmdHelper.GetData(namespace, "secret", "var-nats-password", "go-template={{.data}}")
			Expect(err).ToNot(HaveOccurred())
			Expect(sd).To(BeEmpty())
			sd, err = cmdHelper.GetData(namespace, "secret", "var-nats-ca", "go-template={{.data}}")
			Expect(err).ToNot(HaveOccurred())
			Expect(sd).To(BeEmpty())
			sd, err = cmdHelper.GetData(namespace, "secret", "var-nats-cert", "go-template={{.data}}")
			Expect(err).ToNot(HaveOccurred())
			Expect(sd).To(BeEmpty())

			// Check that the manifest contains the user's certs and passwords
			outSecret, err := cmdHelper.GetData(namespace, "secret", "desired-manifest-v1", `go-template={{index .data "manifest.yaml"}}`)
			Expect(err).ToNot(HaveOccurred())
			desiredManifest, _ := b64.StdEncoding.DecodeString(string(outSecret))
			Expect(string(desiredManifest)).To(ContainSubstring("password: deadbeef"))
			Expect(string(desiredManifest)).To(ContainSubstring("ca_cert: my-ca-cert-data"))
			Expect(string(desiredManifest)).To(ContainSubstring("ca_key: my-ca-private-key"))
			Expect(string(desiredManifest)).To(ContainSubstring("cert: my-cert-data"))
			Expect(string(desiredManifest)).To(ContainSubstring("key: my-private-key-data"))
			Expect(string(desiredManifest)).To(ContainSubstring("cert_ca: my-ca-cert-data"))
		})
	})

	Context("bosh-deployment with a custom variable and logging sidecar disable example", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment-with-custom-variable-disable-sidecar.yaml"
		})
		It("disables the logging sidecar", func() {
			By("Checking for pods")
			waitReady("pod/nats-0")
			waitReady("pod/nats-1")

			By("Ensure only one container exists")
			containerName, err := cmdHelper.GetData(namespace, "pod", "nats-0", "jsonpath={range .spec.containers[*]}{.name}")
			Expect(err).ToNot(HaveOccurred())
			Expect(containerName).To(ContainSubstring("nats-nats"))
			Expect(containerName).ToNot(ContainSubstring("logs"))

			containerName, err = cmdHelper.GetData(namespace, "pod", "nats-1", "jsonpath={range .spec.containers[*]}{.name}")
			Expect(err).ToNot(HaveOccurred())
			Expect(containerName).To(ContainSubstring("nats-nats"))
			Expect(containerName).ToNot(ContainSubstring("logs"))
		})
	})

	Context("bosh-deployment with an implicit variable example", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment-with-implicit-variable.yaml"
		})

		It("updates deployment when implicit variable changes", func() {
			By("Checking for pods")
			waitReady("pod/nats-0")
			status, err := kubectl.PodStatus(namespace, "nats-0")
			Expect(err).ToNot(HaveOccurred(), "error getting pod status")
			startTime := status.StartTime

			By("Updating implicit variable")
			implicitVariablePath := examplesDir + "bosh-deployment/implicit-variable-updated.yaml"
			err = cmdHelper.Apply(namespace, implicitVariablePath)
			Expect(err).ToNot(HaveOccurred())

			By("Checking for pod restart")
			podRestarted("nats-0", startTime.Time)
		})
	})

	Context("bosh-deployment with an implicit variable used by an explicit variable example", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment-with-implicit-in-explicit-variable.yaml"
		})

		It("updates quarks secret when implicit variable changes, then deployment updates", func() {
			By("Checking for pods")
			waitReady("pod/nats-0")
			status, err := kubectl.PodStatus(namespace, "nats-0")
			Expect(err).ToNot(HaveOccurred(), "error getting pod status")
			startTime := status.StartTime

			By("Updating implicit variable")
			implicitVariablePath := examplesDir + "bosh-deployment/implicit-variable-updated.yaml"
			err = cmdHelper.Apply(namespace, implicitVariablePath)
			Expect(err).ToNot(HaveOccurred())

			By("Checking for pod restart")
			podRestarted("nats-0", startTime.Time)
		})
	})

	Context("bosh dns example", func() {
		BeforeEach(func() {
			example = "bosh-deployment/boshdeployment-with-bosh-dns.yaml"
		})

		It("resolves BOSH DNS placeholder aliases", func() {
			By("Getting expected IP")
			podName := "nats-0"
			waitReady(fmt.Sprintf("pod/%s", podName))
			serviceName := "nats-0"
			service, err := kubectl.Service(namespace, serviceName)
			Expect(err).ToNot(HaveOccurred())

			By("DNS lookup")
			err = kubectl.WaitLabelFilter(namespace, "ready", "pod", fmt.Sprintf("app=%s", boshdns.AppName))
			Expect(err).ToNot(HaveOccurred())

			placeholderNames := []string{
				"nats-0.myplaceholderalias.service.cf.internal.",
				"nats-0.myplaceholderalias.service.cf.internal",
			}
			Eventually(func() error {
				for _, name := range placeholderNames {
					if err := kubectl.RunCommandWithCheckString(namespace, podName, fmt.Sprintf("nslookup %s", name), service.Spec.ClusterIP); err != nil {
						return err
					}
				}
				return nil
			}).Should(BeNil())

			By("negativ DNS lookup")
			unresolvableNames := []string{
				"myplaceholderalias.",
				"myplaceholderalias.service.",
			}

			for _, name := range unresolvableNames {
				err = kubectl.RunCommandWithCheckString(namespace, podName, fmt.Sprintf("nslookup %s || true", name), "NXDOMAIN")
				Expect(err).NotTo(HaveOccurred())
			}
		})

		It("resolves BOSH DNS aliases", func() {
			By("Getting expected IP")
			podName := "nats-0"
			waitReady(fmt.Sprintf("pod/%s", podName))
			podStatus, err := kubectl.PodStatus(namespace, podName)
			Expect(err).ToNot(HaveOccurred())

			By("DNS lookup")
			err = kubectl.WaitLabelFilter(namespace, "ready", "pod", fmt.Sprintf("app=%s", boshdns.AppName))
			Expect(err).ToNot(HaveOccurred())

			cnames := []string{
				"nats.service.cf.internal.",
				"nats.service.cf.internal",
			}
			Eventually(func() error {
				for _, name := range cnames {
					if err := kubectl.RunCommandWithCheckString(namespace, podName, fmt.Sprintf("nslookup %s", name), podStatus.PodIP); err != nil {
						return err
					}
				}
				return nil
			}).Should(BeNil())

			By("negative DNS lookup")
			unresolvableWildCardNames := []string{
				"foo.nats.service.cf.internal.",
				"foo.nats.service.cf.internal",
			}
			for _, name := range unresolvableWildCardNames {
				err = kubectl.RunCommandWithCheckString(namespace, podName, fmt.Sprintf("nslookup %s || true", name), "NXDOMAIN")
				Expect(err).NotTo(HaveOccurred())
			}

			unresolvableCNAMES := []string{
				"myplaceholderalias.",
				"myplaceholderalias.service.",
			}
			for _, name := range unresolvableCNAMES {
				err = kubectl.RunCommandWithCheckString(namespace, podName, fmt.Sprintf("nslookup %s || true", name), "NXDOMAIN")
				Expect(err).NotTo(HaveOccurred())
			}
		})
	})

})
