// Copyright 2025 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2025 Microsoft
// Licensed under the Apache License, Version 2.0.
package e2e

import (
	"context"
	"errors"
	"fmt"
	"net/http"

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

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

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"

	hcpsdk "github.com/Azure/ARO-HCP/test/sdk/resourcemanager/redhatopenshifthcp/armredhatopenshifthcp"
	"github.com/Azure/ARO-HCP/test/util/framework"
	"github.com/Azure/ARO-HCP/test/util/labels"
)

var _ = Describe("Customer", func() {
	BeforeEach(func() {
		// do nothing.  per test initialization usually ages better than shared.
	})

	testingPrefix := "ea-list"
	dummyUID := "00000000-0000-0000-0000-000000000000"

	It("should be able to lifecycle and confirm external auth on a cluster",
		labels.RequireNothing,
		labels.High,
		labels.Positive,
		labels.AroRpApiCompatible,
		labels.MIContainers(1),
		func(ctx context.Context) {
			clusterName := testingPrefix + rand.String(6)
			tc := framework.NewTestContext()

			if tc.UsePooledIdentities() {
				err := tc.AssignIdentityContainers(ctx, 1, framework.IdentityContainerAssignmentRetryInterval)
				Expect(err).NotTo(HaveOccurred(), "failed to assign pooled identity containers")
			}

			By("creating resource group for the HCP cluster")
			resourceGroup, err := tc.NewResourceGroup(ctx, testingPrefix, tc.Location())
			Expect(err).NotTo(HaveOccurred(), "failed to create resource group for external auth list test")

			By("creating cluster parameters")
			clusterParams := framework.NewDefaultClusterParams20240610()
			clusterParams.ClusterName = clusterName
			managedResourceGroupName := framework.SuffixName(*resourceGroup.Name, "managed", 64)
			clusterParams.ManagedResourceGroupName = managedResourceGroupName

			By("creating customer resources")
			clusterParams, err = tc.CreateClusterCustomerResources20240610(ctx,
				resourceGroup,
				clusterParams,
				map[string]interface{}{},
				TestArtifactsFS,
				framework.RBACScopeResourceGroup,
			)
			Expect(err).NotTo(HaveOccurred(), "failed to create customer resources for external auth list test")

			By("creating HCP cluster")
			err = tc.CreateHCPClusterFromParam20240610(ctx,
				GinkgoLogr,
				*resourceGroup.Name,
				clusterParams,
				framework.ClusterCreationTimeout,
			)
			Expect(err).NotTo(HaveOccurred(), "failed to create HCP cluster for external auth list test")

			expectedExternalAuth := hcpsdk.ExternalAuth{
				Name: to.Ptr(testingPrefix),
				Properties: &hcpsdk.ExternalAuthProperties{
					Issuer: &hcpsdk.TokenIssuerProfile{
						URL:       to.Ptr(fmt.Sprintf("https://login.microsoftonline.com/%s/v2.0", tc.TenantID())),
						Audiences: []*string{to.Ptr(dummyUID)},
					},
					Claim: &hcpsdk.ExternalAuthClaimProfile{
						Mappings: &hcpsdk.TokenClaimMappingsProfile{
							Username: &hcpsdk.UsernameClaimProfile{
								Claim:        to.Ptr("sub"), // objectID of SP
								PrefixPolicy: to.Ptr(hcpsdk.UsernameClaimPrefixPolicyPrefix),
								Prefix:       to.Ptr(testingPrefix),
							},
							Groups: &hcpsdk.GroupClaimProfile{
								Claim: to.Ptr("groups"),
							},
						},
					},
				},
			}

			By("create an external auth and confirm it's in a succeeded state")
			_, err = framework.CreateOrUpdateExternalAuthAndWait20240610(
				ctx,
				tc.Get20240610ClientFactoryOrDie(ctx).NewExternalAuthsClient(),
				*resourceGroup.Name,
				clusterName,
				*expectedExternalAuth.Name,
				expectedExternalAuth,
				framework.ExternalAuthCreationTimeout,
			)
			Expect(err).NotTo(HaveOccurred(), "failed to create external auth config on cluster %s", clusterName)

			result, err := framework.GetExternalAuth20240610(
				ctx,
				tc.Get20240610ClientFactoryOrDie(ctx).NewExternalAuthsClient(),
				*resourceGroup.Name,
				clusterName,
				*expectedExternalAuth.Name,
			)
			Expect(err).NotTo(HaveOccurred(), "failed to get external auth config from cluster %s", clusterName)
			Expect(*result.Properties.ProvisioningState).To(Equal(hcpsdk.ExternalAuthProvisioningStateSucceeded), "external auth provisioning state should be Succeeded")

			By("confirming we're only allowed to create a single external auth")
			anotherExternalAuth := expectedExternalAuth
			anotherExternalAuth.Name = to.Ptr(testingPrefix + "2")
			_, err = framework.CreateOrUpdateExternalAuthAndWait20240610(
				ctx,
				tc.Get20240610ClientFactoryOrDie(ctx).NewExternalAuthsClient(),
				*resourceGroup.Name,
				clusterName,
				*anotherExternalAuth.Name,
				anotherExternalAuth,
				framework.ExternalAuthCreationTimeout,
			)
			Expect(err).To(HaveOccurred(), "expected error when creating a second external auth config on cluster %s", clusterName)
			Expect(err).To(MatchError(ContainSubstring("There are other external auths on the cluster. Only one external auth is allowed per cluster.")), "expected error when creating a second external auth config on cluster %s", clusterName)

			By("listing all external auth configs to verify a list call works")
			externalAuthClient := tc.Get20240610ClientFactoryOrDie(ctx).NewExternalAuthsClient()
			pager := externalAuthClient.NewListByParentPager(*resourceGroup.Name, clusterName, nil)
			var extAuthResult []hcpsdk.ExternalAuth
			for pager.More() {
				page, err := pager.NextPage(ctx)
				Expect(err).NotTo(HaveOccurred(), "failed to list external auth configs on cluster %s", clusterName)
				for _, eaPtr := range page.Value {
					if eaPtr != nil {
						extAuthResult = append(extAuthResult, *eaPtr)
					}
				}
			}
			Expect(len(extAuthResult)).To(Equal(1), "expected exactly one external auth config on cluster %s", clusterName)

			By("comparing ARM results with expected external auth config")
			// Compare core properties
			actual := extAuthResult[0]
			Expect(*actual.Properties.Issuer.URL).To(Equal(*expectedExternalAuth.Properties.Issuer.URL), "external auth Issuer.URL should match expected value")
			Expect(actual.Properties.Issuer.Audiences).To(Equal(expectedExternalAuth.Properties.Issuer.Audiences), "external auth Issuer.Audiences should match expected value")
			Expect(*actual.Properties.Claim.Mappings.Username.Claim).To(Equal(*expectedExternalAuth.Properties.Claim.Mappings.Username.Claim), "external auth Username.Claim should match expected value")
			Expect(*actual.Properties.Claim.Mappings.Username.PrefixPolicy).To(Equal(*expectedExternalAuth.Properties.Claim.Mappings.Username.PrefixPolicy), "external auth Username.PrefixPolicy should match expected value")
			Expect(*actual.Properties.Claim.Mappings.Groups.Claim).To(Equal(*expectedExternalAuth.Properties.Claim.Mappings.Groups.Claim), "external auth Groups.Claim should match expected value")

			// Compare prefix (handle nil case for NoPrefix policy)
			if expectedExternalAuth.Properties.Claim.Mappings.Username.Prefix != nil {
				Expect(actual.Properties.Claim.Mappings.Username.Prefix).NotTo(BeNil(), "external auth Properties.Claim.Mappings.Username.Prefix was nil")
				Expect(*actual.Properties.Claim.Mappings.Username.Prefix).To(Equal(*expectedExternalAuth.Properties.Claim.Mappings.Username.Prefix), "external auth Username.Prefix should match expected value")
			} else {
				// Accept either nil or empty string for NoPrefix policy
				Expect(actual.Properties.Claim.Mappings.Username.Prefix).To(
					Or(BeNil(), BeEmpty()), "external auth Username.Prefix should be nil or empty for NoPrefix policy",
				)
			}

			By("updating the external auth to a different prefix and confirming the update works")
			expectedExternalAuth.Properties.Claim.Mappings.Username.Prefix = to.Ptr(testingPrefix + "updated")
			_, err = framework.CreateOrUpdateExternalAuthAndWait20240610(
				ctx,
				tc.Get20240610ClientFactoryOrDie(ctx).NewExternalAuthsClient(),
				*resourceGroup.Name,
				clusterName,
				*expectedExternalAuth.Name,
				expectedExternalAuth,
				framework.ExternalAuthCreationTimeout,
			)
			Expect(err).NotTo(HaveOccurred(), "failed to update external auth config prefix on cluster %s", clusterName)

			updatedResult, err := framework.GetExternalAuth20240610(
				ctx,
				tc.Get20240610ClientFactoryOrDie(ctx).NewExternalAuthsClient(),
				*resourceGroup.Name,
				clusterName,
				*expectedExternalAuth.Name,
			)
			Expect(err).NotTo(HaveOccurred(), "failed to get updated external auth config from cluster %s", clusterName)
			Expect(*updatedResult.Properties.ProvisioningState).To(Equal(hcpsdk.ExternalAuthProvisioningStateSucceeded), "updated external auth provisioning state should be Succeeded")
			Expect(*updatedResult.Properties.Claim.Mappings.Username.Prefix).To(Equal(*expectedExternalAuth.Properties.Claim.Mappings.Username.Prefix), "updated external auth Username.Prefix should match expected value")

			By("deleting the external auth")
			err = framework.DeleteExternalAuthAndWait20240610(
				ctx,
				externalAuthClient,
				*resourceGroup.Name,
				clusterName,
				*expectedExternalAuth.Name,
				framework.ExternalAuthDeletionTimeout,
			)
			Expect(err).NotTo(HaveOccurred(), "failed to delete external auth config on cluster %s", clusterName)

			By("confirming the external auth has been deleted")
			_, getErr := framework.GetExternalAuth20240610(
				ctx,
				externalAuthClient,
				*resourceGroup.Name,
				clusterName,
				*expectedExternalAuth.Name,
			)
			Expect(getErr).To(HaveOccurred(), "expected error when getting deleted external auth %s on cluster %s", *expectedExternalAuth.Name, clusterName)
			var respErr *azcore.ResponseError
			Expect(errors.As(getErr, &respErr)).To(BeTrue(), "expected azcore.ResponseError when getting deleted external auth %s", *expectedExternalAuth.Name)
			Expect(respErr.StatusCode).To(Equal(http.StatusNotFound), "expected HTTP 404 when getting deleted external auth %s", *expectedExternalAuth.Name)

			By("confirming list returns no external auth configs")
			var extAuthCount int
			pager = externalAuthClient.NewListByParentPager(*resourceGroup.Name, clusterName, nil)
			for pager.More() {
				page, err := pager.NextPage(ctx)
				Expect(err).NotTo(HaveOccurred(), "failed to list external auth configs on cluster %s after delete", clusterName)
				extAuthCount += len(page.Value)
			}
			Expect(extAuthCount).To(Equal(0), "expected no external auth configs on cluster %s after delete", clusterName)

		})
})
