package e2e

import (
	"context"
	"fmt"
	"io"
	"net/url"
	"testing"
	"time"

	g "github.com/onsi/ginkgo/v2"
	"github.com/stretchr/testify/require"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"

	configv1 "github.com/openshift/api/config/v1"

	e2e "github.com/openshift/cluster-authentication-operator/test/library"
)

var _ = g.Describe("[sig-auth] authentication operator", func() {
	g.It("[Templates] TestTemplatesConfig", func() {
		testTemplatesConfig(g.GinkgoTB())
	})
})

func testTemplatesConfig(t testing.TB) {
	clients := e2e.NewTestClients(t)

	configSecretsClient := clients.KubeClient.CoreV1().Secrets("openshift-config")

	cleanup := createSecret(t, configSecretsClient, "login", "login.html", "login test")
	defer cleanup()
	cleanup = createSecret(t, configSecretsClient, "providers", "providers.html", "provider selection test")
	defer cleanup()
	cleanup = createSecret(t, configSecretsClient, "error", "errors.html", "error test")
	defer cleanup()
	cleanup = createSecret(t, configSecretsClient, "htpasswd1", "htpasswd", "test:$2y$05$9Co/ojOvEs6IZUTxAdlHbO8leelkkmcwPMtlGTHFkxcTLrC86EbLG") // test:password
	defer cleanup()

	oauthConfig, err := clients.ConfigClient.ConfigV1().OAuths().Get(context.TODO(), "cluster", metav1.GetOptions{})
	require.NoError(t, err)

	oauthConfigCopy := oauthConfig.DeepCopy()

	oauthConfigCopy.Spec.Templates.Login.Name = "login"
	oauthConfigCopy.Spec.Templates.ProviderSelection.Name = "providers"
	oauthConfigCopy.Spec.Templates.Error.Name = "error"

	oauthConfigCopy.Spec.IdentityProviders = []configv1.IdentityProvider{
		{
			Name: "super duper production-ready htpasswd idp",
			IdentityProviderConfig: configv1.IdentityProviderConfig{
				Type: configv1.IdentityProviderTypeHTPasswd,
				HTPasswd: &configv1.HTPasswdIdentityProvider{
					FileData: configv1.SecretNameReference{
						Name: "htpasswd1",
					},
				},
			},
		},
	}

	_, err = clients.ConfigClient.ConfigV1().OAuths().Update(context.TODO(), oauthConfigCopy, metav1.UpdateOptions{})
	require.NoError(t, err)
	defer func() {
		oauthConfigNew, err := clients.ConfigClient.ConfigV1().OAuths().Get(context.TODO(), "cluster", metav1.GetOptions{})
		require.NoError(t, err)

		oauthConfigNew.Spec.IdentityProviders = oauthConfig.Spec.IdentityProviders
		oauthConfigNew.Spec.Templates = oauthConfig.Spec.Templates

		_, err = clients.ConfigClient.ConfigV1().OAuths().Update(context.TODO(), oauthConfigNew, metav1.UpdateOptions{})
		require.NoError(t, err)
	}()

	// wait for new rollout
	err = e2e.WaitForClusterOperatorProgressing(t, clients.ConfigClient.ConfigV1(), "authentication")
	require.NoError(t, err, "authentication operator never became progressing")

	err = e2e.WaitForClusterOperatorAvailableNotProgressingNotDegraded(t, clients.ConfigClient.ConfigV1(), "authentication")
	require.NoError(t, err, "failed to wait for the authentication operator to become available")

	route, err := clients.RouteClient.RouteV1().Routes("openshift-authentication").Get(context.TODO(), "oauth-openshift", metav1.GetOptions{})
	require.NoError(t, err)
	oauthURL, err := url.Parse("https://" + route.Spec.Host)
	require.NoError(t, err)

	httpClient := e2e.NewInsecureHTTPClient(30 * time.Second)
	oauthURL.Path = "/oauth/token/request" // should redirect to where the providers are
	resp, err := httpClient.Get(oauthURL.String())
	require.NoError(t, err)
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)
	require.Contains(t, string(body), "provider selection test")

	// modify URL to go to the IdP login
	oauthURL.Path = "/oauth/token/display"
	oauthURL.RawQuery = fmt.Sprintf("client_id=openshift-browser-client&idp=super+duper+production-ready+htpasswd+idp&redirect_uri=%s&response_type=code", url.QueryEscape(oauthURL.String()))
	oauthURL.Path = "/oauth/authorize"
	resp, err = httpClient.Get(oauthURL.String())
	require.NoError(t, err)
	body, err = io.ReadAll(resp.Body)
	require.NoError(t, err)
	require.Contains(t, string(body), "login test")
}

func createSecret(t testing.TB, secrets corev1client.SecretInterface, name, key, content string) func() {
	_, err := secrets.Create(context.TODO(), &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Data: map[string][]byte{
			key: []byte(content),
		},
	}, metav1.CreateOptions{})

	require.NoError(t, err)

	return func() {
		if err := secrets.Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil {
			t.Logf("failed to remove secret openshift-config/%s: %v", name, err)
		}
	}
}
