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

package integration

import (
	"context"
	"encoding/base64"
	"fmt"
	"testing"
	"time"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
	"go.pinniped.dev/test/testlib"
)

func TestLDAPIDPPhaseAndConditions_Parallel(t *testing.T) {
	env := testlib.IntegrationEnv(t)

	supervisorNamespace := testlib.IntegrationEnv(t).SupervisorNamespace
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
	t.Cleanup(cancel)

	ldapIDPClient := testlib.NewSupervisorClientset(t).IDPV1alpha1().LDAPIdentityProviders(supervisorNamespace)

	bindSecret := testlib.CreateTestSecret(
		t,
		env.SupervisorNamespace,
		"ldap-bind-secret",
		corev1.SecretTypeBasicAuth,
		map[string]string{
			corev1.BasicAuthUsernameKey: env.SupervisorUpstreamLDAP.BindUsername,
			corev1.BasicAuthPasswordKey: env.SupervisorUpstreamLDAP.BindPassword,
		},
	)

	wantCABundleMessage := func(caBundleConfigured bool) string {
		if caBundleConfigured {
			return "spec.tls is valid: using configured CA bundle"
		} else {
			return "spec.tls is valid: no TLS configuration provided: using default root CA bundle from container image"
		}
	}

	happySpec := idpv1alpha1.LDAPIdentityProviderSpec{
		Host: env.SupervisorUpstreamLDAP.Host,
		Bind: idpv1alpha1.LDAPIdentityProviderBind{
			SecretName: bindSecret.Name,
		},
		TLS: &idpv1alpha1.TLSSpec{
			CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.CABundle)),
		},
		UserSearch: idpv1alpha1.LDAPIdentityProviderUserSearch{
			Base:   env.SupervisorUpstreamLDAP.UserSearchBase,
			Filter: "",
			Attributes: idpv1alpha1.LDAPIdentityProviderUserSearchAttributes{
				Username: env.SupervisorUpstreamLDAP.TestUserMailAttributeName,
				UID:      env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeName,
			},
		},
		GroupSearch: idpv1alpha1.LDAPIdentityProviderGroupSearch{
			Base:   env.SupervisorUpstreamLDAP.GroupSearchBase,
			Filter: env.SupervisorUpstreamLDAP.GroupSearchFilter, // if empty, uses the default value of "member={}"
			Attributes: idpv1alpha1.LDAPIdentityProviderGroupSearchAttributes{
				GroupName: "", // use the default value of "dn"
			},
		},
	}

	tests := []struct {
		name           string
		ldapSpec       idpv1alpha1.LDAPIdentityProviderSpec
		wantPhase      idpv1alpha1.LDAPIdentityProviderPhase
		wantConditions []*metav1.Condition
	}{
		{
			name:      "Happy Path",
			ldapSpec:  happySpec,
			wantPhase: idpv1alpha1.LDAPPhaseReady,
			wantConditions: []*metav1.Condition{
				{
					Type:    "BindSecretValid",
					Status:  "True",
					Reason:  "Success",
					Message: "loaded bind secret",
				},
				{
					Type:   "LDAPConnectionValid",
					Status: "True",
					Reason: "Success",
					Message: fmt.Sprintf(
						`successfully able to connect to %q and bind as user %q [validated with Secret %q at version %q]`,
						env.SupervisorUpstreamLDAP.Host,
						env.SupervisorUpstreamLDAP.BindUsername,
						bindSecret.Name,
						bindSecret.ResourceVersion),
				},
				{
					Type:    "TLSConfigurationValid",
					Status:  "True",
					Reason:  "Success",
					Message: wantCABundleMessage(len(happySpec.TLS.CertificateAuthorityData) != 0),
				},
			},
		},
		{
			name: "CA bundle is invalid yields conditions TLSConfigurationValid with status 'False' and LDAPConnectionValid/SearchBaseFound with status 'Unknown'",
			ldapSpec: func() idpv1alpha1.LDAPIdentityProviderSpec {
				temp := happySpec.DeepCopy()
				temp.TLS.CertificateAuthorityData = "this-is-not-base64-encoded"
				return *temp
			}(),
			wantPhase: idpv1alpha1.LDAPPhaseError,
			wantConditions: []*metav1.Condition{
				{
					Type:    "BindSecretValid",
					Status:  "True",
					Reason:  "Success",
					Message: "loaded bind secret",
				},
				{
					Type:    "LDAPConnectionValid",
					Status:  "Unknown",
					Reason:  "UnableToValidate",
					Message: "unable to validate; see other conditions for details",
				},
				{
					Type:    "TLSConfigurationValid",
					Status:  "False",
					Reason:  "InvalidTLSConfig",
					Message: "spec.tls.certificateAuthorityData is invalid: illegal base64 data at input byte 4",
				},
			},
		},
		{
			name: "Bind secret not found yields conditions BindSecretValid with status 'False' and LDAPConnectionValid/SearchBaseFound with status 'Unknown'",
			ldapSpec: func() idpv1alpha1.LDAPIdentityProviderSpec {
				temp := happySpec.DeepCopy()
				temp.Bind.SecretName = "this-secret-does-not-exist"
				return *temp
			}(),
			wantPhase: idpv1alpha1.LDAPPhaseError,
			wantConditions: []*metav1.Condition{
				{
					Type:    "BindSecretValid",
					Status:  "False",
					Reason:  "SecretNotFound",
					Message: `secret "this-secret-does-not-exist" not found`,
				},
				{
					Type:    "LDAPConnectionValid",
					Status:  "Unknown",
					Reason:  "UnableToValidate",
					Message: "unable to validate; see other conditions for details",
				},
				{
					Type:    "TLSConfigurationValid",
					Status:  "True",
					Reason:  "Success",
					Message: wantCABundleMessage(len(happySpec.TLS.CertificateAuthorityData) != 0),
				},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()

			idp := testlib.CreateTestLDAPIdentityProvider(t, test.ldapSpec, test.wantPhase)
			testlib.WaitForLDAPIdentityProviderStatusConditions(
				ctx,
				t,
				ldapIDPClient,
				idp.Name,
				test.wantConditions,
			)
		})
	}
}
