package k8scliv2

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"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"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/loader"
	"github.com/tidbcloud/observability/agent-control-server/pkg/testkits"
)

type getClientTestCase struct {
	name    string
	cluster client.Object
	wantErr bool
	errMsg  string
}

func runGetClientTests(
	t *testing.T,
	tests []getClientTestCase,
	clusterName string,
	newManager func(client.Client, client.Options) ClientManager,
	getFunc string,
) {
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var objs []client.Object
			if tt.cluster != nil {
				objs = append(objs, tt.cluster)
			}

			fakeCli := fake.NewClientBuilder().WithScheme(loader.ProviderScheme()).WithObjects(objs...).Build()
			manager := newManager(fakeCli, client.Options{})

			key := client.ObjectKey{Name: clusterName}
			if tt.cluster == nil {
				key = client.ObjectKey{Name: "non-existent"}
			}

			var err error
			if getFunc == "GetClient" {
				_, err = manager.GetClient(context.Background(), key)
			} else {
				_, err = manager.GetMetricsClient(context.Background(), key)
			}
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			if err != nil {
				assert.Contains(t, err.Error(), "exec plugin")
				return
			}
		})
	}
}

type getConfigTestCase struct {
	name           string
	cluster        client.Object
	setupResources []client.Object
	wantErr        bool
	errMsg         string
	verify         func(*testing.T, *rest.Config)
}

func runGetConfigTests(t *testing.T, tests []getConfigTestCase, clusterName string, newManager func(client.Client, client.Options) ClientManager) {
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var objs []client.Object
			if tt.cluster != nil {
				objs = append(objs, tt.cluster)
			}
			objs = append(objs, tt.setupResources...)

			fakeCli := fake.NewClientBuilder().WithScheme(loader.ProviderScheme()).WithObjects(objs...).Build()
			manager := newManager(fakeCli, client.Options{})

			key := client.ObjectKey{Name: clusterName}
			if tt.cluster == nil {
				key = client.ObjectKey{Name: "non-existent"}
			}

			cfg, err := manager.GetConfig(context.Background(), key)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
			assert.NotNil(t, cfg)
			assert.True(t, cfg.Insecure)

			if tt.verify != nil {
				tt.verify(t, cfg)
			}
		})
	}
}

func TestNetworkClientGetter(t *testing.T) {
	tests := []struct {
		name       string
		provider   string
		rootConfig *config.RootConfig
		wantErr    bool
		errMsg     string
	}{
		{
			name:     "returns client getter for EKS provider",
			provider: consts.ProviderTypeEKS,
			rootConfig: &config.RootConfig{
				DataplaneAssumeRoleName: "",
			},
		},
		{
			name:     "returns client getter for GKE provider",
			provider: consts.ProviderTypeGKE,
			rootConfig: &config.RootConfig{
				DataplaneAssumeRoleName: "",
			},
		},
		{
			name:     "returns client getter for ACK provider with assume role",
			provider: consts.ProviderTypeACK,
			rootConfig: &config.RootConfig{
				DataplaneAssumeRoleName: "test-role",
			},
		},
		{
			name:       "returns error for unknown provider",
			provider:   "unknown",
			rootConfig: &config.RootConfig{},
			wantErr:    true,
			errMsg:     "unknown provider type",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			getter, err := NetworkClientGetter(nil, testkits.Scheme(), tt.provider, "test-cluster", tt.rootConfig)
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				assert.Nil(t, getter)
				return
			}

			if err != nil {
				t.Skipf("Skipping test due to initialization error: %v", err)
			}
			assert.NotNil(t, getter)

			cg, ok := getter.(*clientGetter)
			assert.True(t, ok)
			assert.NotNil(t, cg.clientManager)
		})
	}
}

func TestClientGetter_GetClient(t *testing.T) {
	tests := []struct {
		name    string
		manager *testkits.ClientManager
		wantErr bool
		errMsg  string
		verify  func(*testing.T, client.Client)
	}{
		{
			name: "delegates to client manager get client",
			manager: testkits.NewClientManager().
				WithGetClient(func(ctx context.Context, key client.ObjectKey) (client.Client, error) {
					return testkits.BuildClient(), nil
				}),
			verify: func(t *testing.T, cli client.Client) {
				assert.NotNil(t, cli)
			},
		},
		{
			name: "returns error when client manager get client fails",
			manager: testkits.NewClientManager().
				WithGetClient(func(ctx context.Context, key client.ObjectKey) (client.Client, error) {
					return nil, assert.AnError
				}),
			wantErr: true,
			errMsg:  assert.AnError.Error(),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			getter := &clientGetter{
				clusterName:   "test-cluster",
				clientManager: tt.manager,
			}

			cli, err := getter.GetClient(context.Background())
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
			if tt.verify != nil {
				tt.verify(t, cli)
			}
		})
	}
}

func TestClientGetter_GetMetricsClient(t *testing.T) {
	tests := []struct {
		name    string
		manager *testkits.ClientManager
		wantErr bool
		errMsg  string
	}{
		{
			name: "delegates to client manager get metrics client",
			manager: testkits.NewClientManager().
				WithGetMetricsClient(func(ctx context.Context, key client.ObjectKey) (resourceclient.MetricsV1beta1Interface, error) {
					return nil, nil
				}),
		},
		{
			name: "returns error when client manager get metrics client fails",
			manager: testkits.NewClientManager().
				WithGetMetricsClient(func(ctx context.Context, key client.ObjectKey) (resourceclient.MetricsV1beta1Interface, error) {
					return nil, assert.AnError
				}),
			wantErr: true,
			errMsg:  assert.AnError.Error(),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			getter := &clientGetter{
				clusterName:   "test-cluster",
				clientManager: tt.manager,
			}

			_, err := getter.GetMetricsClient(context.Background())
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
		})
	}
}

func TestClientGetter_GetHelmConfig(t *testing.T) {
	tests := []struct {
		name    string
		manager *testkits.ClientManager
		wantErr bool
		errMsg  string
	}{
		{
			name: "creates helm action configuration",
			manager: testkits.NewClientManager().
				WithGetConfig(func(ctx context.Context, key client.ObjectKey) (*rest.Config, error) {
					return &rest.Config{Host: "https://test.example.com"}, nil
				}),
		},
		{
			name: "returns error when get config fails",
			manager: testkits.NewClientManager().
				WithGetConfig(func(ctx context.Context, key client.ObjectKey) (*rest.Config, error) {
					return nil, assert.AnError
				}),
			wantErr: true,
			errMsg:  "failed to get remote kubeconfig",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			getter := &clientGetter{
				clusterName:   "test-cluster",
				clientManager: tt.manager,
			}

			helmCfg, err := getter.GetHelmConfig(context.Background())
			if tt.wantErr {
				assert.Error(t, err)
				if tt.errMsg != "" {
					assert.Contains(t, err.Error(), tt.errMsg)
				}
				return
			}

			assert.NoError(t, err)
			assert.NotNil(t, helmCfg)
		})
	}
}
