package agentbuild

import (
	"context"

	vmoperatorv1beta1 "github.com/VictoriaMetrics/operator/api/operator/v1beta1"
	"github.com/pingcap/errors"
	rbacv1 "k8s.io/api/rbac/v1"
	schedulingv1 "k8s.io/api/scheduling/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/observability/agent-control-server/pkg/helmcli"
	"github.com/tidbcloud/observability/agent-control-server/pkg/k8sclient/k8scliv1"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util"
)

// InstallOrUpgradeHelm installs or upgrades a Helm chart using the provided builder function to generate values.
//
// It builds a Helm client and performs the installation or upgrade with the specified chart details.
// labels is stamped on the release; pass nil to skip labeling.
func InstallOrUpgradeHelm(
	ctx context.Context,
	cliGetter k8scliv1.ClientGetter,
	builder func() (string, error),
	chartName,
	chartVersion,
	releaseName,
	namespace string,
	labels map[string]string,
) error {
	values, err := builder()
	if err != nil {
		return err
	}
	helm, err := helmcli.BuildHelmClient(ctx, cliGetter, namespace)
	if err != nil {
		return err
	}
	return helmcli.InstallOrUpgradeChart(
		ctx,
		helm,
		releaseName,
		chartName,
		namespace,
		chartVersion,
		values,
		labels,
	)
}

// CreateOrUpdateVMAgent creates or updates a VMAgent resource using the provided builder function.
//
// It uses server-side apply to create or update the resource, merging metadata from the builder.
func CreateOrUpdateVMAgent(ctx context.Context, cli client.Client, builder func() (*vmoperatorv1beta1.VMAgent, error)) error {
	v, err := builder()
	if err != nil {
		return err
	}
	target := &vmoperatorv1beta1.VMAgent{
		ObjectMeta: metav1.ObjectMeta{
			Name:      v.Name,
			Namespace: v.Namespace,
		},
	}
	_, err = controllerutil.CreateOrUpdate(ctx, cli, target, func() error {
		target.Spec = v.Spec
		util.MergeMetadata(&target.ObjectMeta, v.ObjectMeta)
		return nil
	})
	return err
}

// CreateOrUpdateVMPodScrape creates or updates a VMPodScrape resource using the provided builder function.
//
// It uses server-side apply to create or update the resource, merging metadata from the builder.
func CreateOrUpdateVMPodScrape(ctx context.Context, cli client.Client, builder func() (*vmoperatorv1beta1.VMPodScrape, error)) error {
	v, err := builder()
	if err != nil {
		return err
	}
	target := &vmoperatorv1beta1.VMPodScrape{
		ObjectMeta: metav1.ObjectMeta{
			Name:      v.Name,
			Namespace: v.Namespace,
		},
	}
	_, err = controllerutil.CreateOrUpdate(ctx, cli, target, func() error {
		target.Spec = v.Spec
		util.MergeMetadata(&target.ObjectMeta, v.ObjectMeta)
		return nil
	})
	return err
}

// CreateOrUpdateVMServiceScrape creates or updates a VMServiceScrape resource using the provided builder function.
//
// It uses server-side apply to create or update the resource, merging metadata from the builder.
func CreateOrUpdateVMServiceScrape(ctx context.Context, cli client.Client, builder func() (*vmoperatorv1beta1.VMServiceScrape, error)) error {
	v, err := builder()
	if err != nil {
		return err
	}
	target := &vmoperatorv1beta1.VMServiceScrape{
		ObjectMeta: metav1.ObjectMeta{
			Name:      v.Name,
			Namespace: v.Namespace,
		},
	}
	_, err = controllerutil.CreateOrUpdate(ctx, cli, target, func() error {
		target.Spec = v.Spec
		util.MergeMetadata(&target.ObjectMeta, v.ObjectMeta)
		return nil
	})
	return err
}

// CreateOrUpdateRole creates or updates a Role resource using the provided builder function.
//
// It uses server-side apply to create or update the resource, merging metadata from the builder.
func CreateOrUpdateRole(ctx context.Context, cli client.Client, builder func() (*rbacv1.Role, error)) error {
	v, err := builder()
	if err != nil {
		return err
	}
	target := &rbacv1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name:      v.Name,
			Namespace: v.Namespace,
		},
	}
	_, err = controllerutil.CreateOrUpdate(ctx, cli, target, func() error {
		target.Rules = v.Rules
		util.MergeMetadata(&target.ObjectMeta, v.ObjectMeta)
		return nil
	})
	return err
}

// CreateOrUpdateRoleBinding creates or updates a RoleBinding resource using the provided builder function.
//
// It uses server-side apply to create or update the resource, merging metadata from the builder.
func CreateOrUpdateRoleBinding(ctx context.Context, cli client.Client, builder func() (*rbacv1.RoleBinding, error)) error {
	v, err := builder()
	if err != nil {
		return err
	}
	target := &rbacv1.RoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:      v.Name,
			Namespace: v.Namespace,
		},
	}
	_, err = controllerutil.CreateOrUpdate(ctx, cli, target, func() error {
		target.Subjects = v.Subjects
		target.RoleRef = v.RoleRef
		util.MergeMetadata(&target.ObjectMeta, v.ObjectMeta)
		return nil
	})
	return err
}

// CreateAgentPriorityClass creates a PriorityClass for the o11y agent with a predefined high value.
//
// The priority value is set to 1000000000-2, which is near the maximum allowed user-defined priority.
func CreateAgentPriorityClass(ctx context.Context, cli client.Client, name string) error {
	return CreateAgentPriorityClassWithValue(ctx, cli, name, 1000000000-2)
}

// CreateAgentPriorityClassWithValue creates a PriorityClass for the o11y agent with the specified value.
//
// The maximum allowed value of a user defined priority is 1000000000.
func CreateAgentPriorityClassWithValue(ctx context.Context, cli client.Client, name string, value int32) error {
	priorityClass := &schedulingv1.PriorityClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		// Maximum allowed value of a user defined priority is 1000000000
		Value:         value,
		GlobalDefault: false,
		Description:   "Priority class for o11y agent",
	}
	_, err := controllerutil.CreateOrUpdate(ctx, cli, priorityClass, func() error { return nil })
	if err != nil {
		return errors.Annotate(err, "fail to create or update priority class")
	}
	return nil
}
