package smoke

import (
	"bytes"
	"context"
	"fmt"
	"html/template"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/linkerd/linkerd2/pkg/healthcheck"
	"github.com/linkerd/linkerd2/pkg/version"
	"github.com/linkerd/linkerd2/testutil"
)

//////////////////////
///   TEST SETUP   ///
//////////////////////

var (
	TestHelper *testutil.TestHelper
)

func TestMain(m *testing.M) {
	TestHelper = testutil.NewTestHelper()
	// Block test execution until control plane pods are running
	TestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)
	os.Exit(m.Run())
}

//////////////////////
/// TEST EXECUTION ///
//////////////////////

func TestSmoke(t *testing.T) {
	ctx := context.Background()
	TestHelper.WithDataPlaneNamespace(ctx, "smoke-test", map[string]string{}, t, func(t *testing.T, ns string) {
		cmd := []string{"inject", "testdata/smoke_test.yaml"}
		out, injectReport, err := TestHelper.PipeToLinkerdRun("", cmd...)
		if err != nil {
			testutil.AnnotatedFatalf(t, "'linkerd inject' command failed",
				"'linkerd inject' command failed: %s\n%s", err, out)
		}

		err = TestHelper.ValidateOutput(injectReport, "inject.report.golden")
		if err != nil {
			testutil.AnnotatedFatalf(t, "received unexpected output",
				"received unexpected output\n%s", err.Error())
		}

		out, err = TestHelper.KubectlApply(out, ns)
		if err != nil {
			testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
				"'kubectl apply' command failed\n%s", out)
		}

		// Wait for pods to in smoke-test deployment to come up
		for _, deploy := range []string{"smoke-test-terminus", "smoke-test-gateway"} {
			if err := TestHelper.CheckPods(ctx, ns, deploy, 1); err != nil {
				//nolint:errorlint
				if rce, ok := err.(*testutil.RestartCountError); ok {
					testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
				} else {
					testutil.AnnotatedFatal(t, "CheckPods timed-out", err)
				}
			}
		}

		// Test 'linkerd check --proxy' with the current image version
		cmd = []string{"check", "--proxy", "--expected-version", TestHelper.GetVersion(), "--namespace", ns, "--wait=60m"}
		expected := getCheckOutput(t, "check.proxy.golden", TestHelper.GetLinkerdNamespace())

		// Use a short time window for check tests to get rid of transient
		// errors
		timeout := 5 * time.Minute
		err = testutil.RetryFor(timeout, func() error {
			out, err := TestHelper.LinkerdRun(cmd...)
			if err != nil {
				return fmt.Errorf("'linkerd check' command failed\n%w\n%s", err, out)
			}

			if !strings.Contains(out, expected) {
				return fmt.Errorf(
					"Expected:\n%s\nActual:\n%s", expected, out)
			}

			return nil
		})

		if err != nil {
			testutil.AnnotatedFatal(t, fmt.Sprintf("'linkerd check' command timed-out (%s)", timeout), err)
		}

		// Test traffic from smoke-test client to server
		url, err := TestHelper.URLFor(ctx, ns, "smoke-test-gateway", 8080)
		if err != nil {
			testutil.AnnotatedFatalf(t, "failed to get URL",
				"failed to get URL: %s", err)
		}

		output, err := TestHelper.HTTPGetURL(url)
		if err != nil {
			testutil.AnnotatedFatalf(t, "unexpected error",
				"unexpected error: %v %s", err, output)
		}

		expectedStringInPayload := "\"payload\":\"BANANA\""
		if !strings.Contains(output, expectedStringInPayload) {
			testutil.AnnotatedFatalf(t, "application response doesn't contain the expected response",
				"expected application response to contain string [%s], but it was [%s]",
				expectedStringInPayload, output)
		}
	})

}

func getCheckOutput(t *testing.T, goldenFile string, namespace string) string {
	pods, err := TestHelper.KubernetesHelper.GetPods(context.Background(), namespace, nil)
	if err != nil {
		testutil.AnnotatedFatal(t, fmt.Sprintf("failed to retrieve pods: %s", err), err)
	}

	proxyVersionErr := ""
	err = healthcheck.CheckProxyVersionsUpToDate(pods, version.Channels{})
	if err != nil {
		proxyVersionErr = err.Error()
	}

	tpl := template.Must(template.ParseFiles("testdata" + "/" + goldenFile))
	vars := struct {
		ProxyVersionErr string
		HintURL         string
	}{
		proxyVersionErr,
		healthcheck.HintBaseURL(TestHelper.GetVersion()),
	}

	var expected bytes.Buffer
	if err := tpl.Execute(&expected, vars); err != nil {
		testutil.AnnotatedFatal(t, fmt.Sprintf("failed to parse %s template: %s", goldenFile, err), err)
	}

	return expected.String()
}
