package gke

import (
	"context"
	"errors"
	"fmt"
	"sort"

	"github.com/crossplane-contrib/provider-gcp/apis/container/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"

	"github.com/tidbcloud/eks-provider/controllers/imports/task"
	"github.com/tidbcloud/eks-provider/controllers/imports/task/common"
	"github.com/tidbcloud/eks-provider/controllers/utils"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
)

type NodePool struct {
	*task.BaseTask
}

func NewNodePool(ctx *task.Context) *NodePool {
	return &NodePool{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

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

func (t *NodePool) Create(ctx context.Context) deploy.Result {
	nodePool, err := t.desiredNodePools()
	if err != nil {
		return deploy.Error(err)
	}
	if err := k8s.Apply(ctx, t.Context.Local, nodePool); err != nil {
		t.Logger.Error(err, "fail to apply node group "+nodePool.Name)
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *NodePool) Delete(ctx context.Context) deploy.Result {
	nodePool := &v1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.GetGKENodePoolName(),
		},
	}
	return deploy.ErrorOrDone(k8s.DeleteObject(ctx, t.Local, nodePool))
}

func (t *NodePool) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

// desiredNodePools generate nodepools expected to createOrUpdate
func (t *NodePool) desiredNodePools() (*v1beta1.NodePool, error) {

	zoneID, err := t.getZoneID()
	if err != nil {
		return nil, err
	}

	labels := common.GetNodeLabels(t.Namer, t.Import)
	it := t.Config.ImportConfig.InstanceType
	sa, err := getGCPNodePoolServiceAccountEmail(t.BaseTask)
	if err != nil {
		return nil, err
	}
	nodePool := &v1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Name:        t.GetGKENodePoolName(),
			Labels:      labels,
			Annotations: map[string]string{},
		},
		Spec: v1beta1.NodePoolSpec{
			ForProvider: v1beta1.NodePoolParameters{
				Cluster: t.GetGCPClusterRef(),
				Autoscaling: &v1beta1.NodePoolAutoscaling{
					Enabled: ptr.To(false),
				},
				Config: &v1beta1.NodeConfig{
					Labels:         labels,
					DiskSizeGb:     ptr.To(int64(100)),
					DiskType:       ptr.To(utils.GetGcpNodepoolDiskType(it)),
					ImageType:      ptr.To("cos_containerd"),
					MachineType:    &it,
					ServiceAccount: &sa, // node sa
					OauthScopes: []string{
						"https://www.googleapis.com/auth/cloud-platform",
					},
					Taints: []*v1beta1.NodeTaint{
						{
							Key:    "dedicated",
							Value:  "tidb-" + task.LightningComponent,
							Effect: "NO_SCHEDULE",
						},
					},
				},
				InitialNodeCount: ptr.To(int64(1)),
				Locations:        []string{zoneID},
				Management: &v1beta1.NodeManagementSpec{
					// Do it manually
					AutoUpgrade: ptr.To(false),
					AutoRepair:  ptr.To(false),
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.GetGKEProviderConfigName(),
				},
			},
		},
	}
	return nodePool, nil
}

func (t *NodePool) GetGCPClusterRef() string {
	return fmt.Sprintf("projects/%s/locations/%s/clusters/%s", t.EnvProvider.GetAccount(), t.EnvProvider.GetRegion(), t.EnvProvider.GetClusterName())
}

func (t *NodePool) getZoneID() (string, error) {
	zones := t.Zones()
	if len(zones) < 1 {
		return "", errors.New("zones is required")
	}
	sort.Strings(zones)
	return zones[0], nil
}

func (t *NodePool) GetGKENodePoolName() string {
	return getNodePoolName(t.BaseTask)
}
