package parent

import (
	"context"
	"fmt"

	observabilitypingcapcomv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/deploy"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/serverless/task"

	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"
)

type EndpointService struct {
	*task.BaseTask
}

func NewEndpointService(ctx *task.Context) *EndpointService {
	return &EndpointService{BaseTask: &task.BaseTask{Context: ctx}}
}

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

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

	desired, err := t.makeEndpointService()
	if err != nil {
		return deploy.Error(err)
	}

	endpointSvc := &observabilitypingcapcomv1.EndpointService{ObjectMeta: metav1.ObjectMeta{
		Name:      desired.GetName(),
		Namespace: desired.GetNamespace(),
	}}

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

	if endpointSvc.Status.Phase == constants.ACTIVE {
		t.Cluster.Status.ServiceName = endpointSvc.Status.ServiceName
		t.Cluster.Status.DnsName = t.Config.AZConfig[t.Cluster.Spec.VictoriaAccount.AvailabilityZone].DnsName
	} else {
		return deploy.Panic(fmt.Errorf("endpoint service is not ready, phase: %s", endpointSvc.Status.Phase))
	}

	return deploy.Done()
}

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

	if err := t.ensureEndpointServiceDeleted(ctx); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *EndpointService) makeEndpointService() (*observabilitypingcapcomv1.EndpointService, error) {
	endpoint := &observabilitypingcapcomv1.EndpointService{}
	endpoint.SetNamespace(t.Cluster.Namespace)
	endpoint.SetName(svcPrefix + t.Cluster.Spec.Endpoint.Name)
	endpoint.Spec.ServiceName = svcPrefix + t.Cluster.Spec.Endpoint.Name
	if userArn := t.Cluster.Spec.Endpoint.UserARN; userArn != "" {
		endpoint.Spec.UserARNs = append(endpoint.Spec.UserARNs, userArn)
	}
	endpoint.Spec.Shared = t.Cluster.Spec.Endpoint.Shared

	if endpoint.Spec.Shared {
		az := t.Cluster.Spec.VictoriaAccount.AvailabilityZone
		azCfg, ok := t.Config.AZConfig[az]
		if !ok {
			return nil, fmt.Errorf("az %s not found in config", az)
		}
		endpoint.Spec.EndpointServiceID = azCfg.EndpointServiceID
	}

	return endpoint, nil
}

func (t *EndpointService) ensureEndpointServiceDeleted(ctx context.Context) error {
	endpoint := &observabilitypingcapcomv1.EndpointService{}
	key := client.ObjectKey{Namespace: t.Cluster.Namespace, Name: svcPrefix + t.Cluster.Spec.Endpoint.Name}
	if err := t.Remote.Get(ctx, key, endpoint); err != nil {
		if apierrors.IsNotFound(err) {
			return nil
		}
		return err
	}
	if err := t.Remote.Delete(ctx, endpoint); err != nil && !apierrors.IsNotFound(err) {
		return err
	}
	return nil
}
