package common

import (
	"context"
	"fmt"
	"time"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"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/util"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
)

const (
	kubeDNSNamespace = "kube-system"
	kubeDNSName      = "kube-dns"
)

type NodeLocalDNS struct {
	*task.BaseTask
}

func NewNodeLocalDNS(ctx *task.Context) *NodeLocalDNS {
	return &NodeLocalDNS{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

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

func (t *NodeLocalDNS) 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)
	}

	dnsSvc := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: kubeDNSNamespace,
			Name:      kubeDNSName,
		},
	}

	if err := cli.Get(ctx, client.ObjectKeyFromObject(dnsSvc), dnsSvc); client.IgnoreNotFound(err) != nil {
		return deploy.Error(err)
	}
	svcIP := dnsSvc.Spec.ClusterIP
	chart := t.InfraTmplConfig.NodeLocalDNS.DeepCopy()
	if svcIP != "" {
		if cfg, ok := chart.HelmValues["config"].(map[string]interface{}); ok {
			cfg["dnsServer"] = svcIP
		} else {
			chart.HelmValues["config"] = map[string]interface{}{
				"dnsServer": svcIP,
			}
		}
	}
	return deploy.ErrorOrDone(t.ChartApplier.Apply(ctx, t.Remote, chart))
}

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

	return deploy.ErrorOrDone(ensureNodeLocalDNSReady(ctx, cli, t.InfraTmplConfig.NodeLocalDNS))
}

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

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