package testkits

import (
	"context"
	"errors"

	"helm.sh/helm/v3/pkg/action"
	"k8s.io/client-go/rest"
	resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	"github.com/tidbcloud/observability/agent-control-server/pkg/config"
)

// BuildClient creates a fake client with the given objects and scheme.
func BuildClient(objs ...client.Object) client.Client {
	return fake.NewClientBuilder().
		WithScheme(Scheme()).
		WithObjects(objs...).
		Build()
}

type ClientGetter struct {
	client.Client

	cliErr  error
	mcliErr error
	helmErr error

	deleteFunc func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error
	createFunc func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error
	listFunc   func(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error
}

func NewClientGetter(cli client.Client) *ClientGetter {
	return &ClientGetter{
		Client: cli,
	}
}

func (m *ClientGetter) WithGetClientError(err error) *ClientGetter {
	m.cliErr = err
	return m
}

func (m *ClientGetter) WithGetHelmError(err error) *ClientGetter {
	m.helmErr = err
	return m
}

func (m *ClientGetter) WithMetricsClientError(err error) *ClientGetter {
	m.mcliErr = err
	return m
}

func (m *ClientGetter) WithCreateFunc(f func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error) *ClientGetter {
	m.createFunc = f
	return m
}

func (m *ClientGetter) WithDeleteFunc(f func(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error) *ClientGetter {
	m.deleteFunc = f
	return m
}

func (m *ClientGetter) WithListFunc(f func(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error) *ClientGetter {
	m.listFunc = f
	return m
}

func (m *ClientGetter) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
	if m.createFunc != nil {
		return m.createFunc(ctx, obj, opts...)
	}
	return m.Client.Create(ctx, obj, opts...)
}

func (m *ClientGetter) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
	if m.deleteFunc != nil {
		return m.deleteFunc(ctx, obj, opts...)
	}
	return m.Client.Delete(ctx, obj, opts...)
}

func (m *ClientGetter) List(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error {
	if m.listFunc != nil {
		return m.listFunc(ctx, obj, opts...)
	}
	return m.Client.List(ctx, obj, opts...)
}

func (m *ClientGetter) GetClient(ctx context.Context) (client.Client, error) {
	if m.cliErr != nil {
		return nil, m.cliErr
	}
	return m.Client, nil
}

func (m *ClientGetter) GetHelmConfig(ctx context.Context) (*action.Configuration, error) {
	if m.helmErr != nil {
		return nil, m.helmErr
	}
	return &action.Configuration{}, nil
}

func (m *ClientGetter) GetMetricsClient(ctx context.Context) (resourceclient.MetricsV1beta1Interface, error) {
	if m.mcliErr != nil {
		return nil, m.mcliErr
	}
	return nil, nil
}

// TaskOptions contains common options for creating task test instances.
type TaskOptions struct {
	// Client is the client for the target cluster
	Client client.Client

	// ClientError if true, mock will return error on GetClient
	ClientError bool

	// HelmError if true, mock will return error on GetHelmConfig
	HelmError bool

	// MetricsClientErr if true, mock will return error on GetMetricsClient
	MetricsClientErr bool

	// Config is the configuration for the task
	Config config.RootConfig

	// Templates for rendering manifests
	Templates config.Templates

	// LocalObjects are additional objects to add to the fake client
	LocalObjects []client.Object
}

// NewClientGetterWithOpts creates a ClientGetter based on the TaskOptions.
func NewClientGetterWithOpts(opts TaskOptions) *ClientGetter {
	var helmErr error
	if opts.HelmError {
		helmErr = errors.New("helm config error")
	}
	var cliErr error
	if opts.ClientError {
		cliErr = errors.New("remote client error")
	}

	return NewClientGetter(opts.Client).WithGetClientError(cliErr).WithGetHelmError(helmErr)
}

// ClientManager is a mock implementation of a client manager that can be used in tests to
// provide custom behavior for GetClient, GetConfig, and GetMetricsClient.
type ClientManager struct {
	getClient        func(ctx context.Context, key client.ObjectKey) (client.Client, error)
	getConfig        func(ctx context.Context, key client.ObjectKey) (*rest.Config, error)
	getMetricsClient func(ctx context.Context, key client.ObjectKey) (resourceclient.MetricsV1beta1Interface, error)
}

func NewClientManager() *ClientManager {
	return &ClientManager{}
}

func (m *ClientManager) WithGetClient(f func(context.Context, client.ObjectKey) (client.Client, error)) *ClientManager {
	m.getClient = f
	return m
}

func (m *ClientManager) WithGetConfig(f func(context.Context, client.ObjectKey) (*rest.Config, error)) *ClientManager {
	m.getConfig = f
	return m
}

func (m *ClientManager) WithGetMetricsClient(f func(context.Context, client.ObjectKey) (resourceclient.MetricsV1beta1Interface, error)) *ClientManager {
	m.getMetricsClient = f
	return m
}

func (m *ClientManager) GetClient(ctx context.Context, key client.ObjectKey) (client.Client, error) {
	if m.getClient != nil {
		return m.getClient(ctx, key)
	}
	return nil, nil
}

func (m *ClientManager) GetConfig(ctx context.Context, key client.ObjectKey) (*rest.Config, error) {
	if m.getConfig != nil {
		return m.getConfig(ctx, key)
	}
	return nil, nil
}

func (m *ClientManager) GetMetricsClient(ctx context.Context, key client.ObjectKey) (resourceclient.MetricsV1beta1Interface, error) {
	if m.getMetricsClient != nil {
		return m.getMetricsClient(ctx, key)
	}
	return nil, nil
}
