package common

import (
	"context"
	"fmt"
	"strings"
	"time"

	appsv1 "k8s.io/api/apps/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
)

// FIXME: daemonSetName is not equal to the releaseName,
// find a better way for status checking to avoid hard-code.
const daemonSetName = "node-exporter-prometheus-node-exporter"

// NodeExporter is a task to deploy node exporter,
// which is a Prometheus exporter for hardware and OS metrics exposed by *NIX kernels.
type NodeExporter struct {
	*task.BaseTask
}

func NewNodeExporter(ctx *task.Context) *NodeExporter {
	return &NodeExporter{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

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

func (t *NodeExporter) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())

	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	// TODO(just1900): re-consider node-exporter chart compatibility issue when migration.
	// The logic below will result in the node-exporter chart being unmanaged.
	// It is incompatible from 3.x to 4.x node-exporter helm charts
	// Ignore existing node-exporter ds to avoid compatibility issues
	var app appsv1.DaemonSet
	if err := cli.Get(ctx, types.NamespacedName{
		Namespace: task.ObservabilityNamespace,
		Name:      daemonSetName,
	}, &app); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, &t.InfraTmplConfig.NodeExporter))
		}
		return deploy.Error(fmt.Errorf("failed to get NodeExporter Daemonset: %v", err))
	}
	// If the node-exporter is 3.x, ignore it.
	// upgrade to 4.x need to delete the daemonset first. we will do it in future.
	if strings.HasPrefix(app.GetLabels()["helm.sh/chart"], "prometheus-node-exporter-3.") {
		return deploy.Done()
	}

	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, &t.InfraTmplConfig.NodeExporter))
}

func (t *NodeExporter) PostCreate(ctx context.Context) (res deploy.Result) {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	return deploy.ErrorOrDone(ensureNodeExporterReady(ctx, cli, t.InfraTmplConfig.NodeExporter))
}

func ensureNodeExporterReady(ctx context.Context, cli client.Client, nodeExporter config.InfraComponentConfig) error {
	var app appsv1.DaemonSet
	if err := cli.Get(ctx, types.NamespacedName{
		Namespace: nodeExporter.GetNamespace(),
		Name:      daemonSetName,
	}, &app); err != nil {
		return err
	}

	if app.Status.NumberReady != app.Status.DesiredNumberScheduled {
		return fmt.Errorf("NodeExporter is not ready")
	}
	return nil
}
