package reconciler

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	"golang.org/x/time/rate"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/observability/agent-control-server/pkg/cloud/alicloud"
	"github.com/tidbcloud/observability/agent-control-server/pkg/cloud/aws"
	"github.com/tidbcloud/observability/agent-control-server/pkg/cloud/gcp"
	"github.com/tidbcloud/observability/agent-control-server/pkg/config"
	"github.com/tidbcloud/observability/agent-control-server/pkg/deploy"
	"github.com/tidbcloud/observability/agent-control-server/pkg/k8sclient/k8scliv1"
	"github.com/tidbcloud/observability/agent-control-server/pkg/o11y"
)

type Reconciler struct {
	client.Client
	Scheme                  *runtime.Scheme
	EventRecorder           record.EventRecorder
	Config                  *config.RootConfig
	Templates               config.Templates
	AWS                     aws.Client
	GCP                     gcp.Client
	AliCloud                alicloud.Client
	O11yAPI                 o11y.O11yInfraAPIClientV2
	MaxConcurrentReconciles int
}

type Context[T any] struct {
	// Agent is the target object that this task is working on.
	Agent T
	// L is the logger injected from the caller.
	L logr.Logger
	// Local refers to local controlplane's apiserver.
	Local client.Client
	// Remote refers to dataplane's apiserver.
	Remote k8scliv1.ClientGetter
	// Config contains the root configuration for the agent-controller.
	Config *config.RootConfig
	// Templates contains the configuration templates for the agents.
	Templates config.Templates
	// AWS refers to the AWS client.
	AWS aws.Client
	// GCP refers to the GCP client.
	GCP gcp.Client
	// AliCloud refers to the AliCloud client.
	AliCloud alicloud.Client
	// O11yAPI refers to the observability API client.
	O11yAPI o11y.O11yInfraAPIClientV2
}

var _ deploy.Task = &BaseTask[any]{}

type BaseTask[T any] struct {
	*Context[T]
}

func (t *BaseTask[T]) Name() string {
	return "BaseTask"
}

func (t *BaseTask[T]) ClusterMeta() deploy.ClusterMeta {
	return deploy.ClusterMeta{}
}

func (t *BaseTask[T]) Create(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask[T]) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask[T]) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *BaseTask[T]) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

const (
	controllerBaseBackoff = 6 * time.Millisecond
	controllerMaxBackoff  = 120 * time.Second
)

func DefaultControllerRateLimiter[T comparable]() workqueue.TypedRateLimiter[T] {
	return workqueue.NewTypedMaxOfRateLimiter(
		workqueue.NewTypedItemExponentialFailureRateLimiter[T](controllerBaseBackoff, controllerMaxBackoff),
		// Keep the same overall QPS/burst as client-go defaults.
		&workqueue.TypedBucketRateLimiter[T]{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
	)
}
