package common

import (
	"context"
	"fmt"
	"time"

	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/chart/loader"
	appsv1 "k8s.io/api/apps/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/apimachinery/pkg/types"

	"github.com/tidbcloud/eks-provider/controllers/network/task"
	"github.com/tidbcloud/eks-provider/controllers/utils"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"
	"github.com/tidbcloud/infra-provider/pkg/metrics"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

// KubeStateMetrics is a task to deploy kube-state-metrics (KSM),
// which is a simple service that listens to the Kubernetes API server
// and generates metrics about the state of the objects.
type KubeStateMetrics struct {
	*task.BaseTask
	config *KSMConfig
}

type KSMConfig struct {
	helmChartPath string
	image         config.ImageConfig
}

func (c *KSMConfig) GetNamespace() string {
	return task.ObservabilityNamespace
}

func (c *KSMConfig) GetName() string {
	return "kube-state-metrics"
}

func (c *KSMConfig) GetCRDs() ([]*apiextensionsv1.CustomResourceDefinition, error) {
	return nil, nil
}

func (c *KSMConfig) ToHelmChart() (result *chart.Chart, err error) {
	result, err = loader.Load(c.helmChartPath)
	if err != nil {
		return nil, fmt.Errorf("failed to load helm chart %q: %w", c.helmChartPath, err)
	}
	return
}

func (c *KSMConfig) ToHelmValues() map[string]interface{} {
	return map[string]interface{}{
		"replicas": 1,
		"image": map[string]string{
			"registry":   c.image.Registry,
			"repository": c.image.Repo,
			"tag":        c.image.Tag,
		},
		"resources": map[string]interface{}{
			"requests": map[string]string{
				"cpu":    "100m",
				"memory": "250Mi",
			},
			"limits": map[string]string{
				"cpu":    "200m",
				"memory": "500Mi",
			},
		},
		"extraArgs": []string{
			"--metric-labels-allowlist=nodes=[*]", // Add node labels to metrics labels.
		},
	}
}

func NewKubeStateMetrics(ctx *task.Context) *KubeStateMetrics {
	return &KubeStateMetrics{
		BaseTask: &task.BaseTask{Context: ctx},
		config: &KSMConfig{
			helmChartPath: ctx.Config.Network.KubeStateMetrics.ChartPath,
			image:         ctx.Config.Network.KubeStateMetrics.Image,
		},
	}
}

var _ helm.Package = &KSMConfig{}

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

func (t *KubeStateMetrics) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, t.config))
}

func (t *KubeStateMetrics) PostCreate(ctx context.Context) (res deploy.Result) {
	defer func() {
		metrics.UpdateO11yComponentHealthStatus(t.Network, t.config.GetNamespace(), t.Name(), res.IsDoneState())
	}()

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

	var app appsv1.Deployment
	if err := cli.Get(ctx, types.NamespacedName{
		Namespace: t.config.GetNamespace(),
		Name:      t.config.GetName(),
	}, &app); err != nil {
		return deploy.Error(fmt.Errorf("failed to get KubeStateMetrics Deployment: %v", err))
	}

	if utils.IsStatusConditionTrue(app.Status.Conditions, appsv1.DeploymentAvailable) {
		return deploy.Done()
	}

	return deploy.Error(fmt.Errorf("KubeStateMetrics is not ready"))
}

func (t *KubeStateMetrics) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

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