package service

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	perrors "github.com/pingcap/errors"
	"github.com/stretchr/testify/require"
	cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v3"
	"google.golang.org/api/container/v1"
	gkehub "google.golang.org/api/gkehub/v1beta"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/option"
	"google.golang.org/api/serviceusage/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-agent/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

type fakeGKEPolicyControlClient struct {
	enabledServices    []string
	missingServices    []string
	membership         string
	policyStatus       GKEPolicyControllerStatus
	err                error
	projectIdentityErr error
	serviceStatusErr   error
	serviceStartErr    error
	fleetStatusErr     error
	fleetStartErr      error
	policyStatusErr    error
	policyStartErr     error

	projectIdentities map[string]GKEPolicyControlProjectIdentity

	servicesProjectID string
	serviceStartOps   []string
	fleetProjectID    string
	fleetLocation     string
	fleetClusterName  string
	fleetHostProject  string
	fleetStartOps     []string
	policyProjectID   string
	policyMembership  string
	policyStartOps    []string

	serviceStatusCalls   int
	serviceStartCalls    int
	fleetStatusCalls     int
	fleetStartCalls      int
	policyStatusCalls    int
	policyStartCalls     int
	projectIdentityCalls []string
}

func (f *fakeGKEPolicyControlClient) GetProjectIdentity(_ context.Context, project string) (GKEPolicyControlProjectIdentity, error) {
	f.projectIdentityCalls = append(f.projectIdentityCalls, project)
	if f.projectIdentityErr != nil {
		return GKEPolicyControlProjectIdentity{}, f.projectIdentityErr
	}
	if identity, ok := f.projectIdentities[project]; ok {
		if identity.Input == "" {
			identity.Input = project
		}
		return identity, nil
	}
	return GKEPolicyControlProjectIdentity{
		Input:         project,
		ProjectID:     project,
		ProjectNumber: project,
	}, nil
}

func (f *fakeGKEPolicyControlClient) GetServicesStatus(_ context.Context, projectID string, services []string) ([]string, []string, error) {
	f.serviceStatusCalls++
	f.servicesProjectID = projectID
	if f.serviceStatusErr != nil {
		return nil, nil, f.serviceStatusErr
	}
	if f.err != nil {
		return nil, nil, f.err
	}
	if f.enabledServices == nil && f.missingServices == nil {
		return append([]string{}, services...), nil, nil
	}
	return append([]string{}, f.enabledServices...), append([]string{}, f.missingServices...), nil
}

func (f *fakeGKEPolicyControlClient) StartEnableServices(_ context.Context, projectID string, services []string) ([]string, error) {
	f.serviceStartCalls++
	f.servicesProjectID = projectID
	f.missingServices = append([]string{}, services...)
	if f.serviceStartErr != nil {
		return nil, f.serviceStartErr
	}
	if f.err != nil {
		return nil, f.err
	}
	if f.serviceStartOps != nil {
		return append([]string{}, f.serviceStartOps...), nil
	}
	return []string{"serviceusage-op"}, nil
}

func (f *fakeGKEPolicyControlClient) GetFleetMembership(_ context.Context, projectID, location, clusterName string) (string, error) {
	f.fleetStatusCalls++
	f.fleetProjectID = projectID
	f.fleetLocation = location
	f.fleetClusterName = clusterName
	if f.fleetStatusErr != nil {
		return "", f.fleetStatusErr
	}
	if f.err != nil {
		return "", f.err
	}
	return f.membership, nil
}

func (f *fakeGKEPolicyControlClient) StartEnableFleet(_ context.Context, projectID, location, clusterName, fleetProjectID string) ([]string, error) {
	f.fleetStartCalls++
	f.fleetProjectID = projectID
	f.fleetLocation = location
	f.fleetClusterName = clusterName
	f.fleetHostProject = fleetProjectID
	if f.fleetStartErr != nil {
		return nil, f.fleetStartErr
	}
	if f.err != nil {
		return nil, f.err
	}
	if f.fleetStartOps != nil {
		return append([]string{}, f.fleetStartOps...), nil
	}
	return []string{"container-op"}, nil
}

func (f *fakeGKEPolicyControlClient) GetPolicyController(_ context.Context, fleetProjectID, membership string) (GKEPolicyControllerStatus, error) {
	f.policyStatusCalls++
	f.policyProjectID = fleetProjectID
	f.policyMembership = membership
	if f.policyStatusErr != nil {
		return GKEPolicyControllerStatus{}, f.policyStatusErr
	}
	if f.err != nil {
		return GKEPolicyControllerStatus{}, f.err
	}
	return f.policyStatus, nil
}

func (f *fakeGKEPolicyControlClient) StartEnablePolicyController(_ context.Context, fleetProjectID, membership string) (string, []string, error) {
	f.policyStartCalls++
	f.policyProjectID = fleetProjectID
	f.policyMembership = membership
	if f.policyStartErr != nil {
		return "", nil, f.policyStartErr
	}
	if f.err != nil {
		return "", nil, f.err
	}
	if f.policyStartOps != nil {
		return gkePolicyControllerFeaturePath(fleetProjectID), append([]string{}, f.policyStartOps...), nil
	}
	return gkePolicyControllerFeaturePath(fleetProjectID), []string{"gkehub-op"}, nil
}

func testGKEPolicyControlTarget() gkePolicyControlTarget {
	return gkePolicyControlTarget{
		ProjectID:      "cluster-project",
		Location:       "us-west1",
		ClusterName:    "dedicated-dev-us-west1-abc",
		FleetProjectID: "cluster-project",
		MembershipID:   "membership-a",
	}
}

func testGKEPolicyControlRequest() *apiv1.EnableGKEPolicyControlRequest {
	return &apiv1.EnableGKEPolicyControlRequest{
		ClusterId:      "123",
		ProjectId:      "cluster-project",
		Location:       "us-west1",
		GkeClusterName: "dedicated-dev-us-west1-abc",
		FleetProjectId: "cluster-project",
		MembershipId:   "membership-a",
	}
}

func testGKEPolicyControlGetRequest() *apiv1.GetGKEPolicyControlRequest {
	return &apiv1.GetGKEPolicyControlRequest{
		ClusterId:      "123",
		ProjectId:      "cluster-project",
		Location:       "us-west1",
		GkeClusterName: "dedicated-dev-us-west1-abc",
		FleetProjectId: "cluster-project",
		MembershipId:   "membership-a",
	}
}

func readyGKEPolicyControllerStatus(feature string) GKEPolicyControllerStatus {
	return GKEPolicyControllerStatus{
		FeaturePath:            feature,
		DesiredEnabled:         true,
		MutationEnabled:        true,
		LifecycleState:         "ACTIVE",
		MutationComponentState: "ACTIVE",
	}
}

func desiredGKEPolicyControllerStatus(feature, lifecycleState, mutationComponentState string) GKEPolicyControllerStatus {
	return GKEPolicyControllerStatus{
		FeaturePath:            feature,
		DesiredEnabled:         true,
		MutationEnabled:        true,
		LifecycleState:         lifecycleState,
		MutationComponentState: mutationComponentState,
	}
}

func newGKEPolicyControlTestService(t *testing.T, opts ...Option) *AgentService {
	t.Helper()
	scheme := newGKEHeterogeneousTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).Build()
	defaultOpts := []Option{
		WithClusterNamespace(buildNamespace("123")),
		WithProvider(consts.ProviderTypeGKE),
		WithGKEPolicyControlTarget("cluster-project", "us-west1", "dedicated-dev-us-west1-abc", "cluster-project"),
	}
	defaultOpts = append(defaultOpts, opts...)
	return NewAgentService(cli, defaultOpts...)
}

func TestAgentService_EnableGKEPolicyControlStartsServicesWhenMissing(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{
		missingServices: append([]string{}, gkePolicyControlRequiredServices...),
	}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLING, resp.State)
	require.Equal(t, gkePolicyControlStepEnableServices, resp.CurrentStep)
	require.Equal(t, gkePolicyControlRequiredServices, resp.MissingServices)
	require.Equal(t, []string{"serviceusage-op"}, resp.OperationNames)
	require.Equal(t, "cluster-project", fakeClient.servicesProjectID)
	require.Equal(t, 1, fakeClient.serviceStartCalls)
	require.Zero(t, fakeClient.fleetStartCalls)
	require.Zero(t, fakeClient.policyStartCalls)
}

func TestAgentService_EnableGKEPolicyControlStartsFleetAfterServicesEnabled(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLING, resp.State)
	require.Equal(t, gkePolicyControlStepEnableFleet, resp.CurrentStep)
	require.Equal(t, []string{"container-op"}, resp.OperationNames)
	require.Equal(t, "cluster-project", fakeClient.fleetProjectID)
	require.Equal(t, "cluster-project", fakeClient.fleetHostProject)
	require.Equal(t, 1, fakeClient.fleetStartCalls)
	require.Zero(t, fakeClient.policyStartCalls)
}

func TestAgentService_EnableGKEPolicyControlStartsPolicyControllerAfterFleetReady(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	fakeClient := &fakeGKEPolicyControlClient{membership: membership}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLING, resp.State)
	require.Equal(t, gkePolicyControlStepEnablePolicyController, resp.CurrentStep)
	require.Equal(t, membership, resp.Membership)
	require.Equal(t, gkePolicyControllerFeaturePath("cluster-project"), resp.Feature)
	require.Equal(t, []string{"gkehub-op"}, resp.OperationNames)
	require.Equal(t, "cluster-project", fakeClient.policyProjectID)
	require.Equal(t, membership, fakeClient.policyMembership)
	require.Equal(t, 1, fakeClient.policyStartCalls)
}

func TestAgentService_EnableGKEPolicyControlReturnsFailedPreconditionForPolicyControlIAMPermissionDenied(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	permissionErr := status.Error(codes.PermissionDenied, "gcp iam denied")
	tests := []struct {
		name       string
		fakeClient *fakeGKEPolicyControlClient
		assert     func(*testing.T, *fakeGKEPolicyControlClient)
	}{
		{
			name: "enable services",
			fakeClient: &fakeGKEPolicyControlClient{
				missingServices: append([]string{}, gkePolicyControlRequiredServices...),
				serviceStartErr: permissionErr,
			},
			assert: func(t *testing.T, fakeClient *fakeGKEPolicyControlClient) {
				t.Helper()
				require.Equal(t, 1, fakeClient.serviceStartCalls)
				require.Zero(t, fakeClient.fleetStartCalls)
				require.Zero(t, fakeClient.policyStartCalls)
			},
		},
		{
			name: "enable fleet",
			fakeClient: &fakeGKEPolicyControlClient{
				fleetStartErr: permissionErr,
			},
			assert: func(t *testing.T, fakeClient *fakeGKEPolicyControlClient) {
				t.Helper()
				require.Equal(t, 1, fakeClient.fleetStartCalls)
				require.Zero(t, fakeClient.policyStartCalls)
			},
		},
		{
			name: "enable policycontroller feature",
			fakeClient: &fakeGKEPolicyControlClient{
				membership:     membership,
				policyStartErr: permissionErr,
			},
			assert: func(t *testing.T, fakeClient *fakeGKEPolicyControlClient) {
				t.Helper()
				require.Equal(t, 1, fakeClient.policyStartCalls)
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			svc := newGKEPolicyControlTestService(t,
				WithGKEPolicyControlClient(tt.fakeClient),
			)

			_, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

			require.Equal(t, codes.FailedPrecondition, status.Code(err))
			require.Contains(t, status.Convert(err).Message(), gkePolicyControlIAMPermissionMessage)
			require.Contains(t, status.Convert(err).Message(), "gcp iam denied")
			tt.assert(t, tt.fakeClient)
		})
	}
}

func TestAgentService_GKEPolicyControlDescribeReturnsFailedPreconditionForIAMPermissionDenied(t *testing.T) {
	readErr := googleAPIStatusErrorf(
		&googleapi.Error{Code: http.StatusForbidden, Message: "AUTH_PERMISSION_DENIED"},
		codes.Internal,
		"get service %s",
		gkeHubAPIService,
	)
	tests := []struct {
		name string
		run  func(*AgentService) (*apiv1.GKEPolicyControl, error)
	}{
		{
			name: "enable",
			run: func(svc *AgentService) (*apiv1.GKEPolicyControl, error) {
				return svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())
			},
		},
		{
			name: "get",
			run: func(svc *AgentService) (*apiv1.GKEPolicyControl, error) {
				return svc.GetGKEPolicyControl(context.Background(), testGKEPolicyControlGetRequest())
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fakeClient := &fakeGKEPolicyControlClient{serviceStatusErr: readErr}
			svc := newGKEPolicyControlTestService(t,
				WithGKEPolicyControlClient(fakeClient),
			)

			_, err := tt.run(svc)

			require.Equal(t, codes.FailedPrecondition, status.Code(err))
			require.Contains(t, status.Convert(err).Message(), gkePolicyControlIAMPermissionMessage)
			require.Contains(t, status.Convert(err).Message(), "get service "+gkeHubAPIService)
			require.Equal(t, 1, fakeClient.serviceStatusCalls)
			require.Zero(t, fakeClient.serviceStartCalls)
			require.Zero(t, fakeClient.fleetStartCalls)
			require.Zero(t, fakeClient.policyStartCalls)
		})
	}
}

func TestAgentService_EnableGKEPolicyControlReturnsEnabledWhenReady(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	feature := gkePolicyControllerFeaturePath("cluster-project")
	fakeClient := &fakeGKEPolicyControlClient{
		membership:   membership,
		policyStatus: readyGKEPolicyControllerStatus(feature),
	}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLED, resp.State)
	require.Equal(t, gkePolicyControlStepCompleted, resp.CurrentStep)
	require.Equal(t, membership, resp.Membership)
	require.Equal(t, feature, resp.Feature)
	require.True(t, resp.MutationEnabled)
	require.Empty(t, resp.OperationNames)
	require.Zero(t, fakeClient.serviceStartCalls)
	require.Zero(t, fakeClient.fleetStartCalls)
	require.Zero(t, fakeClient.policyStartCalls)
}

func TestAgentService_GetGKEPolicyControlReturnsEnablingWhenPolicyControllerInstalling(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	feature := gkePolicyControllerFeaturePath("cluster-project")
	fakeClient := &fakeGKEPolicyControlClient{
		membership:   membership,
		policyStatus: desiredGKEPolicyControllerStatus(feature, "INSTALLING", "ACTIVE"),
	}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.GetGKEPolicyControl(context.Background(), testGKEPolicyControlGetRequest())

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLING, resp.State)
	require.Equal(t, gkePolicyControlStepEnablePolicyController, resp.CurrentStep)
	require.True(t, resp.MutationEnabled)
	require.Equal(t, feature, resp.Feature)
	require.Equal(t, "cluster-project", fakeClient.fleetProjectID)
	require.Equal(t, "us-west1", fakeClient.fleetLocation)
	require.Equal(t, "dedicated-dev-us-west1-abc", fakeClient.fleetClusterName)
	require.Equal(t, "cluster-project", fakeClient.policyProjectID)
}

func TestAgentService_GetGKEPolicyControlAcceptsEquivalentFleetProjectNumber(t *testing.T) {
	membership := "projects/123456789/locations/us-west1/memberships/membership-a"
	feature := gkePolicyControllerFeaturePath("123456789")
	fakeClient := &fakeGKEPolicyControlClient{
		membership:   membership,
		policyStatus: desiredGKEPolicyControllerStatus(feature, "ACTIVE", "ACTIVE"),
		projectIdentities: map[string]GKEPolicyControlProjectIdentity{
			"cluster-project": {ProjectID: "cluster-project", ProjectNumber: "123456789"},
			"123456789":       {ProjectID: "cluster-project", ProjectNumber: "123456789"},
		},
	}
	req := testGKEPolicyControlGetRequest()
	req.FleetProjectId = "123456789"
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.GetGKEPolicyControl(context.Background(), req)

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLED, resp.State)
	require.Equal(t, []string{"123456789", "cluster-project", "123456789", "cluster-project"}, fakeClient.projectIdentityCalls)
}

func TestAgentService_GetGKEPolicyControlRejectsUnconfiguredFleetProjectNumber(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{
		projectIdentities: map[string]GKEPolicyControlProjectIdentity{
			"cluster-project": {ProjectID: "cluster-project", ProjectNumber: "123456789"},
			"987654321":       {ProjectID: "other-project", ProjectNumber: "987654321"},
		},
	}
	req := testGKEPolicyControlGetRequest()
	req.FleetProjectId = "987654321"
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	_, err := svc.GetGKEPolicyControl(context.Background(), req)

	require.Equal(t, codes.PermissionDenied, status.Code(err))
	require.Contains(t, status.Convert(err).Message(), "fleet project")
	require.Equal(t, []string{"987654321", "cluster-project"}, fakeClient.projectIdentityCalls)
	require.Zero(t, fakeClient.serviceStatusCalls)
}

func TestAgentService_GetGKEPolicyControlCanonicalizesProjectResourceName(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{
		membership: "projects/cluster-project/locations/us-west1/memberships/membership-a",
		policyStatus: desiredGKEPolicyControllerStatus(
			gkePolicyControllerFeaturePath("cluster-project"),
			"ACTIVE",
			"ACTIVE",
		),
	}
	req := testGKEPolicyControlGetRequest()
	req.ProjectId = "projects/cluster-project"
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.GetGKEPolicyControl(context.Background(), req)

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLED, resp.State)
	require.Empty(t, fakeClient.projectIdentityCalls)
}

func TestAgentService_GetGKEPolicyControlReturnsEnablingWhenPolicyControllerMembershipStateMissing(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	feature := gkePolicyControllerFeaturePath("cluster-project")
	fakeClient := &fakeGKEPolicyControlClient{
		membership: membership,
		policyStatus: GKEPolicyControllerStatus{
			FeaturePath:     feature,
			DesiredEnabled:  true,
			MutationEnabled: true,
		},
	}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.GetGKEPolicyControl(context.Background(), testGKEPolicyControlGetRequest())

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLING, resp.State)
	require.Equal(t, gkePolicyControlStepEnablePolicyController, resp.CurrentStep)
	require.NotEqual(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLED, resp.State)
}

func TestAgentService_GetGKEPolicyControlReturnsDegradedForPolicyControllerErrorStates(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	feature := gkePolicyControllerFeaturePath("cluster-project")
	tests := []struct {
		name           string
		lifecycleState string
	}{
		{name: "cluster error", lifecycleState: "CLUSTER_ERROR"},
		{name: "hub error", lifecycleState: "HUB_ERROR"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fakeClient := &fakeGKEPolicyControlClient{
				membership:   membership,
				policyStatus: desiredGKEPolicyControllerStatus(feature, tt.lifecycleState, "ACTIVE"),
			}
			svc := newGKEPolicyControlTestService(t,
				WithGKEPolicyControlClient(fakeClient),
			)

			resp, err := svc.GetGKEPolicyControl(context.Background(), testGKEPolicyControlGetRequest())

			require.NoError(t, err)
			require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_DEGRADED, resp.State)
			require.Equal(t, gkePolicyControlStepEnablePolicyController, resp.CurrentStep)
		})
	}
}

func TestAgentService_GetGKEPolicyControlDoesNotEnableUntilMutationComponentActive(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	feature := gkePolicyControllerFeaturePath("cluster-project")
	fakeClient := &fakeGKEPolicyControlClient{
		membership:   membership,
		policyStatus: desiredGKEPolicyControllerStatus(feature, "ACTIVE", "UPDATING"),
	}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.GetGKEPolicyControl(context.Background(), testGKEPolicyControlGetRequest())

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLING, resp.State)
	require.NotEqual(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_ENABLED, resp.State)
}

func TestAgentService_GetGKEPolicyControlDescribesWithoutStarting(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{
		missingServices: append([]string{}, gkePolicyControlRequiredServices...),
	}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.GetGKEPolicyControl(context.Background(), testGKEPolicyControlGetRequest())

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_DISABLED, resp.State)
	require.Equal(t, gkePolicyControlStepEnableServices, resp.CurrentStep)
	require.Equal(t, gkePolicyControlRequiredServices, resp.MissingServices)
	require.Equal(t, "cluster-project", fakeClient.servicesProjectID)
	require.Zero(t, fakeClient.serviceStartCalls)
	require.Zero(t, fakeClient.fleetStartCalls)
	require.Zero(t, fakeClient.policyStartCalls)
}

func TestAgentService_EnableGKEPolicyControlUsesRequestedMembershipAsPostEnableExpectation(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/requested-membership"
	fakeClient := &fakeGKEPolicyControlClient{membership: membership}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.EnableGKEPolicyControl(context.Background(), &apiv1.EnableGKEPolicyControlRequest{
		ClusterId:      "123",
		ProjectId:      "cluster-project",
		Location:       "us-west1",
		GkeClusterName: "dedicated-dev-us-west1-abc",
		FleetProjectId: "cluster-project",
		MembershipId:   "requested-membership",
	})

	require.NoError(t, err)
	require.Equal(t, gkePolicyControlStepEnablePolicyController, resp.CurrentStep)
	require.Equal(t, membership, resp.Membership)
	require.Equal(t, membership, fakeClient.policyMembership)
}

func TestAgentService_EnableGKEPolicyControlReturnsDegradedForRequestedMembershipMismatch(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{
		membership: "projects/cluster-project/locations/us-west1/memberships/actual-membership",
	}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	resp, err := svc.EnableGKEPolicyControl(context.Background(), &apiv1.EnableGKEPolicyControlRequest{
		ClusterId:      "123",
		ProjectId:      "cluster-project",
		Location:       "us-west1",
		GkeClusterName: "dedicated-dev-us-west1-abc",
		FleetProjectId: "cluster-project",
		MembershipId:   "other-membership",
	})

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_DEGRADED, resp.State)
	require.Equal(t, gkePolicyControlStepEnableFleet, resp.CurrentStep)
	require.Contains(t, resp.Message, "does not match this cluster-agent target")
	require.Zero(t, fakeClient.policyStatusCalls)
	require.Zero(t, fakeClient.policyStartCalls)
}

func TestAgentService_GetGKEPolicyControlReturnsDegradedForActualMembershipMismatch(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{
		membership: "projects/cluster-project/locations/us-west1/memberships/actual-membership",
	}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	req := testGKEPolicyControlGetRequest()
	req.MembershipId = "other-membership"
	resp, err := svc.GetGKEPolicyControl(context.Background(), req)

	require.NoError(t, err)
	require.Equal(t, apiv1.GKEPolicyControlState_GKE_POLICY_CONTROL_STATE_DEGRADED, resp.State)
	require.Equal(t, gkePolicyControlStepEnableFleet, resp.CurrentStep)
	require.Contains(t, resp.Message, "does not match this cluster-agent target")
	require.Zero(t, fakeClient.policyStatusCalls)
	require.Zero(t, fakeClient.policyStartCalls)
}

func TestAgentService_EnableGKEPolicyControlRejectsSplitFleetProject(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
		WithGKEPolicyControlTarget("cluster-project", "us-west1", "dedicated-dev-us-west1-abc", "fleet-project"),
	)

	_, err := svc.EnableGKEPolicyControl(context.Background(), &apiv1.EnableGKEPolicyControlRequest{
		ClusterId:      "123",
		ProjectId:      "cluster-project",
		Location:       "us-west1",
		GkeClusterName: "dedicated-dev-us-west1-abc",
		FleetProjectId: "fleet-project",
	})

	require.Equal(t, codes.FailedPrecondition, status.Code(err))
	require.Zero(t, fakeClient.serviceStatusCalls)
}

func TestAgentService_EnableGKEPolicyControlRejectsNonGKEProvider(t *testing.T) {
	svc := newGKEPolicyControlTestService(t,
		WithProvider(consts.ProviderTypeEKS),
		WithGKEPolicyControlClient(&fakeGKEPolicyControlClient{}),
	)

	_, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.Equal(t, codes.FailedPrecondition, status.Code(err))
}

func TestAgentService_EnableGKEPolicyControlReturnsClientError(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{err: errors.New("boom")}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	_, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.ErrorContains(t, err, "boom")
}

func TestAgentService_EnableGKEPolicyControlRejectsMismatchedClusterNamespace(t *testing.T) {
	fakeClient := &fakeGKEPolicyControlClient{}
	svc := newGKEPolicyControlTestService(t,
		WithGKEPolicyControlClient(fakeClient),
	)

	req := testGKEPolicyControlRequest()
	req.ClusterId = "456"
	_, err := svc.EnableGKEPolicyControl(context.Background(), req)

	require.Equal(t, codes.PermissionDenied, status.Code(err))
	require.Zero(t, fakeClient.serviceStatusCalls)
	require.Zero(t, fakeClient.serviceStartCalls)
	require.Zero(t, fakeClient.fleetStartCalls)
	require.Zero(t, fakeClient.policyStartCalls)
}

func TestAgentService_GKEPolicyControlRejectsUnconfiguredAgentTarget(t *testing.T) {
	scheme := newGKEHeterogeneousTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).Build()
	fakeClient := &fakeGKEPolicyControlClient{}
	svc := NewAgentService(cli,
		WithClusterNamespace(buildNamespace("123")),
		WithProvider(consts.ProviderTypeGKE),
		WithGKEPolicyControlClient(fakeClient),
	)

	_, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.Equal(t, codes.FailedPrecondition, status.Code(err))
	require.Contains(t, status.Convert(err).Message(), "target is not configured")
	require.Zero(t, fakeClient.serviceStatusCalls)
}

func TestAgentService_GKEPolicyControlTreatsEmptyConfiguredTargetAsUnconfigured(t *testing.T) {
	scheme := newGKEHeterogeneousTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).Build()
	fakeClient := &fakeGKEPolicyControlClient{}
	svc := NewAgentService(cli,
		WithClusterNamespace(buildNamespace("123")),
		WithProvider(consts.ProviderTypeGKE),
		WithGKEPolicyControlClient(fakeClient),
		WithGKEPolicyControlTarget(" ", "", "", ""),
	)

	_, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.Equal(t, codes.FailedPrecondition, status.Code(err))
	require.Contains(t, status.Convert(err).Message(), "target is not configured")
	require.Zero(t, fakeClient.serviceStatusCalls)
}

func TestAgentService_GKEPolicyControlRejectsIncompleteConfiguredTarget(t *testing.T) {
	scheme := newGKEHeterogeneousTestScheme(t)
	cli := fake.NewClientBuilder().WithScheme(scheme).Build()
	fakeClient := &fakeGKEPolicyControlClient{}
	svc := NewAgentService(cli,
		WithClusterNamespace(buildNamespace("123")),
		WithProvider(consts.ProviderTypeGKE),
		WithGKEPolicyControlClient(fakeClient),
		WithGKEPolicyControlTarget("cluster-project", "", "", ""),
	)

	_, err := svc.EnableGKEPolicyControl(context.Background(), testGKEPolicyControlRequest())

	require.Equal(t, codes.FailedPrecondition, status.Code(err))
	require.Contains(t, status.Convert(err).Message(), "target is incomplete")
	require.Zero(t, fakeClient.serviceStatusCalls)
}

func TestAgentService_GKEPolicyControlRejectsRequestTargetMismatch(t *testing.T) {
	tests := []struct {
		name   string
		mutate func(*apiv1.EnableGKEPolicyControlRequest)
	}{
		{
			name: "project",
			mutate: func(req *apiv1.EnableGKEPolicyControlRequest) {
				req.ProjectId = "other-project"
				req.FleetProjectId = "other-project"
			},
		},
		{
			name: "location",
			mutate: func(req *apiv1.EnableGKEPolicyControlRequest) {
				req.Location = "us-east1"
			},
		},
		{
			name: "gke cluster name",
			mutate: func(req *apiv1.EnableGKEPolicyControlRequest) {
				req.GkeClusterName = "other-gke"
			},
		},
		{
			name: "fleet project",
			mutate: func(req *apiv1.EnableGKEPolicyControlRequest) {
				req.FleetProjectId = "other-project"
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fakeClient := &fakeGKEPolicyControlClient{}
			svc := newGKEPolicyControlTestService(t, WithGKEPolicyControlClient(fakeClient))
			req := testGKEPolicyControlRequest()
			tt.mutate(req)

			_, err := svc.EnableGKEPolicyControl(context.Background(), req)

			require.Equal(t, codes.PermissionDenied, status.Code(err))
			require.Contains(t, status.Convert(err).Message(), "does not match this cluster-agent target")
			require.Zero(t, fakeClient.serviceStatusCalls)
		})
	}
}

func TestAgentService_GKEPolicyControlRejectsMissingRequestTarget(t *testing.T) {
	tests := []struct {
		name    string
		mutate  func(*apiv1.EnableGKEPolicyControlRequest, *apiv1.GetGKEPolicyControlRequest)
		runCall func(*AgentService, *apiv1.EnableGKEPolicyControlRequest, *apiv1.GetGKEPolicyControlRequest) error
	}{
		{
			name: "enable missing project_id",
			mutate: func(enableReq *apiv1.EnableGKEPolicyControlRequest, _ *apiv1.GetGKEPolicyControlRequest) {
				enableReq.ProjectId = ""
			},
			runCall: func(svc *AgentService, enableReq *apiv1.EnableGKEPolicyControlRequest, _ *apiv1.GetGKEPolicyControlRequest) error {
				_, err := svc.EnableGKEPolicyControl(context.Background(), enableReq)
				return err
			},
		},
		{
			name: "enable missing location",
			mutate: func(enableReq *apiv1.EnableGKEPolicyControlRequest, _ *apiv1.GetGKEPolicyControlRequest) {
				enableReq.Location = ""
			},
			runCall: func(svc *AgentService, enableReq *apiv1.EnableGKEPolicyControlRequest, _ *apiv1.GetGKEPolicyControlRequest) error {
				_, err := svc.EnableGKEPolicyControl(context.Background(), enableReq)
				return err
			},
		},
		{
			name: "enable missing gke_cluster_name",
			mutate: func(enableReq *apiv1.EnableGKEPolicyControlRequest, _ *apiv1.GetGKEPolicyControlRequest) {
				enableReq.GkeClusterName = ""
			},
			runCall: func(svc *AgentService, enableReq *apiv1.EnableGKEPolicyControlRequest, _ *apiv1.GetGKEPolicyControlRequest) error {
				_, err := svc.EnableGKEPolicyControl(context.Background(), enableReq)
				return err
			},
		},
		{
			name: "enable missing fleet_project_id",
			mutate: func(enableReq *apiv1.EnableGKEPolicyControlRequest, _ *apiv1.GetGKEPolicyControlRequest) {
				enableReq.FleetProjectId = ""
			},
			runCall: func(svc *AgentService, enableReq *apiv1.EnableGKEPolicyControlRequest, _ *apiv1.GetGKEPolicyControlRequest) error {
				_, err := svc.EnableGKEPolicyControl(context.Background(), enableReq)
				return err
			},
		},
		{
			name: "get missing project_id",
			mutate: func(_ *apiv1.EnableGKEPolicyControlRequest, getReq *apiv1.GetGKEPolicyControlRequest) {
				getReq.ProjectId = ""
			},
			runCall: func(svc *AgentService, _ *apiv1.EnableGKEPolicyControlRequest, getReq *apiv1.GetGKEPolicyControlRequest) error {
				_, err := svc.GetGKEPolicyControl(context.Background(), getReq)
				return err
			},
		},
		{
			name: "get missing location",
			mutate: func(_ *apiv1.EnableGKEPolicyControlRequest, getReq *apiv1.GetGKEPolicyControlRequest) {
				getReq.Location = ""
			},
			runCall: func(svc *AgentService, _ *apiv1.EnableGKEPolicyControlRequest, getReq *apiv1.GetGKEPolicyControlRequest) error {
				_, err := svc.GetGKEPolicyControl(context.Background(), getReq)
				return err
			},
		},
		{
			name: "get missing gke_cluster_name",
			mutate: func(_ *apiv1.EnableGKEPolicyControlRequest, getReq *apiv1.GetGKEPolicyControlRequest) {
				getReq.GkeClusterName = ""
			},
			runCall: func(svc *AgentService, _ *apiv1.EnableGKEPolicyControlRequest, getReq *apiv1.GetGKEPolicyControlRequest) error {
				_, err := svc.GetGKEPolicyControl(context.Background(), getReq)
				return err
			},
		},
		{
			name: "get missing fleet_project_id",
			mutate: func(_ *apiv1.EnableGKEPolicyControlRequest, getReq *apiv1.GetGKEPolicyControlRequest) {
				getReq.FleetProjectId = ""
			},
			runCall: func(svc *AgentService, _ *apiv1.EnableGKEPolicyControlRequest, getReq *apiv1.GetGKEPolicyControlRequest) error {
				_, err := svc.GetGKEPolicyControl(context.Background(), getReq)
				return err
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fakeClient := &fakeGKEPolicyControlClient{}
			svc := newGKEPolicyControlTestService(t, WithGKEPolicyControlClient(fakeClient))
			enableReq := testGKEPolicyControlRequest()
			getReq := testGKEPolicyControlGetRequest()
			tt.mutate(enableReq, getReq)

			err := tt.runCall(svc, enableReq, getReq)

			require.Equal(t, codes.InvalidArgument, status.Code(err))
			require.Zero(t, fakeClient.serviceStatusCalls)
		})
	}
}

func TestGKEPolicyControlMembershipValidation(t *testing.T) {
	target := testGKEPolicyControlTarget()
	fakeClient := &fakeGKEPolicyControlClient{
		projectIdentities: map[string]GKEPolicyControlProjectIdentity{
			"cluster-project": {ProjectID: "cluster-project", ProjectNumber: "123456789"},
			"123456789":       {ProjectID: "cluster-project", ProjectNumber: "123456789"},
			"987654321":       {ProjectID: "other-project", ProjectNumber: "987654321"},
		},
	}

	require.NoError(t, validateGKEPolicyControlMembership(context.Background(), fakeClient, "projects/cluster-project/locations/us-west1/memberships/membership-a", target))
	require.NoError(t, validateGKEPolicyControlMembership(context.Background(), fakeClient, "projects/123456789/locations/us-west1/memberships/membership-a", target))
	require.NoError(t, validateGKEPolicyControlMembership(context.Background(), fakeClient, "projects/cluster-project/locations/global/memberships/membership-a", target))
	require.Equal(t, codes.FailedPrecondition, grpcCodeOfError(validateGKEPolicyControlMembership(context.Background(), fakeClient, "projects/other/locations/us-west1/memberships/membership-a", target)))
	require.Equal(t, codes.FailedPrecondition, grpcCodeOfError(validateGKEPolicyControlMembership(context.Background(), fakeClient, "projects/987654321/locations/us-west1/memberships/membership-a", gkePolicyControlTarget{
		ProjectID:      "123456789",
		Location:       "us-west1",
		ClusterName:    "dedicated-dev-us-west1-abc",
		FleetProjectID: "123456789",
		MembershipID:   "membership-a",
	})))
	require.Equal(t, codes.FailedPrecondition, grpcCodeOfError(validateGKEPolicyControlMembership(context.Background(), fakeClient, "not-a-membership", target)))

	target.MembershipID = ""
	require.NoError(t, validateGKEPolicyControlMembership(context.Background(), fakeClient, "projects/cluster-project/locations/us-west1/memberships/gke-generated-membership", target))
}

func TestGKEPolicyControlMembershipValidationRejectsUnverifiedProject(t *testing.T) {
	target := testGKEPolicyControlTarget()
	fakeClient := &fakeGKEPolicyControlClient{projectIdentityErr: errors.New("lookup denied")}

	err := validateGKEPolicyControlMembership(context.Background(), fakeClient, "projects/123456789/locations/us-west1/memberships/membership-a", target)

	require.Equal(t, codes.FailedPrecondition, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "cannot be verified")
	require.Equal(t, []string{"123456789"}, fakeClient.projectIdentityCalls)
}

func TestGoogleGKEPolicyControlClientGetProjectIdentityReturnsErrorForNilResponse(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodGet, r.Method)
		require.Equal(t, "/v3/projects/cluster-project", r.URL.Path)
		writeJSON(t, w, nil)
	}))
	defer server.Close()

	resourceManagerSvc, err := cloudresourcemanager.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{resourceManagerSvc: resourceManagerSvc}

	got, err := cli.GetProjectIdentity(context.Background(), "cluster-project")

	require.Error(t, err)
	require.Equal(t, codes.Internal, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "get project cluster-project returned nil response")
	require.Empty(t, got)
}

func TestGoogleGKEPolicyControlClientStartEnableServicesIgnoresConflict(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodPost, r.Method)
		require.Equal(t, "/v1/projects/cluster-project/services/"+gkeHubAPIService+":enable", r.URL.Path)
		http.Error(w, `{"error":{"code":409,"message":"operation in progress"}}`, http.StatusConflict)
	}))
	defer server.Close()

	serviceUsageSvc, err := serviceusage.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{serviceUsageSvc: serviceUsageSvc}

	ops, err := cli.StartEnableServices(context.Background(), "cluster-project", []string{gkeHubAPIService})

	require.NoError(t, err)
	require.Empty(t, ops)
}

func TestGoogleGKEPolicyControlClientStartEnableServicesReturnsConflictWhenServiceNotEnabled(t *testing.T) {
	getCalled := false
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodPost:
			require.Equal(t, "/v1/projects/cluster-project/services/"+gkeHubAPIService+":enable", r.URL.Path)
			http.Error(w, `{"error":{"code":409,"message":"already exists"}}`, http.StatusConflict)
		case http.MethodGet:
			getCalled = true
			require.Equal(t, "/v1/projects/cluster-project/services/"+gkeHubAPIService, r.URL.Path)
			writeJSON(t, w, &serviceusage.GoogleApiServiceusageV1Service{State: "DISABLED"})
		default:
			t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	serviceUsageSvc, err := serviceusage.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{serviceUsageSvc: serviceUsageSvc}

	ops, err := cli.StartEnableServices(context.Background(), "cluster-project", []string{gkeHubAPIService})

	require.Error(t, err)
	require.Equal(t, codes.AlreadyExists, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "service is not enabled")
	require.Empty(t, ops)
	require.True(t, getCalled)
}

func TestGoogleGKEPolicyControlClientStartEnableServicesTreatsConflictAsSuccessWhenServiceEnabled(t *testing.T) {
	getCalled := false
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodPost:
			require.Equal(t, "/v1/projects/cluster-project/services/"+gkeHubAPIService+":enable", r.URL.Path)
			http.Error(w, `{"error":{"code":409,"message":"already exists"}}`, http.StatusConflict)
		case http.MethodGet:
			getCalled = true
			require.Equal(t, "/v1/projects/cluster-project/services/"+gkeHubAPIService, r.URL.Path)
			writeJSON(t, w, &serviceusage.GoogleApiServiceusageV1Service{State: "ENABLED"})
		default:
			t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	serviceUsageSvc, err := serviceusage.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{serviceUsageSvc: serviceUsageSvc}

	ops, err := cli.StartEnableServices(context.Background(), "cluster-project", []string{gkeHubAPIService})

	require.NoError(t, err)
	require.Empty(t, ops)
	require.True(t, getCalled)
}

func TestGoogleGKEPolicyControlClientStartEnableFleetReturnsConflictWhenMembershipDoesNotMatch(t *testing.T) {
	clusterPath := gkeClusterPath("cluster-project", "us-west1", "dedicated-dev-us-west1-abc")
	getCalled := false
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodPut:
			require.Equal(t, "/v1/"+clusterPath, r.URL.Path)
			http.Error(w, `{"error":{"code":409,"message":"already exists"}}`, http.StatusConflict)
		case http.MethodGet:
			getCalled = true
			require.Equal(t, "/v1/"+clusterPath, r.URL.Path)
			writeJSON(t, w, &container.Cluster{
				Fleet: &container.Fleet{Membership: "projects/other-project/locations/us-west1/memberships/membership-a"},
			})
		default:
			t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	containerSvc, err := container.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{containerSvc: containerSvc}

	ops, err := cli.StartEnableFleet(context.Background(), "cluster-project", "us-west1", "dedicated-dev-us-west1-abc", "cluster-project")

	require.Error(t, err)
	require.Equal(t, codes.AlreadyExists, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "does not match fleet project")
	require.Empty(t, ops)
	require.True(t, getCalled)
}

func TestGoogleGKEPolicyControlClientStartEnableFleetTreatsConflictAsSuccessWhenMembershipMatches(t *testing.T) {
	clusterPath := gkeClusterPath("cluster-project", "us-west1", "dedicated-dev-us-west1-abc")
	getCalled := false
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodPut:
			require.Equal(t, "/v1/"+clusterPath, r.URL.Path)
			http.Error(w, `{"error":{"code":409,"message":"already exists"}}`, http.StatusConflict)
		case http.MethodGet:
			getCalled = true
			require.Equal(t, "/v1/"+clusterPath, r.URL.Path)
			writeJSON(t, w, &container.Cluster{
				Fleet: &container.Fleet{Membership: "projects/cluster-project/locations/us-west1/memberships/membership-a"},
			})
		default:
			t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	containerSvc, err := container.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{containerSvc: containerSvc}

	ops, err := cli.StartEnableFleet(context.Background(), "cluster-project", "us-west1", "dedicated-dev-us-west1-abc", "cluster-project")

	require.NoError(t, err)
	require.Empty(t, ops)
	require.True(t, getCalled)
}

func TestGoogleGKEPolicyControlClientGetFleetMembershipReturnsErrorForNilResponse(t *testing.T) {
	clusterPath := gkeClusterPath("cluster-project", "us-west1", "dedicated-dev-us-west1-abc")
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodGet, r.Method)
		require.Equal(t, "/v1/"+clusterPath, r.URL.Path)
		writeJSON(t, w, nil)
	}))
	defer server.Close()

	containerSvc, err := container.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{containerSvc: containerSvc}

	got, err := cli.GetFleetMembership(context.Background(), "cluster-project", "us-west1", "dedicated-dev-us-west1-abc")

	require.Error(t, err)
	require.Equal(t, codes.Internal, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "get gke cluster "+clusterPath+" returned nil response")
	require.Empty(t, got)
}

func TestGoogleGKEPolicyControlClientGetPolicyControllerReadsDesiredAndActualState(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodGet, r.Method)
		require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
		writeJSON(t, w, &gkehub.Feature{
			MembershipSpecs: map[string]gkehub.MembershipFeatureSpec{
				membership: {
					Policycontroller: &gkehub.PolicyControllerMembershipSpec{
						PolicyControllerHubConfig: &gkehub.PolicyControllerHubConfig{
							InstallSpec:     "INSTALL_SPEC_ENABLED",
							MutationEnabled: true,
						},
					},
				},
			},
			MembershipStates: map[string]gkehub.MembershipFeatureState{
				membership: {
					Policycontroller: &gkehub.PolicyControllerMembershipState{
						State: "ACTIVE",
						ComponentStates: map[string]gkehub.PolicyControllerOnClusterState{
							"mutation": {State: "INSTALLING"},
						},
					},
				},
			},
		})
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc}

	got, err := cli.GetPolicyController(context.Background(), "cluster-project", membership)

	require.NoError(t, err)
	require.Equal(t, featurePath, got.FeaturePath)
	require.True(t, got.DesiredEnabled)
	require.True(t, got.MutationEnabled)
	require.Equal(t, "ACTIVE", got.LifecycleState)
	require.Equal(t, "INSTALLING", got.MutationComponentState)
}

func TestGoogleGKEPolicyControlClientGetPolicyControllerReturnsErrorForNilResponse(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodGet, r.Method)
		require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
		writeJSON(t, w, nil)
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc}

	got, err := cli.GetPolicyController(context.Background(), "cluster-project", membership)

	require.Error(t, err)
	require.Equal(t, codes.Internal, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "get policycontroller feature "+featurePath+" returned nil response")
	require.Empty(t, got)
}

func TestGoogleGKEPolicyControlClientGetPolicyControllerMatchesCanonicalProjectNumberKey(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	canonicalMembership := "projects/123456789/locations/us-west1/memberships/membership-a"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodGet, r.Method)
		switch r.URL.Path {
		case "/v1beta/" + featurePath:
			writeJSON(t, w, &gkehub.Feature{
				MembershipSpecs: map[string]gkehub.MembershipFeatureSpec{
					canonicalMembership: {
						Policycontroller: &gkehub.PolicyControllerMembershipSpec{
							PolicyControllerHubConfig: &gkehub.PolicyControllerHubConfig{
								InstallSpec:     "INSTALL_SPEC_ENABLED",
								MutationEnabled: true,
							},
						},
					},
				},
				MembershipStates: map[string]gkehub.MembershipFeatureState{
					canonicalMembership: {
						Policycontroller: &gkehub.PolicyControllerMembershipState{
							State: "ACTIVE",
							ComponentStates: map[string]gkehub.PolicyControllerOnClusterState{
								"mutation": {State: "ACTIVE"},
							},
						},
					},
				},
			})
		case "/v3/projects/cluster-project", "/v3/projects/123456789":
			writeJSON(t, w, &cloudresourcemanager.Project{
				Name:      "projects/123456789",
				ProjectId: "cluster-project",
			})
		default:
			t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	resourceManagerSvc, err := cloudresourcemanager.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc, resourceManagerSvc: resourceManagerSvc}

	got, err := cli.GetPolicyController(context.Background(), "cluster-project", membership)

	require.NoError(t, err)
	require.Equal(t, featurePath, got.FeaturePath)
	require.True(t, got.DesiredEnabled)
	require.True(t, got.MutationEnabled)
	require.Equal(t, "ACTIVE", got.LifecycleState)
	require.Equal(t, "ACTIVE", got.MutationComponentState)
}

func TestGoogleGKEPolicyControlClientStartEnablePolicyControllerUsesCanonicalProjectNumberKey(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	canonicalMembership := "projects/123456789/locations/us-west1/memberships/membership-a"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	patchCalled := false
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/v1beta/"+featurePath:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			writeJSON(t, w, &gkehub.Feature{
				MembershipSpecs: map[string]gkehub.MembershipFeatureSpec{
					canonicalMembership: {
						Policycontroller: &gkehub.PolicyControllerMembershipSpec{
							PolicyControllerHubConfig: &gkehub.PolicyControllerHubConfig{
								InstallSpec:     "INSTALL_SPEC_SUSPENDED",
								MutationEnabled: false,
							},
						},
					},
				},
			})
		case r.Method == http.MethodGet && (r.URL.Path == "/v3/projects/cluster-project" || r.URL.Path == "/v3/projects/123456789"):
			writeJSON(t, w, &cloudresourcemanager.Project{
				Name:      "projects/123456789",
				ProjectId: "cluster-project",
			})
		case r.Method == http.MethodPatch && r.URL.Path == "/v1beta/"+featurePath:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			patchCalled = true
			var got gkehub.Feature
			require.NoError(t, json.NewDecoder(r.Body).Decode(&got))
			require.Contains(t, got.MembershipSpecs, canonicalMembership)
			require.NotContains(t, got.MembershipSpecs, membership)
			targetConfig := got.MembershipSpecs[canonicalMembership].Policycontroller.PolicyControllerHubConfig
			require.Equal(t, "INSTALL_SPEC_ENABLED", targetConfig.InstallSpec)
			require.True(t, targetConfig.MutationEnabled)
			writeJSON(t, w, &gkehub.Operation{})
		default:
			t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
		}
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	resourceManagerSvc, err := cloudresourcemanager.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc, resourceManagerSvc: resourceManagerSvc}

	feature, ops, err := cli.StartEnablePolicyController(context.Background(), "cluster-project", membership)

	require.NoError(t, err)
	require.Equal(t, featurePath, feature)
	require.Empty(t, ops)
	require.True(t, patchCalled)
}

func TestGoogleAPIStatusToGRPCCode(t *testing.T) {
	require.Equal(t, codes.PermissionDenied, googleAPIStatusToGRPCCode(&googleapi.Error{Code: http.StatusForbidden}, codes.Internal))
	require.Equal(t, codes.NotFound, googleAPIStatusToGRPCCode(&googleapi.Error{Code: http.StatusNotFound}, codes.Internal))
	require.Equal(t, codes.InvalidArgument, googleAPIStatusToGRPCCode(&googleapi.Error{Code: http.StatusBadRequest}, codes.Internal))
	require.Equal(t, codes.Internal, googleAPIStatusToGRPCCode(errors.New("boom"), codes.Internal))
	require.True(t, googleAPIOperationInProgress(&googleapi.Error{Code: http.StatusConflict, Message: "operation in progress"}))
	require.False(t, googleAPIOperationInProgress(&googleapi.Error{Code: http.StatusConflict, Message: "already exists"}))
	require.False(t, googleAPIOperationInProgress(&googleapi.Error{Code: http.StatusConflict, Message: "conflict"}))
	require.True(t, googleAPIOperationInProgress(&googleapi.Error{Code: http.StatusBadRequest, Message: "operation is already in progress"}))
	require.True(t, googleAPIOperationInProgress(&googleapi.Error{Code: http.StatusBadRequest, Message: "Cluster is running incompatible operation operation-1"}))
	require.True(t, googleAPIOperationInProgress(&googleapi.Error{
		Code:    http.StatusBadRequest,
		Message: "Cluster is running incompatible operation operation-1",
		Details: []interface{}{
			map[string]interface{}{"reason": "CLUSTER_ALREADY_HAS_OPERATION"},
		},
	}))

	err := googleAPIGKEPolicyControlIAMStatusErrorf(
		&googleapi.Error{Code: http.StatusForbidden, Message: "denied"},
		"enable service %s",
		gkeHubAPIService,
	)
	require.Equal(t, codes.PermissionDenied, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "enable service")
	require.Equal(t, codes.PermissionDenied, status.Code(toStatusError(err)))
}

func TestGKEPolicyControlIAMPreconditionErrorWrapsPermissionDeniedWithContext(t *testing.T) {
	root := &googleapi.Error{Code: http.StatusForbidden, Message: "denied"}

	err := gkePolicyControlIAMPreconditionError(googleAPIGKEPolicyControlIAMStatusErrorf(root, "enable fleet for gke cluster %s", "projects/cluster-project/locations/us-west1/clusters/test"))

	require.Equal(t, codes.FailedPrecondition, grpcCodeOfError(err))
	require.Contains(t, err.Error(), gkePolicyControlIAMPermissionMessage)
	require.Contains(t, err.Error(), "enable fleet for gke cluster")
	apiErr, ok := perrors.Cause(err).(*googleapi.Error)
	require.True(t, ok)
	require.Same(t, root, apiErr)
	require.Equal(t, codes.FailedPrecondition, status.Code(toStatusError(err)))
}

func TestToStatusErrorPreservesExistingStatusAndInternalCode(t *testing.T) {
	existing := status.Error(codes.NotFound, "already status")
	require.Same(t, existing, toStatusError(existing))

	internal := wrapGRPCCodeError(codes.FailedPrecondition, &googleapi.Error{Code: http.StatusForbidden, Message: "denied"}, "verify project identity")
	converted := toStatusError(internal)
	require.Equal(t, codes.FailedPrecondition, status.Code(converted))
	require.Contains(t, status.Convert(converted).Message(), "verify project identity")
}

func TestGoogleGKEPolicyControlClientStartEnablePolicyControllerMergesAfterCreateConflict(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	otherMembership := "projects/cluster-project/locations/us-west1/memberships/other"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	getCalls := 0
	patchCalled := false
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			getCalls++
			if getCalls == 1 {
				http.Error(w, `{"error":{"code":404,"message":"not found"}}`, http.StatusNotFound)
				return
			}
			require.Equal(t, 2, getCalls)
			writeJSON(t, w, &gkehub.Feature{
				MembershipSpecs: map[string]gkehub.MembershipFeatureSpec{
					membership: {
						Policycontroller: &gkehub.PolicyControllerMembershipSpec{
							PolicyControllerHubConfig: &gkehub.PolicyControllerHubConfig{
								AuditIntervalSeconds: 321,
								InstallSpec:          "INSTALL_SPEC_SUSPENDED",
								MutationEnabled:      false,
								DeploymentConfigs: map[string]gkehub.PolicyControllerPolicyControllerDeploymentConfig{
									"admission": {PodAffinity: "NO_AFFINITY"},
								},
								Monitoring: &gkehub.PolicyControllerMonitoringConfig{
									Backends: []string{"PROMETHEUS"},
								},
								PolicyContent: &gkehub.PolicyControllerPolicyContentSpec{
									Bundles: map[string]gkehub.PolicyControllerBundleInstallSpec{
										"custom-bundle": {},
									},
									TemplateLibrary: &gkehub.PolicyControllerTemplateLibraryConfig{
										Installation: "NONE",
									},
								},
							},
						},
					},
					otherMembership: {
						Policycontroller: &gkehub.PolicyControllerMembershipSpec{
							PolicyControllerHubConfig: &gkehub.PolicyControllerHubConfig{
								AuditIntervalSeconds: 123,
								InstallSpec:          "INSTALL_SPEC_ENABLED",
							},
						},
					},
				},
			})
		case http.MethodPost:
			require.Equal(t, "/v1beta/"+gkeFeatureParent("cluster-project")+"/features", r.URL.Path)
			require.Equal(t, "policycontroller", r.URL.Query().Get("featureId"))
			http.Error(w, `{"error":{"code":409,"message":"already exists"}}`, http.StatusConflict)
		case http.MethodPatch:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			patchCalled = true
			require.Equal(t, "membership_specs", r.URL.Query().Get("updateMask"))
			var got gkehub.Feature
			require.NoError(t, json.NewDecoder(r.Body).Decode(&got))
			require.Contains(t, got.MembershipSpecs, membership)
			require.Contains(t, got.MembershipSpecs, otherMembership)
			require.Equal(t, int64(123), got.MembershipSpecs[otherMembership].Policycontroller.PolicyControllerHubConfig.AuditIntervalSeconds)
			targetConfig := got.MembershipSpecs[membership].Policycontroller.PolicyControllerHubConfig
			require.Equal(t, "INSTALL_SPEC_ENABLED", targetConfig.InstallSpec)
			require.True(t, targetConfig.MutationEnabled)
			require.Equal(t, int64(321), targetConfig.AuditIntervalSeconds)
			require.Equal(t, "NO_AFFINITY", targetConfig.DeploymentConfigs["admission"].PodAffinity)
			require.Equal(t, []string{"PROMETHEUS"}, targetConfig.Monitoring.Backends)
			require.Contains(t, targetConfig.PolicyContent.Bundles, "custom-bundle")
			require.Equal(t, "NONE", targetConfig.PolicyContent.TemplateLibrary.Installation)
			writeJSON(t, w, &gkehub.Operation{})
		default:
			t.Fatalf("unexpected method %s", r.Method)
		}
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc}

	feature, ops, err := cli.StartEnablePolicyController(context.Background(), "cluster-project", membership)

	require.NoError(t, err)
	require.Equal(t, featurePath, feature)
	require.Empty(t, ops)
	require.Equal(t, 2, getCalls)
	require.True(t, patchCalled)
}

func TestGoogleGKEPolicyControlClientStartEnablePolicyControllerReturnsConflictWhenCreateConflictFeatureMissing(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	getCalls := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			getCalls++
			http.Error(w, `{"error":{"code":404,"message":"not found"}}`, http.StatusNotFound)
		case http.MethodPost:
			require.Equal(t, "/v1beta/"+gkeFeatureParent("cluster-project")+"/features", r.URL.Path)
			require.Equal(t, "policycontroller", r.URL.Query().Get("featureId"))
			http.Error(w, `{"error":{"code":409,"message":"already exists"}}`, http.StatusConflict)
		default:
			t.Fatalf("unexpected method %s", r.Method)
		}
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc}

	feature, ops, err := cli.StartEnablePolicyController(context.Background(), "cluster-project", membership)

	require.Error(t, err)
	require.Equal(t, codes.AlreadyExists, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "feature is not available")
	require.Empty(t, feature)
	require.Empty(t, ops)
	require.Equal(t, 2, getCalls)
}

func TestGoogleGKEPolicyControlClientStartEnablePolicyControllerTreatsCreateInProgressAsEnabling(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	getCalls := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			getCalls++
			http.Error(w, `{"error":{"code":404,"message":"not found"}}`, http.StatusNotFound)
		case http.MethodPost:
			require.Equal(t, "/v1beta/"+gkeFeatureParent("cluster-project")+"/features", r.URL.Path)
			require.Equal(t, "policycontroller", r.URL.Query().Get("featureId"))
			http.Error(w, `{"error":{"code":409,"message":"operation in progress"}}`, http.StatusConflict)
		default:
			t.Fatalf("unexpected method %s", r.Method)
		}
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc}

	feature, ops, err := cli.StartEnablePolicyController(context.Background(), "cluster-project", membership)

	require.NoError(t, err)
	require.Equal(t, featurePath, feature)
	require.Empty(t, ops)
	require.Equal(t, 2, getCalls)
}

func TestGoogleGKEPolicyControlClientStartEnablePolicyControllerTreatsPatchConflictAsSuccessWhenEnabled(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	getCalls := 0
	patchCalls := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			getCalls++
			if getCalls == 1 {
				writeJSON(t, w, &gkehub.Feature{
					MembershipSpecs: map[string]gkehub.MembershipFeatureSpec{
						membership: {
							Policycontroller: &gkehub.PolicyControllerMembershipSpec{
								PolicyControllerHubConfig: &gkehub.PolicyControllerHubConfig{
									InstallSpec:     "INSTALL_SPEC_SUSPENDED",
									MutationEnabled: false,
								},
							},
						},
					},
				})
				return
			}
			writeJSON(t, w, &gkehub.Feature{
				MembershipSpecs: map[string]gkehub.MembershipFeatureSpec{
					membership: {
						Policycontroller: &gkehub.PolicyControllerMembershipSpec{
							PolicyControllerHubConfig: &gkehub.PolicyControllerHubConfig{
								InstallSpec:     "INSTALL_SPEC_ENABLED",
								MutationEnabled: true,
							},
						},
					},
				},
			})
		case http.MethodPatch:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			patchCalls++
			http.Error(w, `{"error":{"code":409,"message":"already exists"}}`, http.StatusConflict)
		default:
			t.Fatalf("unexpected method %s", r.Method)
		}
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc}

	feature, ops, err := cli.StartEnablePolicyController(context.Background(), "cluster-project", membership)

	require.NoError(t, err)
	require.Equal(t, featurePath, feature)
	require.Empty(t, ops)
	require.Equal(t, 2, getCalls)
	require.Equal(t, 1, patchCalls)
}

func TestGoogleGKEPolicyControlClientStartEnablePolicyControllerReturnsConflictWhenPatchRetryFails(t *testing.T) {
	membership := "projects/cluster-project/locations/us-west1/memberships/membership-a"
	featurePath := gkePolicyControllerFeaturePath("cluster-project")

	getCalls := 0
	patchCalls := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			getCalls++
			writeJSON(t, w, &gkehub.Feature{
				MembershipSpecs: map[string]gkehub.MembershipFeatureSpec{
					membership: {
						Policycontroller: &gkehub.PolicyControllerMembershipSpec{
							PolicyControllerHubConfig: &gkehub.PolicyControllerHubConfig{
								InstallSpec:     "INSTALL_SPEC_SUSPENDED",
								MutationEnabled: false,
							},
						},
					},
				},
			})
		case http.MethodPatch:
			require.Equal(t, "/v1beta/"+featurePath, r.URL.Path)
			patchCalls++
			http.Error(w, `{"error":{"code":409,"message":"already exists"}}`, http.StatusConflict)
		default:
			t.Fatalf("unexpected method %s", r.Method)
		}
	}))
	defer server.Close()

	gkeHubSvc, err := gkehub.NewService(context.Background(), option.WithHTTPClient(server.Client()), option.WithEndpoint(server.URL+"/"))
	require.NoError(t, err)
	cli := &GoogleGKEPolicyControlClient{gkeHubSvc: gkeHubSvc}

	feature, ops, err := cli.StartEnablePolicyController(context.Background(), "cluster-project", membership)

	require.Error(t, err)
	require.Equal(t, codes.AlreadyExists, grpcCodeOfError(err))
	require.Contains(t, err.Error(), "retry update policycontroller feature")
	require.Empty(t, feature)
	require.Empty(t, ops)
	require.Equal(t, 2, getCalls)
	require.Equal(t, 2, patchCalls)
}

func writeJSON(t *testing.T, w http.ResponseWriter, v interface{}) {
	t.Helper()
	w.Header().Set("Content-Type", "application/json")
	require.NoError(t, json.NewEncoder(w).Encode(v))
}
