// Copyright 2026 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.

package operations

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	utilsclock "k8s.io/utils/clock"

	arohcpv1alpha1 "github.com/openshift-online/ocm-sdk-go/arohcp/v1alpha1"

	operationtesting "github.com/Azure/ARO-HCP/backend/pkg/utils/operationutils/operationtesting"
	"github.com/Azure/ARO-HCP/internal/api/coreapi"
	"github.com/Azure/ARO-HCP/internal/database/cosmosstorage/cosmosstorageutils"
	"github.com/Azure/ARO-HCP/internal/database/cosmosstoragetesting/corecosmosstoragetesting"
	"github.com/Azure/ARO-HCP/internal/database/listertesting/corelistertesting"
	"github.com/Azure/ARO-HCP/internal/ocm"
	"github.com/Azure/ARO-HCP/internal/utils"
)

func TestOperationExternalAuthCreate_SynchronizeOperation(t *testing.T) {
	defaultExternalAuth := func(fixture *operationtesting.ExternalAuthTestFixture) *coreapi.HCPOpenShiftClusterExternalAuth {
		return fixture.NewExternalAuth()
	}

	externalAuthWithoutCSID := func(fixture *operationtesting.ExternalAuthTestFixture) *coreapi.HCPOpenShiftClusterExternalAuth {
		ea := fixture.NewExternalAuth()
		ea.ServiceProviderProperties.ClusterServiceID = nil
		return ea
	}

	externalAuthWithDeletionTimestamp := func(fixture *operationtesting.ExternalAuthTestFixture) *coreapi.HCPOpenShiftClusterExternalAuth {
		ea := fixture.NewExternalAuth()
		ea.ServiceProviderProperties.DeletionTimestamp = &metav1.Time{Time: time.Now()}
		return ea
	}

	externalAuthWithMismatchedActiveOperationID := func(fixture *operationtesting.ExternalAuthTestFixture) *coreapi.HCPOpenShiftClusterExternalAuth {
		ea := fixture.NewExternalAuth()
		ea.ServiceProviderProperties.ActiveOperationID = "other-operation"
		return ea
	}

	externalAuthWithEmptyActiveOperationID := func(fixture *operationtesting.ExternalAuthTestFixture) *coreapi.HCPOpenShiftClusterExternalAuth {
		ea := fixture.NewExternalAuth()
		ea.ServiceProviderProperties.ActiveOperationID = ""
		return ea
	}

	tests := []struct {
		name         string
		externalAuth func(fixture *operationtesting.ExternalAuthTestFixture) *coreapi.HCPOpenShiftClusterExternalAuth
		setupCSMock  func(ctrl *gomock.Controller, fixture *operationtesting.ExternalAuthTestFixture) ocm.ClusterServiceClientSpec
		expectError  bool
		verify       func(t *testing.T, ctx context.Context, db *corecosmosstoragetesting.MockResourcesDBClient, fixture *operationtesting.ExternalAuthTestFixture)
	}{
		{
			name:         "external auth exists transitions to succeeded",
			externalAuth: defaultExternalAuth,
			setupCSMock: func(ctrl *gomock.Controller, fixture *operationtesting.ExternalAuthTestFixture) ocm.ClusterServiceClientSpec {
				mockCSClient := ocm.NewMockClusterServiceClientSpec(ctrl)
				externalAuth, _ := arohcpv1alpha1.NewExternalAuth().
					ID(operationtesting.TestExternalAuthIDStr).
					Build()
				mockCSClient.EXPECT().
					GetExternalAuth(gomock.Any(), fixture.ExternalAuthInternalID).
					Return(externalAuth, nil)
				return mockCSClient
			},
			expectError: false,
			verify: func(t *testing.T, ctx context.Context, db *corecosmosstoragetesting.MockResourcesDBClient, fixture *operationtesting.ExternalAuthTestFixture) {
				op, err := db.Operations(operationtesting.TestSubscriptionID).Get(ctx, operationtesting.TestOperationName)
				require.NoError(t, err)
				assert.Equal(t, coreapi.ProvisioningStateSucceeded, op.Status)

				externalAuth, err := db.HCPClusters(operationtesting.TestSubscriptionID, operationtesting.TestResourceGroupName).ExternalAuth(operationtesting.TestClusterName).Get(ctx, operationtesting.TestExternalAuthName)
				require.NoError(t, err)
				assert.Equal(t, coreapi.ProvisioningStateSucceeded, externalAuth.Properties.ProvisioningState)
				assert.Empty(t, externalAuth.ServiceProviderProperties.ActiveOperationID)
			},
		},
		{
			name:         "external auth get error returns error",
			externalAuth: defaultExternalAuth,
			setupCSMock: func(ctrl *gomock.Controller, fixture *operationtesting.ExternalAuthTestFixture) ocm.ClusterServiceClientSpec {
				mockCSClient := ocm.NewMockClusterServiceClientSpec(ctrl)
				mockCSClient.EXPECT().
					GetExternalAuth(gomock.Any(), fixture.ExternalAuthInternalID).
					Return(nil, fmt.Errorf("cluster service error"))
				return mockCSClient
			},
			expectError: true,
			verify:      nil,
		},
		{
			name:         "ClusterServiceID nil skips reconciliation",
			externalAuth: externalAuthWithoutCSID,
			verify: func(t *testing.T, ctx context.Context, db *corecosmosstoragetesting.MockResourcesDBClient, fixture *operationtesting.ExternalAuthTestFixture) {
				op, err := db.Operations(operationtesting.TestSubscriptionID).Get(ctx, operationtesting.TestOperationName)
				require.NoError(t, err)
				assert.Equal(t, coreapi.ProvisioningStateAccepted, op.Status)
			},
		},
		{
			name:         "ActiveOperationID mismatch skips reconciliation",
			externalAuth: externalAuthWithMismatchedActiveOperationID,
			verify: func(t *testing.T, ctx context.Context, db *corecosmosstoragetesting.MockResourcesDBClient, fixture *operationtesting.ExternalAuthTestFixture) {
				op, err := db.Operations(operationtesting.TestSubscriptionID).Get(ctx, operationtesting.TestOperationName)
				require.NoError(t, err)
				assert.Equal(t, coreapi.ProvisioningStateAccepted, op.Status)
			},
		},
		{
			name:         "empty ActiveOperationID skips reconciliation",
			externalAuth: externalAuthWithEmptyActiveOperationID,
			verify: func(t *testing.T, ctx context.Context, db *corecosmosstoragetesting.MockResourcesDBClient, fixture *operationtesting.ExternalAuthTestFixture) {
				op, err := db.Operations(operationtesting.TestSubscriptionID).Get(ctx, operationtesting.TestOperationName)
				require.NoError(t, err)
				assert.Equal(t, coreapi.ProvisioningStateAccepted, op.Status)
			},
		},
		{
			name:         "DeletionTimestamp set skips reconciliation",
			externalAuth: externalAuthWithDeletionTimestamp,
			verify: func(t *testing.T, ctx context.Context, db *corecosmosstoragetesting.MockResourcesDBClient, fixture *operationtesting.ExternalAuthTestFixture) {
				op, err := db.Operations(operationtesting.TestSubscriptionID).Get(ctx, operationtesting.TestOperationName)
				require.NoError(t, err)
				assert.Equal(t, coreapi.ProvisioningStateAccepted, op.Status)
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			ctx = utils.ContextWithLogger(ctx, testr.New(t))
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			fixture := operationtesting.NewExternalAuthTestFixture()
			cluster := fixture.NewCluster()
			externalAuth := tt.externalAuth(fixture)
			operation := fixture.NewOperation(cosmosstorageutils.OperationRequestCreate)

			resources := []any{cluster, externalAuth, operation}

			mockResourcesDBClient, err := corecosmosstoragetesting.NewMockResourcesDBClientWithResources(ctx, resources)
			require.NoError(t, err)

			var mockCSClient ocm.ClusterServiceClientSpec
			if tt.setupCSMock != nil {
				mockCSClient = tt.setupCSMock(ctrl, fixture)
			} else {
				mockCSClient = ocm.NewMockClusterServiceClientSpec(ctrl)
			}

			controller := &operationExternalAuthCreate{
				clock:                  utilsclock.RealClock{},
				resourcesDBClient:      mockResourcesDBClient,
				activeOperationsLister: &corelistertesting.DBActiveOperationLister{ResourcesDBClient: mockResourcesDBClient},
				externalAuthLister:     &corelistertesting.DBExternalAuthLister{ResourcesDBClient: mockResourcesDBClient},
				clusterServiceClient:   mockCSClient,
				notificationClient:     nil,
			}

			err = controller.SynchronizeOperation(ctx, fixture.OperationKey())
			if tt.expectError {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)

			if tt.verify != nil {
				tt.verify(t, ctx, mockResourcesDBClient, fixture)
			}
		})
	}
}
