package common

import (
	"context"
	"fmt"
	"time"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/controllers/common"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
)

type DataflowAgent struct {
	*task.BaseTask
	config *dataflowAgentConfig
}

type dataflowAgentConfig struct {
	config.InfraComponentConfig
	lbServiceTags           map[string]string
	overwriteImageTag       string
	overwritePodHostNetwork *bool
}

func (c *dataflowAgentConfig) ToHelmValues() map[string]interface{} {
	values := c.InfraComponentConfig.ToHelmValues()
	svc := values["service"]
	svc.(map[string]interface{})["annotations"] = map[string]string{
		"service.beta.kubernetes.io/aws-load-balancer-scheme": "internet-facing",
		// `external` indicates it's managed by out-of-tree lb-controller
		// see https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/service/annotations/#traffic-routing
		"service.beta.kubernetes.io/aws-load-balancer-type":                     "external",
		"service.beta.kubernetes.io/aws-load-balancer-nlb-target-type":          "instance",
		"service.beta.kubernetes.io/aws-load-balancer-target-group-attributes":  "preserve_client_ip.enabled=true",
		"service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags": cloudv1.TagStringForAWS(c.lbServiceTags),
	}
	if c.overwriteImageTag != "" {
		image := values["image"]
		image.(map[string]interface{})["tag"] = c.overwriteImageTag
	}
	if podHostNetwork := c.overwritePodHostNetwork; podHostNetwork != nil {
		values["podHostNetwork"] = *podHostNetwork
	}
	return values
}

func NewDataflowAgent(ctx *task.Context) *DataflowAgent {
	t := &task.BaseTask{Context: ctx}

	return &DataflowAgent{
		BaseTask: t,
		config: &dataflowAgentConfig{
			InfraComponentConfig: ctx.DPTmplConfig.DataflowAgent,
		},
	}
}

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

func (t *DataflowAgent) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	isLegacy := t.APPBox.Labels[v1alpha1.IsLegacyKey] == "true"
	tags := util.MergeMaps(cloudv1.TagsFromLabels(t.APPBox.Labels), map[string]string{
		common.Component: "dataflow-agent",
		common.UsedBy:    "dbaas-control-plane-shoot",
	})
	t.config.lbServiceTags = tags
	if tag := t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnImageTagDataflowAgent]; tag != "" {
		t.config.overwriteImageTag = tag
	}
	if isLegacy {
		podHostNetwork := false
		t.config.overwritePodHostNetwork = &(podHostNetwork)
	}
	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, t.config))
}

func (t *DataflowAgent) PostCreate(ctx context.Context) deploy.Result {
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	var service corev1.Service
	if err := cli.Get(ctx, types.NamespacedName{
		Namespace: t.config.GetNamespace(),
		Name:      t.config.GetName(),
	}, &service); err != nil {
		return deploy.Panic(err)
	}
	if service.Spec.Type == corev1.ServiceTypeLoadBalancer {
		if len(service.Status.LoadBalancer.Ingress) < 1 {
			t.Logger.Info("waiting for load balancer to be ready")
			return deploy.Error(fmt.Errorf("waiting for load balancer to be provisioned"))
		}
		var addr string
		switch t.APPBox.Spec.Provider {
		case consts.ProviderTypeEKS:
			addr = service.Status.LoadBalancer.Ingress[0].Hostname
		case consts.ProviderTypeGKE:
			addr = service.Status.LoadBalancer.Ingress[0].IP
		case consts.ProviderTypeAKS:
			addr = service.Status.LoadBalancer.Ingress[0].IP
		default:
			t.Logger.Error(fmt.Errorf("unsupported provider type"), "unsupported provider type", "provider", t.APPBox.Spec.Provider)
		}
		t.APPBox.Status.DataflowAgentUrl = addr
	}
	return deploy.ErrorOrDone(controllers.EnsureChartDeploymentReady(ctx, t.Remote, types.NamespacedName{
		Namespace: t.config.GetNamespace(),
		Name:      t.config.GetName(),
	}))
}

func (t *DataflowAgent) Delete(ctx context.Context) deploy.Result {
	if _, ok := t.Remote.(*k8s.EmptyGetter); ok {
		t.Logger.Info("remote client is empty getter, skip deleting")
		return deploy.Done()
	}
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	return deploy.PanicOrDone(k8s.DeleteObjectCompleted(ctx, cli, &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: t.config.GetNamespace(),
			Name:      t.config.GetName(),
		},
	}))
}
