package task

import (
	"context"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/controllers/tidbgroupagent/metadata"
	"github.com/tidbcloud/observability/agent-control-server/pkg/agentbuild"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/deploy"
	"github.com/tidbcloud/observability/agent-control-server/pkg/reconciler"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util"
)

type VectorTask struct {
	*reconciler.BaseTask[*agentv2.TiDBGroupAgent]
}

func NewVectorTask(ctx *reconciler.Context[*agentv2.TiDBGroupAgent]) *VectorTask {
	return &VectorTask{
		BaseTask: &reconciler.BaseTask[*agentv2.TiDBGroupAgent]{Context: ctx},
	}
}

func (t *VectorTask) Name() string {
	return "Vector"
}

func (t *VectorTask) ClusterMeta() deploy.ClusterMeta {
	return metadata.ClusterMeta(t.Agent)
}

func (t *VectorTask) Create(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	cfg := t.Config.O11yConfig.CustomerConfigsV2.NextGen.TiDBGroup.BuildConfig().Vector
	name := t.buildName()
	namespace := t.buildNamespace()
	if err := agentbuild.CreateOrUpdateRole(ctx, cli, t.buildRole); err != nil {
		return deploy.Panic(err)
	}
	if err := agentbuild.CreateOrUpdateRoleBinding(ctx, cli, t.buildRoleBinding); err != nil {
		return deploy.Panic(err)
	}
	if err := agentbuild.InstallOrUpgradeHelm(ctx, t.Remote, t.build, cfg.Chart.Name, cfg.Chart.Version, name, namespace, nil); err != nil {
		return deploy.Panic(err)
	}
	return deploy.Done()
}

func (t *VectorTask) PostCreate(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	// check statefulset
	if err := agentbuild.CheckStatefulSetReady(ctx, cli, t.buildName(), t.buildNamespace()); err != nil {
		return deploy.Panic(err)
	}
	// check and update pvc labels
	labels := map[string]string{
		consts.TagKeyComponent:    consts.TagValueComponentO11y,
		consts.TagKeyTenant:       t.Agent.Spec.O11y.TenantID,
		consts.TagKeyResourcePool: t.Agent.Spec.SharedPoolID,
		consts.TagKeyCluster:      t.Agent.Spec.ClusterID,
	}
	pvcName := fmt.Sprintf("data-o11y-vector-tidb-%s-0", t.Agent.Spec.TiDBGroup)
	if _, err := agentbuild.CheckAndUpdatePVCLabels(ctx, cli, pvcName, t.buildNamespace(), labels); err != nil {
		return deploy.Panic(err)
	}
	return deploy.Done()
}

func (t *VectorTask) Delete(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	name := t.buildName()
	namespace := t.buildNamespace()
	if err := agentbuild.DeleteRoleBinding(ctx, cli, name, namespace); err != nil {
		return deploy.Panic(err)
	}
	if err := agentbuild.DeleteRole(ctx, cli, name, namespace); err != nil {
		return deploy.Panic(err)
	}
	if err := agentbuild.DeleteHelm(ctx, t.Remote, name, namespace); err != nil {
		return deploy.Panic(err)
	}
	if err := agentbuild.DeletePVC(ctx, cli, fmt.Sprintf("data-o11y-vector-tidb-%s-0", t.Agent.Spec.TiDBGroup), namespace); err != nil {
		return deploy.Panic(err)
	}
	return deploy.Done()
}

func (t *VectorTask) PostDelete(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	return deploy.PanicOrDone(agentbuild.CheckStatefulSetDeleted(ctx, cli, t.buildName(), t.buildNamespace()))
}

func (t *VectorTask) build() (string, error) {
	cfg := t.Config.O11yConfig.CustomerConfigsV2.NextGen.TiDBGroup.BuildConfig().Vector

	// TODO: temporary use ak/sk on alicloud
	// When vector supports node role for oss, remove this
	var accessKey, secretKey string
	if t.Config.O11yConfig.Vendor == "alicloud" {
		secret := &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "o11y-oss-secret",
				Namespace: "observability",
			},
		}
		if err := t.Local.Get(context.Background(), client.ObjectKeyFromObject(secret), secret); err != nil {
			return "", err
		}
		accessKey = string(secret.Data["access_key"])
		secretKey = string(secret.Data["secret_key"])
	}

	imageRepo, imageTag, err := agentbuild.BuildVectorImage(t.Agent, cfg.Image)
	if err != nil {
		return "", err
	}

	desiredSpec := util.GetAnnotationWithDefault(t.Agent, consts.AnnotationKeyO11yDesiredSpec, "default")
	spec := t.Config.O11yConfig.CustomerConfigsV2.NextGen.TiDBGroup.Specs.Select(desiredSpec)
	if spec == nil || spec.Vector == nil {
		return "", fmt.Errorf("desired spec not found: %s", desiredSpec)
	}
	resourcesJson, err := agentbuild.BuildVectorResources(t.Agent, spec.Vector.DeepCopy())
	if err != nil {
		return "", err
	}

	configTemplate := fmt.Sprintf("tidbgroup/vector-values-%s.yaml", t.Config.O11yConfig.Vendor)
	tmpl, err := agentbuild.BuildVectorTemplate(t.Agent, t.Templates, configTemplate)
	if err != nil {
		return "", err
	}

	return agentbuild.BuildVectorValues(tmpl, map[string]string{
		"ImageRepo":         imageRepo,
		"ImageTag":          imageTag,
		"Resources":         resourcesJson,
		"SharedPoolID":      t.Agent.Spec.SharedPoolID,
		"TiDBGroup":         t.Agent.Spec.TiDBGroup,
		"ClusterID":         t.Agent.Spec.ClusterID,
		"TenantID":          t.Agent.Spec.O11y.TenantID,
		"AccessKeyID":       accessKey,
		"SecretAccessKey":   secretKey,
		"SecretName":        t.buildName(),
		"VMAccountID":       util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yVMAccountID),
		"VMProjectID":       util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yVMProjectID),
		"RemoteWriteURL":    fmt.Sprintf("http://%s/multitenant/api/v1/write", util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yServiceDNSName)),
		"PriorityClassName": consts.PriorityClassNameClusterCritical,
		"O11yID":            util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yID),
		"Region":            t.Config.O11yConfig.Region,
		"Bucket":            util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yLogBucketName),
		"RoleARN":           util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yAgentRoleArn),
		"AzureClientID":     util.GetAnnotation(t.Agent, consts.AnnotationKeyAKSKubeletClientID),
		"ContainerName":     util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yLogBucketName),
		"StorageAccount":    util.GetAnnotation(t.Agent, consts.AnnotationKeyO11yLogStorageAccount),
		"RefNamespace":      t.Agent.Namespace,
		"RefName":           t.Agent.Name,
	})
}

func (t *VectorTask) buildRole() (*rbacv1.Role, error) {
	return &rbacv1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.buildName(),
			Namespace: t.buildNamespace(),
		},
		Rules: []rbacv1.PolicyRule{{
			APIGroups: []string{""},
			Resources: []string{"pods"},
			Verbs:     []string{"get", "list", "watch"},
		}},
	}, nil
}

func (t *VectorTask) buildRoleBinding() (*rbacv1.RoleBinding, error) {
	name := t.buildName()
	namespace := t.buildNamespace()
	return &rbacv1.RoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
		},
		Subjects: []rbacv1.Subject{{
			Kind:      "ServiceAccount",
			Name:      name,
			Namespace: namespace,
		}},
		RoleRef: rbacv1.RoleRef{
			Kind:     "Role",
			Name:     name,
			APIGroup: "rbac.authorization.k8s.io",
		},
	}, nil
}

func (t *VectorTask) buildName() string {
	return fmt.Sprintf("o11y-vector-tidb-%s", t.Agent.Spec.TiDBGroup)
}

func (t *VectorTask) buildNamespace() string {
	return fmt.Sprintf("tidb%s", t.Agent.Spec.SharedPoolID)
}
