package gke

import (
	"context"
	"fmt"
	"reflect"
	"strconv"
	"time"

	computev1beta1 "github.com/crossplane-contrib/provider-gcp/apis/compute/v1beta1"
	gkev1beta2 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta2"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"google.golang.org/api/compute/v1"
	"google.golang.org/api/container/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/applier"
	"github.com/tidbcloud/infra-provider/pkg/applier/mutatefuncs"
	"github.com/tidbcloud/infra-provider/pkg/cloud/gcp"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/util"
	utiltime "github.com/tidbcloud/infra-provider/pkg/util/time"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
)

const (
	maintenanceExclusionOperationAnnotation = "operation.container.cloud.pingcap.com/maintenance-exclusion"
	defaultMaintenanceExclusionName         = "tidbcloud"
	defaultMaintenanceExclusionRequeueAfter = 12 * time.Hour
)

type GKE struct {
	*task.BaseTask
	gcpCli gcp.Client
	Clock  utiltime.Clock
}

func NewGKE(ctx *task.Context, gcpCli gcp.Client) *GKE {
	return &GKE{
		BaseTask: &task.BaseTask{Context: ctx},
		gcpCli:   gcpCli,
		Clock:    utiltime.RealClock{},
	}
}

func (t *GKE) Name() string {
	return "GKE"
}

func (t *GKE) buildGKE(ctx context.Context) (*gkev1beta2.Cluster, error) {
	isLegacy := t.APPBox.Labels[v1alpha1.IsLegacyKey] == "true"

	// we need to overwrite some names for migrated appbox
	providerConfigName := t.ProviderConfigName()
	if name := t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameProvidreConfig]; name != "" {
		providerConfigName = name
	}
	tags := cloudv1.TagsFromLabels(t.APPBox.Labels)
	endpointPublicAccess := t.Config.APPBoxConfig.EnableK8sPublicAccess
	cidrDispatcher, err := controllers.GetGKECIDRDispatcher(t.VPC, t.Config)
	if err != nil {
		return nil, err
	}

	subnetRef, err := t.controlPlaneSubnets()
	if err != nil {
		return nil, err
	}

	cluster := &gkev1beta2.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.GKEName(),
			Labels: t.Namer.Labels(),
		},
		Spec: gkev1beta2.ClusterSpec{
			ForProvider: gkev1beta2.ClusterParameters{
				LoggingService:        pointer.String("none"),
				MonitoringService:     pointer.String("none"),
				Location:              t.APPBox.Spec.Region,
				Locations:             configuredZones(t.VPC, t.Config),
				InitialClusterVersion: pointer.String(t.APPBox.Spec.KubernetesConfig.Version),
				ResourceLabels:        tags,
				NetworkRef:            &xpv1.Reference{Name: t.VPC.Status.VPC.Name},
				SubnetworkRef:         subnetRef,
				DefaultMaxPodsConstraint: &gkev1beta2.MaxPodsConstraint{
					MaxPodsPerNode: t.Config.APPBoxConfig.NodeConfig.MaxPodsPerNode,
				},
				IPAllocationPolicy: &gkev1beta2.IPAllocationPolicy{
					ClusterIpv4CidrBlock:  pointer.String(cidrDispatcher.PodIPv4CIDRBlock()),
					ServicesIpv4CidrBlock: pointer.String(cidrDispatcher.ServiceIPv4CIDRBlock()),
					UseIPAliases:          pointer.Bool(true),
				},
				PrivateClusterConfig: &gkev1beta2.PrivateClusterConfigSpec{
					EnablePrivateEndpoint: pointer.Bool(true),
					EnablePrivateNodes:    pointer.Bool(true),
					MasterIpv4CidrBlock:   pointer.String(cidrDispatcher.MasterIPv4CIDRBlock()),
				},
				MasterAuthorizedNetworksConfig: &gkev1beta2.MasterAuthorizedNetworksConfig{
					Enabled: pointer.Bool(endpointPublicAccess),
				},
				ReleaseChannel: &gkev1beta2.ReleaseChannel{
					// Auto-upgrade must be true in nodepool when it is set to others
					Channel: "UNSPECIFIED",
				},
				MaintenancePolicy: t.buildMaintenancePolicy(),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: providerConfigName},
			},
		},
	}
	// New 3layer gke enable workload identity by default, and migrated gke disable it by default,
	// so we need to set it to make sure all gke have the same behavior.
	cluster.Spec.ForProvider.WorkloadIdentityConfig = &gkev1beta2.WorkloadIdentityConfig{
		WorkloadPool: fmt.Sprintf("%s.svc.id.goog", t.ProjectID()),
	}
	if maxpodspernode := t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnSpecMaxPodsPerNode]; maxpodspernode != "" {
		val, err := strconv.ParseInt(maxpodspernode, 10, 64)
		if err != nil {
			return nil, err
		}
		cluster.Spec.ForProvider.DefaultMaxPodsConstraint.MaxPodsPerNode = val
	}
	if isLegacy {
		cluster.Labels[v1alpha1.IsLegacyKey] = "true"
	}

	if endpointPublicAccess {
		cidrs := []*gkev1beta2.CidrBlock{}
		for _, cidr := range t.Config.APPBoxConfig.ApiserverPublicAccessCidrs {
			cidrs = append(cidrs, &gkev1beta2.CidrBlock{
				CidrBlock: cidr,
			})
		}
		cluster.Spec.ForProvider.PrivateClusterConfig.EnablePrivateEndpoint = pointer.Bool(false)
		cluster.Spec.ForProvider.MasterAuthorizedNetworksConfig.CidrBlocks = cidrs
	}
	if err := t.ignoreChanges(ctx, cluster); err != nil {
		return nil, err
	}
	return cluster, nil
}

func (t *GKE) buildMaintenancePolicy() *gkev1beta2.MaintenancePolicySpec {
	if !t.isConfigMaintenanceExclusion() {
		return nil
	}

	var startTimeStr, endTimeStr string
	if t.enableAutoUpdateMaintenanceExclusion() {
		now := t.Clock.Now()

		// Calculate previous day's 0:00 (midnight)
		// So it will only update the start time at most once a day.
		yesterdayMidnight := time.Date(
			now.Year(),
			now.Month(),
			now.Day()-1, // subtract one day
			0, 0, 0, 0,
			time.UTC,
		)

		// Add 30 days to the start time
		endTime := yesterdayMidnight.AddDate(0, 0, 30)
		startTimeStr = yesterdayMidnight.Format(time.RFC3339)
		endTimeStr = endTime.Format(time.RFC3339)
	} else {
		startTimeStr = t.Config.APPBoxConfig.GKEMaintenanceExclusionTimeWindow.StartTime.Format(time.RFC3339)
		endTimeStr = t.Config.APPBoxConfig.GKEMaintenanceExclusionTimeWindow.EndTime.Format(time.RFC3339)
	}
	t.Logger.Info("set maintenance exclusion time window", "startTime", startTimeStr, "endTime", endTimeStr)

	// crossplane-gcp will write-back MaintenancePolicy if the field in spec is empty and the real cluster has one.
	// Also, it has a bug(not set fingerprint when sending request) and can't update maintenance policy with spec.
	// To workaroud its write-back mechanism, set MaintenancePolicy in spec and send correct update request later with cli.
	return &gkev1beta2.MaintenancePolicySpec{
		Window: gkev1beta2.MaintenanceWindowSpec{
			MaintenanceExclusions: map[string]gkev1beta2.TimeWindow{
				defaultMaintenanceExclusionName: {
					StartTime: startTimeStr,
					EndTime:   endTimeStr,
				},
			},
		},
	}
}

// ignoreChanges ignores some fields changes for existing clusters
func (t *GKE) ignoreChanges(ctx context.Context, newCluster *gkev1beta2.Cluster) error {
	key := types.NamespacedName{
		Name: t.GKEName(),
	}
	existing := &gkev1beta2.Cluster{}
	if err := t.Local.Get(ctx, key, existing); err != nil {
		return client.IgnoreNotFound(err)
	}

	// `InitialClusterVersion` is immutable
	newCluster.Spec.ForProvider.InitialClusterVersion = existing.Spec.ForProvider.InitialClusterVersion
	// ignore pod cidr change, see https://pingcap.feishu.cn/wiki/HbDlwu8GFitnHnkqtWFcG5JDnHe for more detail
	newCluster.Spec.ForProvider.IPAllocationPolicy.ClusterIpv4CidrBlock = existing.Spec.ForProvider.IPAllocationPolicy.ClusterIpv4CidrBlock
	newCluster.Spec.ForProvider.IPAllocationPolicy.ServicesIpv4CidrBlock = existing.Spec.ForProvider.IPAllocationPolicy.ServicesIpv4CidrBlock
	newCluster.Spec.ForProvider.PrivateClusterConfig.MasterIpv4CidrBlock = existing.Spec.ForProvider.PrivateClusterConfig.MasterIpv4CidrBlock
	newCluster.Spec.ForProvider.Locations = append([]string(nil), existing.Spec.ForProvider.Locations...)
	return nil
}

// IsSafeToApplyGKE check the generated gke cr has the same field as the existed gke cr,
// but only focus on some fields.
func (t *GKE) IsSafeToApplyGKE(ctx context.Context, existedCluster *gkev1beta2.Cluster) (bool, error) {
	cluster, err := t.buildGKE(ctx)
	if err != nil {
		return false, err
	}
	generated := cluster.Spec.ForProvider
	existed := existedCluster.Spec.ForProvider

	if *generated.LoggingService != *existed.LoggingService {
		t.Logger.Info("detect diff on Spec.ForProvider.LoggingService",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.LoggingService,
			"existed", *existed.LoggingService,
		)
		return false, nil
	}
	if *generated.MonitoringService != *existed.MonitoringService {
		t.Logger.Info("detect diff on Spec.ForProvider.MonitoringService",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.MonitoringService,
			"existed", *existed.MonitoringService,
		)
		return false, nil
	}
	if generated.Location != existed.Location {
		t.Logger.Info("detect diff on Spec.ForProvider.Location",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.Location,
			"existed", existed.Location,
		)
		return false, nil
	}

	expectedResourceLabels := map[string]string{}
	for k, v := range existed.ResourceLabels {
		expectedResourceLabels[k] = v
	}
	// generated resource labels must equal to existed resource labels
	if len(generated.ResourceLabels) != len(existed.ResourceLabels) {
		t.Logger.Info("detect diff on Spec.ForProvider.ResourceLabels",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.ResourceLabels,
			"existed", existed.ResourceLabels,
		)
		return false, nil
	}
	for k, v := range expectedResourceLabels {
		if generated.ResourceLabels[k] != v {
			t.Logger.Info("detect diff on Spec.ForProvider.ResourceLabels",
				"key", client.ObjectKeyFromObject(existedCluster).String(),
				"generated", generated.ResourceLabels,
				"existed", existed.ResourceLabels,
			)
			return false, nil
		}
	}
	if existed.NetworkRef == nil {
		t.Logger.Info("Spec.ForProvider.NetworkRef is empty",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
		)
		return false, nil
	}
	if existed.SubnetworkRef == nil {
		t.Logger.Info("Spec.ForProvider.SubnetworkRef is empty",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
		)
		return false, nil
	}
	if generated.NetworkRef.Name != existed.NetworkRef.Name {
		t.Logger.Info("detect diff on Spec.ForProvider.NetworkRef.Name",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.NetworkRef.Name,
			"existed", existed.NetworkRef.Name,
		)
		return false, nil
	}
	if generated.SubnetworkRef.Name != existed.SubnetworkRef.Name {
		t.Logger.Info("detect diff on Spec.ForProvider.SubnetworkRef.Name",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.SubnetworkRef.Name,
			"existed", existed.SubnetworkRef.Name,
		)
		return false, nil
	}
	if existed.DefaultMaxPodsConstraint == nil {
		t.Logger.Info("Spec.ForProvider.DefaultMaxPodsConstraint is empty",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
		)
		return false, nil
	}
	if generated.DefaultMaxPodsConstraint.MaxPodsPerNode != existed.DefaultMaxPodsConstraint.MaxPodsPerNode {
		t.Logger.Info("detect diff on Spec.ForProvider.DefaultMaxPodsConstraint.MaxPodsPerNode",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.DefaultMaxPodsConstraint.MaxPodsPerNode,
			"existed", existed.DefaultMaxPodsConstraint.MaxPodsPerNode,
		)
		return false, nil
	}
	if existed.IPAllocationPolicy == nil {
		t.Logger.Info("Spec.ForProvider.IPAllocationPolicy is empty",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
		)
		return false, nil
	}
	if *generated.IPAllocationPolicy.ClusterIpv4CidrBlock != *existed.IPAllocationPolicy.ClusterIpv4CidrBlock {
		t.Logger.Info("detect diff on Spec.ForProvider.IPAllocationPolicy.ClusterIpv4CidrBlock",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.IPAllocationPolicy.ClusterIpv4CidrBlock,
			"existed", existed.IPAllocationPolicy.ClusterIpv4CidrBlock,
		)
		return false, nil
	}
	if *generated.IPAllocationPolicy.ServicesIpv4CidrBlock != *existed.IPAllocationPolicy.ServicesIpv4CidrBlock {
		t.Logger.Info("detect diff on Spec.ForProvider.IPAllocationPolicy.ServicesIpv4CidrBlock",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.IPAllocationPolicy.ServicesIpv4CidrBlock,
			"existed", existed.IPAllocationPolicy.ServicesIpv4CidrBlock,
		)
		return false, nil
	}
	if *generated.IPAllocationPolicy.UseIPAliases != *existed.IPAllocationPolicy.UseIPAliases {
		t.Logger.Info("detect diff on Spec.ForProvider.IPAllocationPolicy.UseIPAliases",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.IPAllocationPolicy.UseIPAliases,
			"existed", existed.IPAllocationPolicy.UseIPAliases,
		)
		return false, nil
	}
	if existed.PrivateClusterConfig == nil {
		t.Logger.Info("Spec.ForProvider.PrivateClusterConfig is empty",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
		)
		return false, nil
	}
	if *generated.PrivateClusterConfig.EnablePrivateEndpoint != *existed.PrivateClusterConfig.EnablePrivateEndpoint {
		t.Logger.Info("detect diff on Spec.ForProvider.PrivateClusterConfig.EnablePrivateEndpoint",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.PrivateClusterConfig.EnablePrivateEndpoint,
			"existed", existed.PrivateClusterConfig.EnablePrivateEndpoint,
		)
		return false, nil
	}
	if *generated.PrivateClusterConfig.EnablePrivateNodes != *existed.PrivateClusterConfig.EnablePrivateNodes {
		t.Logger.Info("detect diff on Spec.ForProvider.PrivateClusterConfig.EnablePrivateNodes",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.PrivateClusterConfig.EnablePrivateNodes,
			"existed", *existed.PrivateClusterConfig.EnablePrivateNodes,
		)
		return false, nil
	}
	if *generated.PrivateClusterConfig.MasterIpv4CidrBlock != *existed.PrivateClusterConfig.MasterIpv4CidrBlock {
		t.Logger.Info("detect diff on Spec.ForProvider.PrivateClusterConfig.MasterIpv4CidrBlock",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.PrivateClusterConfig.MasterIpv4CidrBlock,
			"existed", *existed.PrivateClusterConfig.MasterIpv4CidrBlock,
		)
		return false, nil
	}

	if existed.MasterAuthorizedNetworksConfig == nil {
		t.Logger.Info("Spec.ForProvider.MasterAuthorizedNetworksConfig is empty",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
		)
		return false, nil
	}
	if *generated.MasterAuthorizedNetworksConfig.Enabled != *existed.MasterAuthorizedNetworksConfig.Enabled {
		t.Logger.Info("detect diff on Spec.ForProvider.MasterAuthorizedNetworksConfig.Enabled",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.MasterAuthorizedNetworksConfig.Enabled,
			"existed", *existed.MasterAuthorizedNetworksConfig.Enabled,
		)
		return false, nil
	}
	if existed.ReleaseChannel == nil {
		t.Logger.Info("Spec.ForProvider.ReleaseChannel is empty",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
		)
		return false, nil
	}
	if generated.ReleaseChannel.Channel != existed.ReleaseChannel.Channel {
		t.Logger.Info("detect diff on Spec.ForProvider.ReleaseChannel.Channel",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.ReleaseChannel.Channel,
			"existed", existed.ReleaseChannel.Channel,
		)
		return false, nil
	}

	if !reflect.DeepEqual(generated.PrivateClusterConfig.EnablePrivateEndpoint, existed.PrivateClusterConfig.EnablePrivateEndpoint) {
		t.Logger.Info("detect diff on Spec.ForProvider.PrivateClusterConfig.EnablePrivateEndpoint",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.PrivateClusterConfig.EnablePrivateEndpoint,
			"existed", existed.PrivateClusterConfig.EnablePrivateEndpoint,
		)
		return false, nil
	}
	if !reflect.DeepEqual(generated.MasterAuthorizedNetworksConfig.CidrBlocks, existed.MasterAuthorizedNetworksConfig.CidrBlocks) {
		t.Logger.Info("detect diff on Spec.ForProvider.MasterAuthorizedNetworksConfig.CidrBlocks",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.MasterAuthorizedNetworksConfig.CidrBlocks,
			"existed", existed.MasterAuthorizedNetworksConfig.CidrBlocks,
		)
		return false, nil
	}
	return true, nil
}

func (t *GKE) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	if t.APPBox.Annotations[v1alpha1.AnnotationKeyLegacyAPPBoxSkipGKETask] == "true" {
		return deploy.Done()
	}
	cluster, err := t.buildGKE(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	if _, err := applier.NewApplier(t.Local, mutatefuncs.MutateGKE).
		Expected(cluster).
		Apply(ctx); err != nil {
		return deploy.Panic(err)
	}

	return deploy.Done()
}

func (t *GKE) controlPlaneSubnets() (*xpv1.Reference, error) {
	if len(t.VPC.Status.NodeSubnets) < 1 {
		return nil, fmt.Errorf("node subnets not specified")
	}
	subnetRef := &xpv1.Reference{
		Name: t.VPC.Status.NodeSubnets[0].Name,
	}
	return subnetRef, nil
}

func (t *GKE) isConfigMaintenanceExclusion() bool {
	w := t.Config.APPBoxConfig.GKEMaintenanceExclusionTimeWindow
	return (w.StartTime != nil && w.EndTime != nil) || w.AutoUpdate
}

func (t *GKE) enableAutoUpdateMaintenanceExclusion() bool {
	return t.Config.APPBoxConfig.GKEMaintenanceExclusionTimeWindow.AutoUpdate
}

// crossplane-gcp has a bug(not set fingerprint when sending request) to update maintenance policy, set it with gcp-client
func (t *GKE) setMaintenanceExclusion(ctx context.Context, gke *gkev1beta2.Cluster) error {
	if opName := util.GetAnnotation(t.APPBox, maintenanceExclusionOperationAnnotation); len(opName) > 0 {
		t.Logger.Info("Checking if gke operation is done", "operation", opName)
		done, err := t.gcpCli.IsContainerOperationDone(ctx, opName)
		if err != nil {
			return err
		}
		if !done {
			return fmt.Errorf("waiting gke operation done, operation: %s", opName)
		}
		// if done
		delete(t.APPBox.Annotations, maintenanceExclusionOperationAnnotation)
		return nil
	}

	if mp := gke.Spec.ForProvider.MaintenancePolicy; mp != nil {
		if tw, ok := mp.Window.MaintenanceExclusions[defaultMaintenanceExclusionName]; ok && tw.StartTime != "" && tw.EndTime != "" {
			opName, err := t.gcpCli.SetClusterMaintenanceExclusion(ctx,
				t.ProjectID(),
				t.APPBox.Spec.Region,
				t.GKEName(),
				defaultMaintenanceExclusionName,
				&container.TimeWindow{
					StartTime: tw.StartTime,
					EndTime:   tw.EndTime,
					MaintenanceExclusionOptions: &container.MaintenanceExclusionOptions{
						// Only no_upgrades scope can be specified to no channel clusters
						Scope: "NO_UPGRADES",
					},
				},
			)
			if err != nil {
				return fmt.Errorf("failed to set maintenance exclusion via sdk: %w", err)
			}
			if len(opName) > 0 {
				util.SetAnnotation(t.APPBox, maintenanceExclusionOperationAnnotation, opName)
				return fmt.Errorf("waiting gke operation done, operation: %s", opName)
			}
		}
	}

	return nil
}

func (t *GKE) PostCreate(ctx context.Context) (res deploy.Result) {
	key := types.NamespacedName{Name: t.GKEName()}
	c := &gkev1beta2.Cluster{}
	err := t.Local.Get(ctx, key, c)
	if err != nil {
		return deploy.Panic(err)
	}
	t.Cluster = task.NewGKEClusterIdentifier(c)
	t.APPBox.Status.KubernetesClusterName = c.Name
	status := c.Status.AtProvider.Status
	switch status {
	case gkev1beta2.ClusterStateError:
		t.APPBox.Status.Conditions.SetCondition(metav1.Condition{
			Type:               v1alpha1.APPBoxConditionK8SReady,
			Status:             metav1.ConditionFalse,
			Reason:             "CreationFailed",
			Message:            fmt.Sprintf("GKE Cluster creation error: %s", c.Status.AtProvider.StatusMessage),
			LastTransitionTime: metav1.Now(),
		})
		return deploy.Panic(fmt.Errorf("GKE Cluster has the status of %s", status)).WithRequeuAfter(5 * time.Second)
	case gkev1beta2.ClusterStateRunning, gkev1beta2.ClusterStateReconciling:
		t.APPBox.Status.Conditions.SetCondition(metav1.Condition{
			Type:               v1alpha1.APPBoxConditionK8SReady,
			Status:             metav1.ConditionTrue,
			Reason:             "GKEReady",
			Message:            "GKE Creation succeeded",
			LastTransitionTime: metav1.Now(),
		})
	default:
		t.APPBox.Status.Conditions.SetCondition(metav1.Condition{
			Type:               v1alpha1.APPBoxConditionK8SReady,
			Status:             metav1.ConditionFalse,
			Reason:             "Creating",
			Message:            fmt.Sprintf("GKE Cluster has the status of %s", status),
			LastTransitionTime: metav1.Now(),
		})
		return deploy.Panic(fmt.Errorf("GKE Cluster has the status of %s", status)).WithRequeuAfter(5 * time.Second)
	}

	if err := t.setMaintenanceExclusion(ctx, c); err != nil {
		return deploy.Error(fmt.Errorf("failed to set maintenance exclusion: %w", err))
	}
	if err := t.enableSubSetting(ctx); err != nil {
		return deploy.Error(fmt.Errorf("failed to enable sub setting: %w", err))
	}
	// remove the skip annotation if it is safe to apply gke for legacy appbox.
	if t.APPBox.Annotations[v1alpha1.AnnotationKeyLegacyAPPBoxSkipGKETask] == "true" {
		ok, err := t.IsSafeToApplyGKE(ctx, c)
		if err != nil {
			return deploy.Error(fmt.Errorf("check safe to apply gke for legacy appbox failed: %w", err))
		}
		if ok {
			delete(t.APPBox.Annotations, v1alpha1.AnnotationKeyLegacyAPPBoxSkipGKETask)
		}
	}

	requeueAfter := t.getMaintenanceExclusionRequeueAfter()
	t.Logger.Info("requeue after duration of the maintenance exclusion", "requeueAfter", requeueAfter)
	return deploy.Done().WithRequeuAfter(requeueAfter)
}

func (t *GKE) Delete(ctx context.Context) deploy.Result {
	subnetwork := new(computev1beta1.Subnetwork)
	// TODO: replace the name?
	subnetwork.Name = t.VPC.Status.NodeSubnets[0].Name
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(subnetwork), subnetwork); err != nil {
		return deploy.Error(err)
	}
	// remove subnet sencondaryIPRanges to avoid CIDR conflict when creating new cluster under same network.
	if len(subnetwork.Spec.ForProvider.SecondaryIPRanges) > 0 {
		subnetwork.Spec.ForProvider.SecondaryIPRanges = nil
		// call gcp api to update the sencondaryIPRanges additionally,
		// since crossplane is not able to patch the delete changes on sencondaryIPRanges
		// for more details, check https://github.com/crossplane-contrib/provider-gcp/pull/517
		if err := t.gcpCli.PatchSubnet(ctx, getPatchSN(*subnetwork), t.ProjectID(), t.APPBox.Spec.Region); err != nil {
			return deploy.Error(err)
		}
		if err := t.Local.Update(ctx, subnetwork); err != nil {
			return deploy.Error(err)
		}
	}

	cluster := &gkev1beta2.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.GKEName(),
		},
	}

	if err := k8s.DeleteObject(ctx, t.Local, cluster); err != nil {
		return deploy.Error(err)
	}
	if err := k8s.PatchObject(ctx, t.Local, cluster, k8s.RemoveCrossplaneExternalCreatePending, k8s.DeletionTrigger); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func getPatchSN(s computev1beta1.Subnetwork) *compute.Subnetwork {
	in := s.Spec.ForProvider
	sn := &compute.Subnetwork{
		Name:                  s.Name,
		Description:           utils.StringValue(in.Description),
		EnableFlowLogs:        utils.BoolValue(in.EnableFlowLogs),
		IpCidrRange:           in.IPCidrRange,
		PrivateIpGoogleAccess: utils.BoolValue(in.PrivateIPGoogleAccess),
		Fingerprint:           s.Status.AtProvider.Fingerprint,
		ForceSendFields:       []string{"SecondaryIpRanges"},
		NullFields:            []string{"SecondaryIpRanges"},
	}
	if len(in.SecondaryIPRanges) > 0 {
		sn.SecondaryIpRanges = make([]*compute.SubnetworkSecondaryRange, len(in.SecondaryIPRanges))
	}

	for i, val := range in.SecondaryIPRanges {
		sn.SecondaryIpRanges[i] = &compute.SubnetworkSecondaryRange{
			IpCidrRange: val.IPCidrRange,
			RangeName:   val.RangeName,
		}
	}
	return sn
}

func (t *GKE) PostDelete(ctx context.Context) deploy.Result {
	key := types.NamespacedName{
		Name: t.GKEName(),
	}
	var c gkev1beta2.Cluster
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, key, &c))
}

// getMaintenanceExclusionRequeueAfter returns the requeue after duration of the maintenance exclusion.
// default: 12 hours, min: 1 minute, max: 10 days
func (t *GKE) getMaintenanceExclusionRequeueAfter() time.Duration {
	requeueAfter := defaultMaintenanceExclusionRequeueAfter
	if t.Config.APPBoxConfig.GKEMaintenanceExclusionRequeueAfter.Duration > 0 {
		requeueAfter = t.Config.APPBoxConfig.GKEMaintenanceExclusionRequeueAfter.Duration
	}
	if requeueAfter < 1*time.Minute {
		t.Logger.Info("requeue after duration is less than 1 minute, set to 1 minute", "requeueAfter", requeueAfter)
		return 1 * time.Minute
	}
	if requeueAfter > 10*24*time.Hour {
		t.Logger.Info("requeue after duration is greater than 10 days, set to 10 days", "requeueAfter", requeueAfter)
		return 10 * 24 * time.Hour
	}
	return requeueAfter
}

func (t *GKE) enableSubSetting(ctx context.Context) error {
	return t.gcpCli.EnabelSubSetting(ctx, t.ProjectID(), t.APPBox.Spec.Region, t.GKEName())
}
