package parent

import (
	"context"
	"fmt"

	k8sapiv1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	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/pkg/common/deploy"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/serverless/task"

	observabilitypingcapcomv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
)

const (
	vmInsertSvcPrefix  = "vminsert-"
	vmSelectSvcPrefix  = "vmselect-"
	externalNameSuffix = ".observability.svc.cluster.local"
)

func makeVMSelectExternalService(clusterv1 *observabilitypingcapcomv1.Cluster) (client.Object, error) {
	return makeVMExternalService(clusterv1.Namespace, vmSelectSvcPrefix, clusterv1.Spec.VictoriaAccount.VMClusterName, clusterv1.Name)
}

func makeVMInsertExternalService(clusterv1 *observabilitypingcapcomv1.Cluster) (client.Object, error) {
	return makeVMExternalService(clusterv1.Namespace, vmInsertSvcPrefix, clusterv1.Spec.VictoriaAccount.VMClusterName, clusterv1.Name)
}

func makeVMExternalService(ns, vmComponentPrefix string, vmClusterName, clusterID string) (client.Object, error) {
	var externalService k8sapiv1.Service
	externalService.SetName(vmComponentPrefix + clusterID)
	externalService.SetNamespace(ns)
	externalService.Spec.Type = k8sapiv1.ServiceTypeExternalName
	externalService.Spec.ExternalName = vmComponentPrefix + vmClusterName + externalNameSuffix
	return &externalService, nil
}

type ExternalService struct {
	*task.BaseTask
}

func NewExternalService(ctx *task.Context) *ExternalService {
	return &ExternalService{BaseTask: &task.BaseTask{Context: ctx}}
}

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

func (t *ExternalService) Create(ctx context.Context) deploy.Result {
	if t.Remote == nil {
		return deploy.Panic(fmt.Errorf("remote client is not initialized"))
	}
	if t.Cluster == nil {
		return deploy.Panic(fmt.Errorf("cluster context is nil"))
	}

	makeFns := []func(*observabilitypingcapcomv1.Cluster) (client.Object, error){
		makeVMInsertExternalService,
		makeVMSelectExternalService,
	}

	for _, makeFn := range makeFns {
		desiredObj, err := makeFn(t.Cluster)
		if err != nil {
			return deploy.Error(err)
		}

		desiredSvc, ok := desiredObj.(*k8sapiv1.Service)
		if !ok {
			return deploy.Error(fmt.Errorf("unexpected service type %T", desiredObj))
		}

		svc := &k8sapiv1.Service{ObjectMeta: metav1.ObjectMeta{
			Name:      desiredSvc.GetName(),
			Namespace: desiredSvc.GetNamespace(),
		}}

		if _, err := controllerutil.CreateOrUpdate(ctx, t.Remote, svc, func() error {
			svc.Spec = desiredSvc.Spec
			return nil
		}); err != nil {
			return deploy.Error(err)
		}
	}

	return deploy.Done()
}

func (t *ExternalService) Delete(ctx context.Context) deploy.Result {
	if t.Remote == nil {
		return deploy.Panic(fmt.Errorf("remote client is not initialized"))
	}
	if t.Cluster == nil {
		return deploy.Panic(fmt.Errorf("cluster context is nil"))
	}

	serviceNames := []string{
		vmInsertSvcPrefix + t.Cluster.Name,
		vmSelectSvcPrefix + t.Cluster.Name,
	}

	for _, name := range serviceNames {
		svc := &k8sapiv1.Service{}
		key := client.ObjectKey{Namespace: t.Cluster.Namespace, Name: name}
		if err := t.Remote.Get(ctx, key, svc); err != nil {
			if apierrors.IsNotFound(err) {
				continue
			}
			return deploy.Error(err)
		}
		if err := t.Remote.Delete(ctx, svc); err != nil && !apierrors.IsNotFound(err) {
			if apierrors.IsNotFound(err) {
				continue
			}
			return deploy.Error(err)
		}
	}
	return deploy.Done()
}
