package common

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"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/common/agentcontrolutils"
	o11yapi "github.com/tidbcloud/infra-provider/regional-server/pkg/common/o11ycli/api"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	agentcontrolapi "github.com/tidbcloud/observability/agent-control-server/pkg/agentcontrol/api"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type O11y struct {
	*task.BaseTask
}

var _ deploy.Task = &O11y{}

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

func NewO11y(ctx *task.Context) *O11y {
	return &O11y{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

func (t *O11y) Create(ctx context.Context) deploy.Result {
	if !t.Config.IsEnableO11yService() {
		t.Logger.Info("o11y service is disabled, skip creating o11y route")
		return deploy.Done()
	}
	t.BeginAt = util.Pointer(time.Now())

	labels := t.APPBox.GetLabels()
	if len(labels) == 0 {
		return deploy.Panic(fmt.Errorf("appbox %s/%s has no labels, cannot create o11y", t.APPBox.Namespace, t.APPBox.Name))
	}
	tenantID, hasTenant := labels[v1alpha1.TagTenantKey]
	projectID, hasProject := labels[v1alpha1.TagProjectKey]
	if !hasTenant || !hasProject {
		return deploy.Panic(fmt.Errorf("appbox missing required labels: tenant/project"))
	}
	if config.FeatureEnabled(t.Config.FeatureGates, config.O11yNewAgentController, tenantID, projectID) {
		// Check if O11yRoute already exists.
		existingRoute := &v1alpha1.O11yRoute{}
		err := t.Local.Get(ctx, types.NamespacedName{Name: t.APPBox.Name, Namespace: t.APPBox.Namespace}, existingRoute)
		if err != nil {
			if !apierrors.IsNotFound(err) {
				return deploy.Panic(fmt.Errorf("failed to get o11y route: %w", err))
			}
			// O11yRoute doesn't exist, this means that this is a new project,
			// so redirected to the new path.
			dataplaneCfg, err := agentcontrolutils.BuildDataplaneConfig(ctx, t.Local, t.APPBox)
			if err != nil {
				return deploy.Panic(fmt.Errorf("failed to build dataplane config: %w", err))
			}
			_, err = t.AgentControlAPI.CreateK8sAgent(ctx, &agentcontrolapi.CreateK8sAgentRequest{
				TenantID:  tenantID,
				ProjectID: projectID,
				Dataplane: *dataplaneCfg,
			})
			if err != nil {
				return deploy.Panic(fmt.Errorf("failed to create o11y agent: %w", err))
			}
			return deploy.Done()
		}
	}

	o11yRoute := &v1alpha1.O11yRoute{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.APPBox.Name,
			Namespace: t.APPBox.Namespace,
			Labels:    t.APPBox.Labels,
		},
		Spec: v1alpha1.O11yRouteSpec{
			ApplicationID: o11yapi.ApplicationIDInfra,
			VPCRef:        *t.APPBox.Spec.VPCRef,
			APPBoxRef: v1alpha1.Reference{
				Name:      t.APPBox.Name,
				Namespace: t.APPBox.Namespace,
			},
		},
	}
	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, o11yRoute, func() error {
		return nil
	})

	return deploy.ErrorOrDone(err)
}

func (t *O11y) Delete(ctx context.Context) deploy.Result {
	o11yRoute := &v1alpha1.O11yRoute{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.APPBox.Name,
			Namespace: t.APPBox.Namespace,
		},
	}

	labels := t.APPBox.GetLabels()
	if len(labels) > 0 {
		tenantID, hasTenant := labels[v1alpha1.TagTenantKey]
		projectID, hasProject := labels[v1alpha1.TagProjectKey]
		if hasTenant && hasProject {
			if config.FeatureEnabled(t.Config.FeatureGates, config.O11yNewAgentController, tenantID, projectID) {
				if _, err := t.AgentControlAPI.DeleteK8sAgent(ctx, &agentcontrolapi.DeleteK8sAgentRequest{ProjectID: projectID}); err != nil {
					return deploy.Panic(fmt.Errorf("failed to delete o11y agent: %w", err))
				}
				// Wait for the K8s agent to be fully deleted before proceeding.
				_, err := t.AgentControlAPI.GetK8sAgent(ctx, &agentcontrolapi.GetK8sAgentRequest{ProjectID: projectID})
				if err != nil {
					if errors.Is(err, agentcontrolapi.ErrNotFound) {
						// Agent has been fully deleted, safe to proceed.
						return deploy.Done()
					}
					return deploy.Panic(fmt.Errorf("failed to get o11y agent status: %w", err))
				}
				// Agent still exists (possibly deleting), requeue to wait for completion.
				return deploy.Panic(fmt.Errorf("waiting for k8s agent deletion to complete for project %s", projectID)).WithRequeuAfter(30 * time.Second)
			}
		}
	}

	// block the other deleting operations until the o11yroute is deleted
	return deploy.PanicOrDone(k8s.DeleteObjectCompleted(ctx, t.Local, o11yRoute))
}
