// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package integration

import (
	"bytes"
	"fmt"
	"os/exec"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"go.pinniped.dev/test/testlib"
)

func runTestKubectlCommand(t *testing.T, args ...string) (string, string) {
	t.Helper()
	var stdOut, stdErr bytes.Buffer
	testlib.RequireEventually(t, func(requireEventually *require.Assertions) {
		stdOut.Reset()
		stdErr.Reset()
		cmd := exec.CommandContext(t.Context(), "kubectl", args...)
		cmd.Stdout = &stdOut
		cmd.Stderr = &stdErr
		requireEventually.NoError(cmd.Run())
	}, 120*time.Second, 200*time.Millisecond)
	return stdOut.String(), stdErr.String()
}

func requireCleanKubectlStderr(t *testing.T, stderr string) {
	// Every line must be empty or contain a known, innocuous warning.
	for _, line := range strings.Split(stderr, "\n") {
		switch {
		case strings.TrimSpace(line) == "",
			strings.Contains(line, "Throttling request took"),
			strings.Contains(line, "due to client-side throttling, not priority and fairness"),
			strings.Contains(line, "the gcp auth plugin is deprecated in v1.22+, unavailable in "),
			strings.Contains(line, "To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke"):
			// ignore these allowed stderr lines
		default:
			// anything else is a failure
			require.Failf(t, "unexpected kubectl stderr", "kubectl produced unexpected stderr:\n%s\n\n", stderr)
			return
		}
	}
}

func TestGetPinnipedCategory(t *testing.T) {
	env := testlib.IntegrationEnv(t)
	dotSuffix := "." + env.APIGroupSuffix

	aggregatedAPIResources := []struct {
		metav1.GroupVersion
		ListKind string
		Resource string
	}{
		{
			GroupVersion: metav1.GroupVersion{
				Group:   "login.concierge" + dotSuffix,
				Version: "v1alpha1",
			},
			ListKind: "TokenCredentialRequestList",
			Resource: "tokencredentialrequests",
		},
		{
			GroupVersion: metav1.GroupVersion{
				Group:   "identity.concierge" + dotSuffix,
				Version: "v1alpha1",
			},
			ListKind: "WhoAmIRequestList",
			Resource: "whoamirequests",
		},
		{
			GroupVersion: metav1.GroupVersion{
				Group:   "clientsecret.supervisor" + dotSuffix,
				Version: "v1alpha1",
			},
			ListKind: "OIDCClientSecretRequestList",
			Resource: "oidcclientsecretrequests",
		},
	}

	t.Run("can kubectl get whole category as table", func(t *testing.T) {
		t.Parallel()

		stdout, stderr := runTestKubectlCommand(t, "get", "pinniped", "-A")
		requireCleanKubectlStderr(t, stderr)
		require.NotContains(t, stdout, "MethodNotAllowed")

		// The resulting table should include at least a CredentialIssuer.
		require.Contains(t, stdout, dotSuffix)
	})

	t.Run("can kubectl get each aggregated API as table, and listing these aggregated always results in an empty list", func(t *testing.T) {
		t.Parallel()

		for _, tt := range aggregatedAPIResources {
			t.Run(tt.Resource, func(t *testing.T) {
				t.Parallel()

				stdout, stderr := runTestKubectlCommand(t, "get", fmt.Sprintf("%s.%s", tt.Resource, tt.Group), "-A")
				require.Empty(t, stdout)

				require.NotContains(t, stderr, "MethodNotAllowed")
				require.Contains(t, stderr, `No resources found`)
			})
		}
	})

	t.Run("can kubectl get each aggregated API using raw request, and listing these aggregated always results in an empty list", func(t *testing.T) {
		t.Parallel()

		for _, tt := range aggregatedAPIResources {
			t.Run(tt.Resource, func(t *testing.T) {
				t.Parallel()

				stdout, stderr := runTestKubectlCommand(t, "get",
					"--raw", fmt.Sprintf("/apis/%s/%s/%s", tt.Group, tt.Version, tt.Resource))

				requireCleanKubectlStderr(t, stderr)
				require.NotContains(t, stdout, "MethodNotAllowed")

				require.Contains(t, stdout,
					fmt.Sprintf(`{"kind":"%s","apiVersion":"%s/%s","metadata":{"resourceVersion":"0"},"items":[]}`,
						tt.ListKind, tt.Group, tt.Version),
				)
			})
		}
	})
}
